Merge branch 'x1_010' of https://github.com/cam900/furnace into cam900-x1_010
This commit is contained in:
commit
c6fda7a156
29 changed files with 1754 additions and 38 deletions
224
src/engine/platform/sound/x1_010/x1_010.cpp
Normal file
224
src/engine/platform/sound/x1_010/x1_010.cpp
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holders: cam900
|
||||
Seta/Allumer X1-010 Emulation core
|
||||
|
||||
the chip has 16 voices, all voices can be switchable to Wavetable or PCM sample playback mode.
|
||||
It has also 2 output channels, but no known hardware using this feature for stereo sound.
|
||||
|
||||
Wavetable needs to paired with envelope, it's always enabled and similar as AY PSG's one
|
||||
but its shape is stored at RAM.
|
||||
|
||||
PCM volume is stored by each register.
|
||||
|
||||
Both volume is 4bit per output.
|
||||
|
||||
Everything except PCM sample is stored at paired 8 bit RAM.
|
||||
|
||||
RAM layout (common case: Address bit 12 is swapped when RAM is shared with CPU)
|
||||
|
||||
-----------------------------
|
||||
0000...007f Voice Registers
|
||||
|
||||
0000...0007 Voice 0 Register
|
||||
|
||||
Address Bits Description
|
||||
7654 3210
|
||||
0 x--- ---- Frequency divider*
|
||||
---- -x-- Envelope one-shot mode
|
||||
---- --x- Sound format
|
||||
---- --0- PCM
|
||||
---- --1- Wavetable
|
||||
---- ---x Keyon/off
|
||||
PCM case:
|
||||
1 xxxx xxxx Volume (Each nibble is for each output)
|
||||
|
||||
2 xxxx xxxx Frequency*
|
||||
|
||||
4 xxxx xxxx Start address / 4096
|
||||
|
||||
5 xxxx xxxx 0x100 - (End address / 4096)
|
||||
Wavetable case:
|
||||
1 ---x xxxx Wavetable data select
|
||||
|
||||
2 xxxx xxxx Frequency LSB*
|
||||
3 xxxx xxxx "" MSB
|
||||
|
||||
4 xxxx xxxx Envelope period (.10 fixed point, Low 8 bit)
|
||||
|
||||
5 ---x xxxx Envelope shape select (!= 0 : Reserved for Voice registers)
|
||||
|
||||
0008...000f Voice 1 Register
|
||||
...
|
||||
0078...007f Voice 15 Register
|
||||
-----------------------------
|
||||
0080...0fff Envelope shape data (Same as volume; Each nibble is for each output)
|
||||
|
||||
0080...00ff Envelope shape data 1
|
||||
0100...017f Envelope shape data 2
|
||||
...
|
||||
0f80...0fff Envelope shape data 31
|
||||
-----------------------------
|
||||
1000...1fff Wavetable data
|
||||
|
||||
1000...107f Wavetable data 0
|
||||
1080...10ff Wavetable data 1
|
||||
...
|
||||
1f80...1fff Wavetable data 31
|
||||
-----------------------------
|
||||
|
||||
* Frequency is 4.4 fixed point for PCM,
|
||||
6.10 for Wavetable.
|
||||
Frequency divider is higher precision or just right shift?
|
||||
needs verification.
|
||||
*/
|
||||
|
||||
#include "x1_010.hpp"
|
||||
|
||||
void x1_010_core::tick()
|
||||
{
|
||||
// reset output
|
||||
m_out[0] = m_out[1] = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
voice_t &v = m_voice[i];
|
||||
v.tick();
|
||||
m_out[0] += v.data * v.vol_out[0];
|
||||
m_out[1] += v.data * v.vol_out[1];
|
||||
}
|
||||
}
|
||||
|
||||
void x1_010_core::voice_t::tick()
|
||||
{
|
||||
data = vol_out[0] = vol_out[1] = 0;
|
||||
if (flag.keyon)
|
||||
{
|
||||
if (flag.wavetable) // Wavetable
|
||||
{
|
||||
// envelope, each nibble is for each output
|
||||
u8 vol = m_host.m_envelope[(bitfield(end_envshape, 0, 5) << 7) | bitfield(env_acc, 10, 7)];
|
||||
vol_out[0] = bitfield(vol, 4, 4);
|
||||
vol_out[1] = bitfield(vol, 0, 4);
|
||||
env_acc += start_envfreq;
|
||||
if (flag.env_oneshot && bitfield(env_acc, 17))
|
||||
flag.keyon = false;
|
||||
else
|
||||
env_acc = bitfield(env_acc, 0, 17);
|
||||
// get wavetable data
|
||||
data = m_host.m_wave[(bitfield(vol_wave, 0, 5) << 7) | bitfield(acc, 10, 7)];
|
||||
acc = bitfield(acc + (freq >> flag.div), 0, 17);
|
||||
}
|
||||
else // PCM sample
|
||||
{
|
||||
// volume register, each nibble is for each output
|
||||
vol_out[0] = bitfield(vol_wave, 4, 4);
|
||||
vol_out[1] = bitfield(vol_wave, 0, 4);
|
||||
// get PCM sample
|
||||
data = m_host.m_intf.read_byte(bitfield(acc, 4, 20));
|
||||
acc += bitfield(freq, 0, 8) >> flag.div;
|
||||
if ((acc >> 16) > (0xff ^ end_envshape))
|
||||
flag.keyon = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u8 x1_010_core::ram_r(u16 offset)
|
||||
{
|
||||
if (offset & 0x1000) // wavetable data
|
||||
return m_wave[offset & 0xfff];
|
||||
else if (offset & 0xf80) // envelope shape data
|
||||
return m_envelope[offset & 0xfff];
|
||||
else // channel register
|
||||
return m_voice[bitfield(offset, 3, 4)].reg_r(offset & 0x7);
|
||||
}
|
||||
|
||||
void x1_010_core::ram_w(u16 offset, u8 data)
|
||||
{
|
||||
if (offset & 0x1000) // wavetable data
|
||||
m_wave[offset & 0xfff] = data;
|
||||
else if (offset & 0xf80) // envelope shape data
|
||||
m_envelope[offset & 0xfff] = data;
|
||||
else // channel register
|
||||
m_voice[bitfield(offset, 3, 4)].reg_w(offset & 0x7, data);
|
||||
}
|
||||
|
||||
u8 x1_010_core::voice_t::reg_r(u8 offset)
|
||||
{
|
||||
switch (offset & 0x7)
|
||||
{
|
||||
case 0x00: return (flag.div << 7)
|
||||
| (flag.env_oneshot << 2)
|
||||
| (flag.wavetable << 1)
|
||||
| (flag.keyon << 0);
|
||||
case 0x01: return vol_wave;
|
||||
case 0x02: return bitfield(freq, 0, 8);
|
||||
case 0x03: return bitfield(freq, 8, 8);
|
||||
case 0x04: return start_envfreq;
|
||||
case 0x05: return end_envshape;
|
||||
default: break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void x1_010_core::voice_t::reg_w(u8 offset, u8 data)
|
||||
{
|
||||
switch (offset & 0x7)
|
||||
{
|
||||
case 0x00:
|
||||
{
|
||||
const bool prev_keyon = flag.keyon;
|
||||
flag.div = bitfield(data, 7);
|
||||
flag.env_oneshot = bitfield(data, 2);
|
||||
flag.wavetable = bitfield(data, 1);
|
||||
flag.keyon = bitfield(data, 0);
|
||||
if (!prev_keyon && flag.keyon) // Key on
|
||||
{
|
||||
acc = flag.wavetable ? 0 : (u32(start_envfreq) << 16);
|
||||
env_acc = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x01:
|
||||
vol_wave = data;
|
||||
break;
|
||||
case 0x02:
|
||||
freq = (freq & 0xff00) | data;
|
||||
break;
|
||||
case 0x03:
|
||||
freq = (freq & 0x00ff) | (u16(data) << 8);
|
||||
break;
|
||||
case 0x04:
|
||||
start_envfreq = data;
|
||||
break;
|
||||
case 0x05:
|
||||
end_envshape = data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void x1_010_core::voice_t::reset()
|
||||
{
|
||||
flag.reset();
|
||||
vol_wave = 0;
|
||||
freq = 0;
|
||||
start_envfreq = 0;
|
||||
end_envshape = 0;
|
||||
acc = 0;
|
||||
env_acc = 0;
|
||||
data = 0;
|
||||
vol_out[0] = vol_out[1] = 0;
|
||||
}
|
||||
|
||||
void x1_010_core::reset()
|
||||
{
|
||||
for (auto & elem : m_voice)
|
||||
elem.reset();
|
||||
|
||||
std::fill_n(&m_envelope[0], 0x1000, 0);
|
||||
std::fill_n(&m_wave[0], 0x1000, 0);
|
||||
m_out[0] = m_out[1] = 0;
|
||||
}
|
||||
127
src/engine/platform/sound/x1_010/x1_010.hpp
Normal file
127
src/engine/platform/sound/x1_010/x1_010.hpp
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holders: cam900
|
||||
Seta/Allumer X1-010 Emulation core
|
||||
|
||||
See x1_010.cpp for more info.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#ifndef _VGSOUND_EMU_X1_010_HPP
|
||||
#define _VGSOUND_EMU_X1_010_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef signed char s8;
|
||||
typedef signed int s32;
|
||||
|
||||
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
|
||||
{
|
||||
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
|
||||
}
|
||||
|
||||
class x1_010_mem_intf
|
||||
{
|
||||
public:
|
||||
virtual u8 read_byte(u32 address) { return 0; }
|
||||
};
|
||||
|
||||
class x1_010_core
|
||||
{
|
||||
friend class x1_010_mem_intf;
|
||||
public:
|
||||
// constructor
|
||||
x1_010_core(x1_010_mem_intf &intf)
|
||||
: m_voice{*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,
|
||||
*this,*this,*this,*this}
|
||||
, m_intf(intf)
|
||||
{
|
||||
m_envelope = std::make_unique<u8[]>(0x1000);
|
||||
m_wave = std::make_unique<u8[]>(0x1000);
|
||||
|
||||
std::fill_n(&m_envelope[0], 0x1000, 0);
|
||||
std::fill_n(&m_wave[0], 0x1000, 0);
|
||||
}
|
||||
|
||||
// register accessor
|
||||
u8 ram_r(u16 offset);
|
||||
void ram_w(u16 offset, u8 data);
|
||||
|
||||
// getters
|
||||
s32 output(u8 channel) { return m_out[channel & 1]; }
|
||||
|
||||
// internal state
|
||||
void reset();
|
||||
void tick();
|
||||
|
||||
private:
|
||||
// 16 voices in chip
|
||||
struct voice_t
|
||||
{
|
||||
// constructor
|
||||
voice_t(x1_010_core &host) : m_host(host) {}
|
||||
|
||||
// internal state
|
||||
void reset();
|
||||
void tick();
|
||||
|
||||
// register accessor
|
||||
u8 reg_r(u8 offset);
|
||||
void reg_w(u8 offset, u8 data);
|
||||
|
||||
// registers
|
||||
x1_010_core &m_host;
|
||||
struct flag_t
|
||||
{
|
||||
u8 div : 1;
|
||||
u8 env_oneshot : 1;
|
||||
u8 wavetable : 1;
|
||||
u8 keyon : 1;
|
||||
void reset()
|
||||
{
|
||||
div = 0;
|
||||
env_oneshot = 0;
|
||||
wavetable = 0;
|
||||
keyon = 0;
|
||||
}
|
||||
flag_t()
|
||||
: div(0)
|
||||
, env_oneshot(0)
|
||||
, wavetable(0)
|
||||
, keyon(0)
|
||||
{ }
|
||||
};
|
||||
flag_t flag;
|
||||
u8 vol_wave = 0;
|
||||
u16 freq = 0;
|
||||
u8 start_envfreq = 0;
|
||||
u8 end_envshape = 0;
|
||||
|
||||
// internal registers
|
||||
u32 acc = 0;
|
||||
u32 env_acc = 0;
|
||||
s8 data = 0;
|
||||
u8 vol_out[2] = {0};
|
||||
};
|
||||
voice_t m_voice[16];
|
||||
|
||||
// RAM
|
||||
std::unique_ptr<u8[]> m_envelope = nullptr;
|
||||
std::unique_ptr<u8[]> m_wave = nullptr;
|
||||
|
||||
// output data
|
||||
s32 m_out[2] = {0};
|
||||
|
||||
x1_010_mem_intf &m_intf;
|
||||
};
|
||||
|
||||
#endif
|
||||
884
src/engine/platform/x1_010.cpp
Normal file
884
src/engine/platform/x1_010.cpp
Normal file
|
|
@ -0,0 +1,884 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "x1_010.h"
|
||||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
//#define rWrite(a,v) pendingWrites[a]=v;
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) { x1_010->ram_w(a,v); if (dumpWrites) { addWrite(a,v); } }
|
||||
|
||||
#define chRead(c,a) x1_010->ram_r((c<<3)|(a&7))
|
||||
#define chWrite(c,a,v) rWrite((c<<3)|(a&7),v)
|
||||
#define waveWrite(c,a,v) rWrite(0x1000|(chan[c].waveBank<<11)|(c<<7)|(a&0x7f),(v-128)&0xff)
|
||||
#define envFill(c,a) rWrite(0x800|(c<<7)|(a&0x7f),(chan[c].lvol<<4)|chan[c].rvol)
|
||||
#define envWrite(c,a,l,r) rWrite(0x800|(c<<7)|(a&0x7f),(((chan[c].lvol*(l))/15)<<4)|((chan[c].rvol*(r))/15))
|
||||
|
||||
#define refreshControl(c) chWrite(c,0,chan[c].active?(chan[c].pcm?1:((chan[c].env.flag.envEnable && chan[c].env.flag.envOneshot)?7:3)):0);
|
||||
|
||||
#define CHIP_FREQBASE 4194304
|
||||
|
||||
const char* regCheatSheetX1_010[]={
|
||||
// Channel registers
|
||||
"Ch00_Control", "0000",
|
||||
"Ch00_PCMVol_WavSel", "0001",
|
||||
"Ch00_FreqL", "0002",
|
||||
"Ch00_FreqH", "0003",
|
||||
"Ch00_Start_EnvFrq", "0004",
|
||||
"Ch00_End_EnvSel", "0005",
|
||||
"Ch01_Control", "0008",
|
||||
"Ch01_PCMVol_WavSel", "0009",
|
||||
"Ch01_FreqL", "000A",
|
||||
"Ch01_FreqH", "000B",
|
||||
"Ch01_Start_EnvFrq", "000C",
|
||||
"Ch01_End_EnvSel", "000D",
|
||||
"Ch02_Control", "0010",
|
||||
"Ch02_PCMVol_WavSel", "0011",
|
||||
"Ch02_FreqL", "0012",
|
||||
"Ch02_FreqH", "0013",
|
||||
"Ch02_Start_EnvFrq", "0014",
|
||||
"Ch02_End_EnvSel", "0015",
|
||||
"Ch03_Control", "0018",
|
||||
"Ch03_PCMVol_WavSel", "0019",
|
||||
"Ch03_FreqL", "001A",
|
||||
"Ch03_FreqH", "001B",
|
||||
"Ch03_Start_EnvFrq", "001C",
|
||||
"Ch03_End_EnvSel", "001D",
|
||||
"Ch04_Control", "0020",
|
||||
"Ch04_PCMVol_WavSel", "0021",
|
||||
"Ch04_FreqL", "0022",
|
||||
"Ch04_FreqH", "0023",
|
||||
"Ch04_Start_EnvFrq", "0024",
|
||||
"Ch04_End_EnvSel", "0025",
|
||||
"Ch05_Control", "0028",
|
||||
"Ch05_PCMVol_WavSel", "0029",
|
||||
"Ch05_FreqL", "002A",
|
||||
"Ch05_FreqH", "002B",
|
||||
"Ch05_Start_EnvFrq", "002C",
|
||||
"Ch05_End_EnvSel", "002D",
|
||||
"Ch06_Control", "0030",
|
||||
"Ch06_PCMVol_WavSel", "0031",
|
||||
"Ch06_FreqL", "0032",
|
||||
"Ch06_FreqH", "0033",
|
||||
"Ch06_Start_EnvFrq", "0034",
|
||||
"Ch06_End_EnvSel", "0035",
|
||||
"Ch07_Control", "0038",
|
||||
"Ch07_PCMVol_WavSel", "0039",
|
||||
"Ch07_FreqL", "003A",
|
||||
"Ch07_FreqH", "003B",
|
||||
"Ch07_Start_EnvFrq", "003C",
|
||||
"Ch07_End_EnvSel", "003D",
|
||||
"Ch08_Control", "0040",
|
||||
"Ch08_PCMVol_WavSel", "0041",
|
||||
"Ch08_FreqL", "0042",
|
||||
"Ch08_FreqH", "0043",
|
||||
"Ch08_Start_EnvFrq", "0044",
|
||||
"Ch08_End_EnvSel", "0045",
|
||||
"Ch09_Control", "0048",
|
||||
"Ch09_PCMVol_WavSel", "0049",
|
||||
"Ch09_FreqL", "004A",
|
||||
"Ch09_FreqH", "004B",
|
||||
"Ch09_Start_EnvFrq", "004C",
|
||||
"Ch09_End_EnvSel", "004D",
|
||||
"Ch10_Control", "0050",
|
||||
"Ch10_PCMVol_WavSel", "0051",
|
||||
"Ch10_FreqL", "0052",
|
||||
"Ch10_FreqH", "0053",
|
||||
"Ch10_Start_EnvFrq", "0054",
|
||||
"Ch10_End_EnvSel", "0055",
|
||||
"Ch11_Control", "0058",
|
||||
"Ch11_PCMVol_WavSel", "0059",
|
||||
"Ch11_FreqL", "005A",
|
||||
"Ch11_FreqH", "005B",
|
||||
"Ch11_Start_EnvFrq", "005C",
|
||||
"Ch11_End_EnvSel", "005D",
|
||||
"Ch12_Control", "0060",
|
||||
"Ch12_PCMVol_WavSel", "0061",
|
||||
"Ch12_FreqL", "0062",
|
||||
"Ch12_FreqH", "0063",
|
||||
"Ch12_Start_EnvFrq", "0064",
|
||||
"Ch12_End_EnvSel", "0065",
|
||||
"Ch13_Control", "0068",
|
||||
"Ch13_PCMVol_WavSel", "0069",
|
||||
"Ch13_FreqL", "006A",
|
||||
"Ch13_FreqH", "006B",
|
||||
"Ch13_Start_EnvFrq", "006C",
|
||||
"Ch13_End_EnvSel", "006D",
|
||||
"Ch14_Control", "0070",
|
||||
"Ch14_PCMVol_WavSel", "0071",
|
||||
"Ch14_FreqL", "0072",
|
||||
"Ch14_FreqH", "0073",
|
||||
"Ch14_Start_EnvFrq", "0074",
|
||||
"Ch14_End_EnvSel", "0075",
|
||||
"Ch15_Control", "0078",
|
||||
"Ch15_PCMVol_WavSel", "0079",
|
||||
"Ch15_FreqL", "007A",
|
||||
"Ch15_FreqH", "007B",
|
||||
"Ch15_Start_EnvFrq", "007C",
|
||||
"Ch15_End_EnvSel", "007D",
|
||||
// Envelope data
|
||||
"Env01Data", "0080",
|
||||
"Env02Data", "0100",
|
||||
"Env03Data", "0180",
|
||||
"Env04Data", "0200",
|
||||
"Env05Data", "0280",
|
||||
"Env06Data", "0300",
|
||||
"Env07Data", "0380",
|
||||
"Env08Data", "0400",
|
||||
"Env09Data", "0480",
|
||||
"Env10Data", "0500",
|
||||
"Env11Data", "0580",
|
||||
"Env12Data", "0600",
|
||||
"Env13Data", "0680",
|
||||
"Env14Data", "0700",
|
||||
"Env15Data", "0780",
|
||||
"Env16Data", "0800",
|
||||
"Env17Data", "0880",
|
||||
"Env18Data", "0900",
|
||||
"Env19Data", "0980",
|
||||
"Env20Data", "0A00",
|
||||
"Env21Data", "0A80",
|
||||
"Env22Data", "0B00",
|
||||
"Env23Data", "0B80",
|
||||
"Env24Data", "0C00",
|
||||
"Env25Data", "0C80",
|
||||
"Env26Data", "0D00",
|
||||
"Env27Data", "0D80",
|
||||
"Env28Data", "0E00",
|
||||
"Env29Data", "0E80",
|
||||
"Env30Data", "0F00",
|
||||
"Env31Data", "0F80",
|
||||
// Wavetable data
|
||||
"Wave00Data", "1000",
|
||||
"Wave01Data", "1080",
|
||||
"Wave02Data", "1100",
|
||||
"Wave03Data", "1180",
|
||||
"Wave04Data", "1200",
|
||||
"Wave05Data", "1280",
|
||||
"Wave06Data", "1300",
|
||||
"Wave07Data", "1380",
|
||||
"Wave08Data", "1400",
|
||||
"Wave09Data", "1480",
|
||||
"Wave10Data", "1500",
|
||||
"Wave11Data", "1580",
|
||||
"Wave12Data", "1600",
|
||||
"Wave13Data", "1680",
|
||||
"Wave14Data", "1700",
|
||||
"Wave15Data", "1780",
|
||||
"Wave16Data", "1800",
|
||||
"Wave17Data", "1880",
|
||||
"Wave18Data", "1900",
|
||||
"Wave19Data", "1980",
|
||||
"Wave20Data", "1A00",
|
||||
"Wave21Data", "1A80",
|
||||
"Wave22Data", "1B00",
|
||||
"Wave23Data", "1B80",
|
||||
"Wave24Data", "1C00",
|
||||
"Wave25Data", "1C80",
|
||||
"Wave26Data", "1D00",
|
||||
"Wave27Data", "1D80",
|
||||
"Wave28Data", "1E00",
|
||||
"Wave29Data", "1E80",
|
||||
"Wave30Data", "1F00",
|
||||
"Wave31Data", "1F80",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformX1_010::getRegisterSheet() {
|
||||
return regCheatSheetX1_010;
|
||||
}
|
||||
|
||||
const char* DivPlatformX1_010::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Change envelope shape";
|
||||
break;
|
||||
case 0x17:
|
||||
return "17xx: Toggle PCM mode";
|
||||
break;
|
||||
case 0x20:
|
||||
return "20xx: Set PCM frequency (1 to FF)";
|
||||
break;
|
||||
case 0x22:
|
||||
return "22xx: Set envelope mode (bit 0: enable, bit 1: one-shot, bit 2: split shape to L/R, bit 3/5: H.invert right/left, bit 4/6: V.invert right/left)";
|
||||
break;
|
||||
case 0x23:
|
||||
return "23xx: Set envelope period";
|
||||
break;
|
||||
case 0x25:
|
||||
return "25xx: Envelope slide up";
|
||||
break;
|
||||
case 0x26:
|
||||
return "26xx: Envelope slide down";
|
||||
break;
|
||||
case 0x29:
|
||||
return "29xy: Set auto-envelope (x: numerator; y: denominator)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
x1_010->tick();
|
||||
|
||||
signed int tempL=x1_010->output(0);
|
||||
signed int tempR=x1_010->output(1);
|
||||
|
||||
if (tempL<-32768) tempL=-32768;
|
||||
if (tempL>32767) tempL=32767;
|
||||
if (tempR<-32768) tempR=-32768;
|
||||
if (tempR>32767) tempR=32767;
|
||||
|
||||
//printf("tempL: %d tempR: %d\n",tempL,tempR);
|
||||
bufL[h]=stereo?tempL:((tempL+tempR)>>1);
|
||||
bufR[h]=stereo?tempR:bufL[h];
|
||||
}
|
||||
}
|
||||
|
||||
double DivPlatformX1_010::NoteX1_010(int ch, int note) {
|
||||
if (chan[ch].pcm) { // PCM note
|
||||
double off=1.0;
|
||||
int sample=chan[ch].sample;
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=s->centerRate/8363.0;
|
||||
}
|
||||
}
|
||||
return off*parent->calcBaseFreq(chipClock,8192,note,false);
|
||||
}
|
||||
// Wavetable note
|
||||
return NOTE_FREQUENCY(note);
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::updateWave(int ch) {
|
||||
DivWavetable* wt=parent->getWave(chan[ch].wave);
|
||||
if (chan[ch].active) {
|
||||
chan[ch].waveBank ^= 1;
|
||||
}
|
||||
for (int i=0; i<128; i++) {
|
||||
if (wt->max<1 || wt->len<1) {
|
||||
waveWrite(ch,i,0);
|
||||
} else {
|
||||
waveWrite(ch,i,wt->data[i*wt->len/128]*255/wt->max);
|
||||
}
|
||||
}
|
||||
if (!chan[ch].pcm) {
|
||||
chWrite(ch,1,(chan[ch].waveBank<<4)|(ch&0xf));
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::updateEnvelope(int ch) {
|
||||
if (!chan[ch].pcm) {
|
||||
if (isMuted[ch]) {
|
||||
for (int i=0; i<128; i++) {
|
||||
rWrite(0x800|(ch<<7)|(i&0x7f),0);
|
||||
}
|
||||
} else {
|
||||
if (!chan[ch].env.flag.envEnable) {
|
||||
for (int i=0; i<128; i++) {
|
||||
envFill(ch,i);
|
||||
}
|
||||
} else {
|
||||
DivWavetable* wt=parent->getWave(chan[ch].env.shape);
|
||||
for (int i=0; i<128; i++) {
|
||||
if (wt->max<1 || wt->len<1) {
|
||||
envFill(ch,i);
|
||||
} else if (chan[ch].env.flag.envSplit || chan[ch].env.flag.envHinvR || chan[ch].env.flag.envVinvR || chan[ch].env.flag.envHinvL || chan[ch].env.flag.envVinvL) { // Stereo config
|
||||
int la=i,ra=i;
|
||||
int lo,ro;
|
||||
if (chan[ch].env.flag.envHinvR) { ra=127-i; } // horizontal invert right envelope
|
||||
if (chan[ch].env.flag.envHinvL) { la=127-i; } // horizontal invert left envelope
|
||||
if (chan[ch].env.flag.envSplit) { // Split shape to left and right half
|
||||
lo=wt->data[la*(wt->len/128/2)]*15/wt->max;
|
||||
ro=wt->data[(ra+128)*(wt->len/128/2)]*15/wt->max;
|
||||
} else {
|
||||
lo=wt->data[la*wt->len/128]*15/wt->max;
|
||||
ro=wt->data[ra*wt->len/128]*15/wt->max;
|
||||
}
|
||||
if (chan[ch].env.flag.envVinvR) { ro=15-ro; } // vertical invert right envelope
|
||||
if (chan[ch].env.flag.envVinvL) { lo=15-lo; } // vertical invert left envelope
|
||||
envWrite(ch,i,lo,ro);
|
||||
} else {
|
||||
int out=wt->data[i*wt->len/128]*15/wt->max;
|
||||
envWrite(ch,i,out,out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
chWrite(ch,5,0x10|(ch&0xf));
|
||||
} else {
|
||||
chWrite(ch,1,(chan[ch].lvol<<4)|chan[ch].rvol);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::tick() {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.hadVol) {
|
||||
signed char macroVol=((chan[i].vol&15)*MIN(chan[i].furnacePCM?64:15,chan[i].std.vol))/(chan[i].furnacePCM?64:15);
|
||||
if ((!isMuted[i]) && (macroVol!=chan[i].outVol)) {
|
||||
chan[i].outVol=macroVol;
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
if ((!chan[i].pcm) || chan[i].furnacePCM) {
|
||||
if (chan[i].std.hadArp) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arpMode) {
|
||||
chan[i].baseFreq=NoteX1_010(i,chan[i].std.arp);
|
||||
} else {
|
||||
chan[i].baseFreq=NoteX1_010(i,chan[i].note+chan[i].std.arp);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
|
||||
chan[i].baseFreq=NoteX1_010(i,chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadWave && !chan[i].pcm) {
|
||||
if (chan[i].wave!=chan[i].std.wave) {
|
||||
chan[i].wave=chan[i].std.wave;
|
||||
if (!chan[i].pcm) {
|
||||
updateWave(i);
|
||||
if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadEx1) {
|
||||
bool nextEnable=(chan[i].std.ex1&1);
|
||||
if (nextEnable!=(chan[i].env.flag.envEnable)) {
|
||||
chan[i].env.flag.envEnable=nextEnable;
|
||||
if (!chan[i].pcm) {
|
||||
if (!isMuted[i]) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
refreshControl(i);
|
||||
}
|
||||
}
|
||||
bool nextOneshot=(chan[i].std.ex1&2);
|
||||
if (nextOneshot!=(chan[i].env.flag.envOneshot)) {
|
||||
chan[i].env.flag.envOneshot=nextOneshot;
|
||||
if (!chan[i].pcm) {
|
||||
refreshControl(i);
|
||||
}
|
||||
}
|
||||
bool nextSplit=(chan[i].std.ex1&4);
|
||||
if (nextSplit!=(chan[i].env.flag.envSplit)) {
|
||||
chan[i].env.flag.envSplit=nextSplit;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextHinvR=(chan[i].std.ex1&8);
|
||||
if (nextHinvR!=(chan[i].env.flag.envHinvR)) {
|
||||
chan[i].env.flag.envHinvR=nextHinvR;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextVinvR=(chan[i].std.ex1&16);
|
||||
if (nextVinvR!=(chan[i].env.flag.envVinvR)) {
|
||||
chan[i].env.flag.envVinvR=nextVinvR;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextHinvL=(chan[i].std.ex1&32);
|
||||
if (nextHinvL!=(chan[i].env.flag.envHinvL)) {
|
||||
chan[i].env.flag.envHinvL=nextHinvL;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextVinvL=(chan[i].std.ex1&64);
|
||||
if (nextVinvL!=(chan[i].env.flag.envVinvL)) {
|
||||
chan[i].env.flag.envVinvL=nextVinvL;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadEx2) {
|
||||
if (chan[i].env.shape!=chan[i].std.ex2) {
|
||||
chan[i].env.shape=chan[i].std.ex2;
|
||||
if (!chan[i].pcm) {
|
||||
if (chan[i].env.flag.envEnable && (!isMuted[i])) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadEx3) {
|
||||
chan[i].autoEnvNum=chan[i].std.ex3;
|
||||
if (!chan[i].pcm) {
|
||||
chan[i].freqChanged=true;
|
||||
if (!chan[i].std.willAlg) chan[i].autoEnvDen=1;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadAlg) {
|
||||
chan[i].autoEnvDen=chan[i].std.alg;
|
||||
if (!chan[i].pcm) {
|
||||
chan[i].freqChanged=true;
|
||||
if (!chan[i].std.willEx3) chan[i].autoEnvNum=1;
|
||||
}
|
||||
}
|
||||
if (chan[i].envChanged) {
|
||||
if (!isMuted[i]) {
|
||||
chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15;
|
||||
chan[i].rvol=((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15;
|
||||
}
|
||||
updateEnvelope(i);
|
||||
chan[i].envChanged=false;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false);
|
||||
if (chan[i].pcm) {
|
||||
if (chan[i].freq<1) chan[i].freq=1;
|
||||
if (chan[i].freq>255) chan[i].freq=255;
|
||||
chWrite(i,2,chan[i].freq&0xff);
|
||||
} else {
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
chWrite(i,2,chan[i].freq&0xff);
|
||||
chWrite(i,3,(chan[i].freq>>8)&0xff);
|
||||
if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) {
|
||||
chan[i].env.period=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>12;
|
||||
chWrite(i,4,chan[i].env.period);
|
||||
}
|
||||
}
|
||||
if (chan[i].keyOn || chan[i].keyOff || (chRead(i,0)&1)) {
|
||||
refreshControl(i);
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].env.slide!=0) {
|
||||
chan[i].env.slidefrac+=chan[i].env.slide;
|
||||
while (chan[i].env.slidefrac>0xf) {
|
||||
chan[i].env.slidefrac-=0x10;
|
||||
if (chan[i].env.period<0xff) {
|
||||
chan[i].env.period++;
|
||||
if (!chan[i].pcm) {
|
||||
chWrite(i,4,chan[i].env.period);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (chan[i].env.slidefrac<-0xf) {
|
||||
chan[i].env.slidefrac+=0x10;
|
||||
if (chan[i].env.period>0) {
|
||||
chan[i].env.period--;
|
||||
if (!chan[i].pcm) {
|
||||
chWrite(i,4,chan[i].env.period);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformX1_010::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
chWrite(c.chan,0,0); // reset previous note
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
if ((ins->type==DIV_INS_AMIGA) || chan[c.chan].pcm) {
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
chan[c.chan].furnacePCM=true;
|
||||
} else {
|
||||
chan[c.chan].furnacePCM=false;
|
||||
}
|
||||
if (skipRegisterWrites) break;
|
||||
if (chan[c.chan].furnacePCM) {
|
||||
chan[c.chan].pcm=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].sample=ins->amiga.initSample;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
chWrite(c.chan,4,(s->offX1_010>>12)&0xff);
|
||||
int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded
|
||||
chWrite(c.chan,5,(0x100-(end>>12))&0xff);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
|
||||
chWrite(c.chan,0,0); // reset
|
||||
chWrite(c.chan,1,0);
|
||||
chWrite(c.chan,2,0);
|
||||
chWrite(c.chan,4,0);
|
||||
chWrite(c.chan,5,0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
|
||||
chWrite(c.chan,0,0); // reset
|
||||
chWrite(c.chan,1,0);
|
||||
chWrite(c.chan,2,0);
|
||||
chWrite(c.chan,4,0);
|
||||
chWrite(c.chan,5,0);
|
||||
break;
|
||||
}
|
||||
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
|
||||
chWrite(c.chan,4,(s->offX1_010>>12)&0xff);
|
||||
int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded
|
||||
chWrite(c.chan,5,(0x100-(end>>12))&0xff);
|
||||
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<4)/(chipClock/512);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
} else if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].envChanged=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
refreshControl(c.chan);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].pcm=false;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
chan[c.chan].ins=c.value;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.hasVol) {
|
||||
if (chan[c.chan].outVol!=c.value) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
if (chan[c.chan].std.hasVol) {
|
||||
return chan[c.chan].vol;
|
||||
}
|
||||
return chan[c.chan].outVol;
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_WAVE:
|
||||
chan[c.chan].wave=c.value;
|
||||
updateWave(c.chan);
|
||||
chan[c.chan].keyOn=true;
|
||||
break;
|
||||
case DIV_CMD_X1_010_ENVELOPE_SHAPE:
|
||||
if (chan[c.chan].env.shape!=c.value) {
|
||||
chan[c.chan].env.shape=c.value;
|
||||
if (!chan[c.chan].pcm) {
|
||||
if (chan[c.chan].env.flag.envEnable && (!isMuted[c.chan])) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
chan[c.chan].keyOn=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NoteX1_010(c.chan,c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value;
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
if (chan[c.chan].pcm!=(c.value&1)) {
|
||||
chan[c.chan].pcm=c.value&1;
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
sampleBank=c.value;
|
||||
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
if (chan[c.chan].pan!=c.value) {
|
||||
chan[c.chan].pan=c.value;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note+((chan[c.chan].std.willArp&&!chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_FREQ:
|
||||
if (chan[c.chan].pcm) {
|
||||
chan[c.chan].freq=MAX(1,c.value&0xff);
|
||||
chWrite(c.chan,2,chan[c.chan].freq&0xff);
|
||||
if (chRead(c.chan,0)&1) {
|
||||
refreshControl(c.chan);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_X1_010_ENVELOPE_MODE: {
|
||||
bool nextEnable=c.value&1;
|
||||
if (nextEnable!=(chan[c.chan].env.flag.envEnable)) {
|
||||
chan[c.chan].env.flag.envEnable=nextEnable;
|
||||
if (!chan[c.chan].pcm) {
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
refreshControl(c.chan);
|
||||
}
|
||||
}
|
||||
bool nextOneshot=c.value&2;
|
||||
if (nextOneshot!=(chan[c.chan].env.flag.envOneshot)) {
|
||||
chan[c.chan].env.flag.envOneshot=nextOneshot;
|
||||
if (!chan[c.chan].pcm) {
|
||||
refreshControl(c.chan);
|
||||
}
|
||||
}
|
||||
bool nextSplit=c.value&4;
|
||||
if (nextSplit!=(chan[c.chan].env.flag.envSplit)) {
|
||||
chan[c.chan].env.flag.envSplit=nextSplit;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextHinvR=c.value&8;
|
||||
if (nextHinvR!=(chan[c.chan].env.flag.envHinvR)) {
|
||||
chan[c.chan].env.flag.envHinvR=nextHinvR;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextVinvR=c.value&16;
|
||||
if (nextVinvR!=(chan[c.chan].env.flag.envVinvR)) {
|
||||
chan[c.chan].env.flag.envVinvR=nextVinvR;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextHinvL=c.value&32;
|
||||
if (nextHinvL!=(chan[c.chan].env.flag.envHinvL)) {
|
||||
chan[c.chan].env.flag.envHinvL=nextHinvL;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextVinvL=c.value&64;
|
||||
if (nextVinvL!=(chan[c.chan].env.flag.envVinvL)) {
|
||||
chan[c.chan].env.flag.envVinvL=nextVinvL;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_X1_010_ENVELOPE_PERIOD:
|
||||
chan[c.chan].env.period=c.value;
|
||||
if (!chan[c.chan].pcm) {
|
||||
chWrite(c.chan,4,chan[c.chan].env.period);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_X1_010_ENVELOPE_SLIDE:
|
||||
chan[c.chan].env.slide=c.value;
|
||||
break;
|
||||
case DIV_CMD_X1_010_AUTO_ENVELOPE:
|
||||
chan[c.chan].autoEnvNum=c.value>>4;
|
||||
chan[c.chan].autoEnvDen=c.value&15;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 15;
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
chan[ch].envChanged=true;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::forceIns() {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].envChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformX1_010::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformX1_010::getRegisterPool() {
|
||||
for (int i=0; i<0x2000; i++) {
|
||||
regPool[i]=x1_010->ram_r(i);
|
||||
}
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformX1_010::getRegisterPoolSize() {
|
||||
return 0x2000;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::reset() {
|
||||
memset(regPool,0,0x2000);
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i]=DivPlatformX1_010::Channel();
|
||||
chan[i].reset();
|
||||
}
|
||||
x1_010->reset();
|
||||
sampleBank=0;
|
||||
// set per-channel initial panning
|
||||
for (int i=0; i<16; i++) {
|
||||
chWrite(i,0,0);
|
||||
}
|
||||
}
|
||||
|
||||
bool DivPlatformX1_010::isStereo() {
|
||||
return stereo;
|
||||
}
|
||||
|
||||
bool DivPlatformX1_010::keyOffAffectsArp(int ch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::notifyWaveChange(int wave) {
|
||||
for (int i=0; i<16; i++) {
|
||||
if (chan[i].wave==wave) {
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::setFlags(unsigned int flags) {
|
||||
switch (flags&15) {
|
||||
case 0: // 16MHz (earlier hardwares)
|
||||
chipClock=16000000;
|
||||
break;
|
||||
case 1: // 16.67MHz (later hardwares)
|
||||
chipClock=50000000.0/3.0;
|
||||
break;
|
||||
// Other clock is used?
|
||||
}
|
||||
rate=chipClock/512;
|
||||
stereo=flags&16;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
stereo=false;
|
||||
for (int i=0; i<16; i++) {
|
||||
isMuted[i]=false;
|
||||
}
|
||||
setFlags(flags);
|
||||
intf.parent=parent;
|
||||
x1_010=new x1_010_core(intf);
|
||||
x1_010->reset();
|
||||
reset();
|
||||
return 16;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::quit() {
|
||||
delete x1_010;
|
||||
}
|
||||
|
||||
DivPlatformX1_010::~DivPlatformX1_010() {
|
||||
}
|
||||
144
src/engine/platform/x1_010.h
Normal file
144
src/engine/platform/x1_010.h
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef _X1_010_H
|
||||
#define _X1_010_H
|
||||
|
||||
#include <queue>
|
||||
#include "../dispatch.h"
|
||||
#include "../engine.h"
|
||||
#include "../macroInt.h"
|
||||
#include "sound/x1_010/x1_010.hpp"
|
||||
|
||||
class DivX1_010Interface: public x1_010_mem_intf {
|
||||
public:
|
||||
DivEngine* parent;
|
||||
int sampleBank;
|
||||
virtual u8 read_byte(u32 address) override {
|
||||
if (parent->x1_010Mem==NULL) return 0;
|
||||
return parent->x1_010Mem[address & 0xfffff];
|
||||
}
|
||||
DivX1_010Interface(): parent(NULL), sampleBank(0) {}
|
||||
};
|
||||
|
||||
class DivPlatformX1_010: public DivDispatch {
|
||||
struct Channel {
|
||||
struct Envelope {
|
||||
struct EnvFlag {
|
||||
unsigned char envEnable : 1;
|
||||
unsigned char envOneshot : 1;
|
||||
unsigned char envSplit : 1;
|
||||
unsigned char envHinvR : 1;
|
||||
unsigned char envVinvR : 1;
|
||||
unsigned char envHinvL : 1;
|
||||
unsigned char envVinvL : 1;
|
||||
void reset() {
|
||||
envEnable=0;
|
||||
envOneshot=0;
|
||||
envSplit=0;
|
||||
envHinvR=0;
|
||||
envVinvR=0;
|
||||
envHinvL=0;
|
||||
envVinvL=0;
|
||||
}
|
||||
EnvFlag():
|
||||
envEnable(0),
|
||||
envOneshot(0),
|
||||
envSplit(0),
|
||||
envHinvR(0),
|
||||
envVinvR(0),
|
||||
envHinvL(0),
|
||||
envVinvL(0) {}
|
||||
};
|
||||
int shape, period, slide, slidefrac;
|
||||
EnvFlag flag;
|
||||
void reset() {
|
||||
shape=-1;
|
||||
period=0;
|
||||
flag.reset();
|
||||
}
|
||||
Envelope():
|
||||
shape(-1),
|
||||
period(0),
|
||||
slide(0),
|
||||
slidefrac(0) {}
|
||||
};
|
||||
int freq, baseFreq, pitch, note;
|
||||
int wave, sample, ins;
|
||||
unsigned char pan, autoEnvNum, autoEnvDen;
|
||||
bool active, insChanged, envChanged, freqChanged, keyOn, keyOff, inPorta, furnacePCM, pcm;
|
||||
int vol, outVol, lvol, rvol;
|
||||
unsigned char waveBank;
|
||||
Envelope env;
|
||||
DivMacroInt std;
|
||||
void reset() {
|
||||
freq = baseFreq = pitch = note = 0;
|
||||
wave = sample = ins = -1;
|
||||
pan = 255;
|
||||
autoEnvNum = autoEnvDen = 0;
|
||||
active = false;
|
||||
insChanged = envChanged = freqChanged = true;
|
||||
keyOn = keyOff = inPorta = furnacePCM = pcm = false;
|
||||
vol = outVol = lvol = rvol = 15;
|
||||
waveBank = 0;
|
||||
}
|
||||
Channel():
|
||||
freq(0), baseFreq(0), pitch(0), note(0),
|
||||
wave(-1), sample(-1), ins(-1),
|
||||
pan(255), autoEnvNum(0), autoEnvDen(0),
|
||||
active(false), insChanged(true), envChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), furnacePCM(false), pcm(false),
|
||||
vol(15), outVol(15), lvol(15), rvol(15),
|
||||
waveBank(0) {}
|
||||
};
|
||||
Channel chan[16];
|
||||
bool isMuted[16];
|
||||
bool stereo=false;
|
||||
unsigned char sampleBank;
|
||||
DivX1_010Interface intf;
|
||||
x1_010_core* x1_010;
|
||||
unsigned char regPool[0x2000];
|
||||
double NoteX1_010(int ch, int note);
|
||||
void updateWave(int ch);
|
||||
void updateEnvelope(int ch);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void setFlags(unsigned int flags);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformX1_010();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -752,7 +752,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
chan[c.chan].std.init(ins);
|
||||
if (!chan[c.chan].std.willVol) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
immWrite(0x1b,chan[c.chan].outVol);
|
||||
immWrite(0x1b,chan[c.chan].outVol);
|
||||
}
|
||||
DivSample* s=parent->getSample(ins->amiga.initSample);
|
||||
immWrite(0x12,(s->offB>>8)&0xff);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue