diff --git a/CMakeLists.txt b/CMakeLists.txt index e3dbdbba..de4eefbc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -267,6 +267,8 @@ src/engine/platform/sound/lynx/Mikey.cpp src/engine/platform/sound/qsound.c +src/engine/platform/sound/x1_010/x1_010.cpp + src/engine/platform/sound/swan.cpp src/engine/platform/ym2610Interface.cpp @@ -310,6 +312,7 @@ src/engine/platform/amiga.cpp src/engine/platform/pcspkr.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp +src/engine/platform/x1_010.cpp src/engine/platform/lynx.cpp src/engine/platform/swan.cpp src/engine/platform/vera.cpp diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index fb77bdde..5592fb4e 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -10,7 +10,7 @@ double-click to open the instrument editor. every instrument can be renamed and have its type changed. -depending on the instrument type, there are currently 12 different types of an instrument editor: +depending on the instrument type, there are currently 13 different types of an instrument editor: - [FM synthesis](fm.md) - for use with YM2612, YM2151 and FM block portion of YM2610. - [Standard](standard.md) - for use with NES and Sega Master System's PSG sound source and its derivatives. @@ -22,9 +22,10 @@ depending on the instrument type, there are currently 12 different types of an i - [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source. - [TIA](tia.md) - for use with Atari 2600 system. - [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610. -- [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM and PC Engine's sample playback mode. -- [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console. -- [VERA](vera.md) - for use with Commander X16 VERA. +- [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM, X1-010 and PC Engine's sample playback mode. +- [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console. +- [VERA](vera.md) - for use with Commander X16 VERA. +- [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010. # macros diff --git a/papers/doc/4-instrument/x1_010.md b/papers/doc/4-instrument/x1_010.md new file mode 100644 index 00000000..8f3691ee --- /dev/null +++ b/papers/doc/4-instrument/x1_010.md @@ -0,0 +1,11 @@ +# X1-010 instrument editor + +X1-010 instrument editor consists of 7 macros. + +- [Volume] - volume levels sequence +- [Arpeggio]- pitch sequence +- [Waveform] - spicifies wavetables sequence +- [Envelope Mode] - allows shaping an envelope +- [Envelope] - spicifies envelope shape sequence, it's also wavetable. +- [Auto envelope numerator] - sets the envelope to the channel's frequency multiplied by numerator +- [Auto envelope denominator] - ets the envelope to the channel's frequency multiplied by denominator diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index 00573925..5e234bf6 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -2,4 +2,4 @@ Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. -Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms as of now, with 16-level height for GB and WS, and 32-level height for PCE. If larger wave will be defined for these two systems, it will be squashed to fit within the constraints of the system. +Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope and WS, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system. diff --git a/papers/doc/6-sample/README.md b/papers/doc/6-sample/README.md index 78eb322c..87ddf0f8 100644 --- a/papers/doc/6-sample/README.md +++ b/papers/doc/6-sample/README.md @@ -12,7 +12,8 @@ As of Furnace 0.5.5, the following sound chips have sample support: - PC Engine/TurboGrafx 16/Huc6280 (same conditions as above) - Amiga/Paula (on all channels AND resamplable, but you need to make an instrument with the Amiga format and tie it to a sample first) - Arcade/SEGA PCM (same as above but you don't need to make an instrument for it and you have to use the `20xx` effect command to resample your samples) - - Neo Geo/Neo Geo EXT-Ch2 (on the last 5 channels only and can be resampled the same way as above) + - Neo Geo/Neo Geo EXT-Ch2 (on the last 7 channels only and can be resampled the same way as above) + - Seta/Allumer X1-010 (same as above, and both `1701` and `20xx` effect commands are affected on all 16 channels) Furnace also has a feature where you can make an Amiga formarted instrument on the YM2612 and Huc6280 to resample a sample you have in the module. diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index a42e0d06..a6648a32 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -18,6 +18,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Atari 2600](tia.md) - [Philips SAA1099](saa1099.md) - [Microchip AY8930](ay8930.md) +- [Seta/Allumer X1-010](x1_010.md) - [WonderSwan](wonderswan.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all. diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md new file mode 100644 index 00000000..759d519c --- /dev/null +++ b/papers/doc/7-systems/x1_010.md @@ -0,0 +1,47 @@ +# Seta/Allumer X1-010 + +One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-80s to early-2000s. +It has 2 output channels, but no known hardware using this feature for stereo sound. +Later hardware paired this with external bankswitching logic, but its logic is not emulated now in current furnace revision. +Allumer one is just rebadged Seta's thing for use in their arcade hardwares. + +It has 16 channels, and all channels can be switchable to PCM sample or wavetable playback mode. +Wavetable needs to paired with envelope, this feature is similar as AY PSG, but its shape are stored at RAM: it means it is user-definable. + +In furnace, this chip is can be configurable for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output. + +# waveform type + +This chip supports 2 type waveforms, needs to paired external 8 KB RAM for use these features: + +One is signed 8 bit mono waveform, it's operated like other wavetable based sound systems. +These are stored at the bottom half of RAM at common case. + +Another one ("Envelope") is 4 bit stereo waveform, it's multiplied with above and calculates final output, Each nibble is used for each output channels. +These are stored at the upper half of RAM at common case. + +Both waveforms are 128 byte fixed size, it's freely allocated at each half of RAM except channel register area: each half can be stored total 32/31 waveforms at once. +In furnace, You can set envelope shape split mode. When it sets, its waveform will be split to left half and right half for each outputs. each max size are 128 bytes, total 256 bytes. + +# effects + +- `10xx`: change wave. +- `11xx`: change envelope shape. (also wavetable) +- `17xx`: toggle PCM mode. +- `20xx`: set PCM frequency. (1 to FF)* +- `22xx`: set envelope mode. + - bit 0 sets whether envelope will affect this channel. + - bit 1 sets whether envelope one-shot mode. when it sets, channel is halted after envelope cycle is ended. + - bit 2 sets whether envelope shape split mode. when it sets, envelope shape will splitted to left half and right half. + - bit 3/5 sets whether the right/left shape will mirror the original one. + - bit 4/6 sets whether the right/left output will mirror the original one. +- `23xx`: set envelope period. +- `25xx`: slide envelope period up. +- `26xx`: slide envelope period down. +- `29xy`: enable auto-envelope mode. + - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. + - `x` is the numerator. + - `y` is the denominator. + - if `x` or `y` are 0 this will disable auto-envelope mode. + +* PCM frequency: 255 step, fomula: `step * (Chip clock / 8192)`; 1.95KHz to 498KHz if Chip clock is 16MHz. diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index fe7c3ea3..130841d9 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -109,6 +109,13 @@ enum DivDispatchCmds { DIV_CMD_QSOUND_ECHO_DELAY, DIV_CMD_QSOUND_ECHO_LEVEL, + DIV_CMD_X1_010_ENVELOPE_SHAPE, + DIV_CMD_X1_010_ENVELOPE_ENABLE, + DIV_CMD_X1_010_ENVELOPE_MODE, + DIV_CMD_X1_010_ENVELOPE_PERIOD, + DIV_CMD_X1_010_ENVELOPE_SLIDE, + DIV_CMD_X1_010_AUTO_ENVELOPE, + DIV_CMD_WS_SWEEP_TIME, DIV_CMD_WS_SWEEP_AMOUNT, diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 72791ccc..da37565b 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -41,9 +41,10 @@ #include "platform/pcspkr.h" #include "platform/segapcm.h" #include "platform/qsound.h" -#include "platform/lynx.h" -#include "platform/swan.h" #include "platform/vera.h" +#include "platform/x1_010.h" +#include "platform/swan.h" +#include "platform/lynx.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -257,6 +258,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SEGAPCM_COMPAT: dispatch=new DivPlatformSegaPCM; break; + case DIV_SYSTEM_X1_010: + dispatch=new DivPlatformX1_010; + break; case DIV_SYSTEM_SWAN: dispatch=new DivPlatformSwan; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 886c4b9d..a2fe24ed 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -19,7 +19,6 @@ #include "dataErrors.h" #include "song.h" -#include #define _USE_MATH_DEFINES #include "engine.h" #include "instrument.h" @@ -532,6 +531,36 @@ void DivEngine::renderSamples() { memPos+=length+16; } qsoundMemLen=memPos+256; + + // step 4: allocate x1-010 pcm samples + if (x1_010Mem==NULL) x1_010Mem=new unsigned char[1048576]; + memset(x1_010Mem,0,1048576); + + memPos=0; + for (int i=0; ilength8+4095)&(~0xfff); + // fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!) + if (paddedLen>131072) { + paddedLen=131072; + } + if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) { + memPos=(memPos+0x1ffff)&0xfe0000; + } + if (memPos>=1048576) { + logW("out of X1-010 memory for sample %d!\n",i); + break; + } + if (memPos+paddedLen>=1048576) { + memcpy(x1_010Mem+memPos,s->data8,1048576-memPos); + logW("out of X1-010 memory for sample %d!\n",i); + } else { + memcpy(x1_010Mem+memPos,s->data8,paddedLen); + } + s->offX1_010=memPos; + memPos+=paddedLen; + } + x1_010MemLen=memPos+256; } void DivEngine::createNew(const int* description) { @@ -950,12 +979,14 @@ int DivEngine::getEffectiveSampleRate(int rate) { return 1789773/(1789773/rate); case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: return (31250*MIN(255,(rate*255/31250)))/255; - case DIV_SYSTEM_QSOUND: + case DIV_SYSTEM_QSOUND: return (24038*MIN(65535,(rate*4096/24038)))/4096; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: return 18518; case DIV_SYSTEM_VERA: return (48828*MIN(128,(rate*128/48828)))/128; + case DIV_SYSTEM_X1_010: + return (31250*MIN(255,(rate*16/31250)))/16; // TODO: support variable clock case default: break; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 19d589db..d16a987c 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -632,6 +632,8 @@ class DivEngine { size_t qsoundAMemLen; unsigned char* dpcmMem; size_t dpcmMemLen; + unsigned char* x1_010Mem; + size_t x1_010MemLen; DivEngine(): output(NULL), diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index ec7cd1cb..3a79cebe 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -149,8 +149,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { // Neo Geo detune if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT - || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT - || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { + || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT + || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { ds.tuning=443.23; } @@ -259,8 +259,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ins->type=DIV_INS_C64; } if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT - || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT - || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { + || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT + || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { if (!ins->mode) { ins->type=DIV_INS_AY; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 1bb61f17..f1885225 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -52,6 +52,8 @@ enum DivInstrumentType { DIV_INS_SWAN=22, DIV_INS_MIKEY=23, DIV_INS_VERA=24, + DIV_INS_X1_010=25, + DIV_INS_MAX, }; // FM operator structure: diff --git a/src/engine/platform/sound/x1_010/x1_010.cpp b/src/engine/platform/sound/x1_010/x1_010.cpp new file mode 100644 index 00000000..150928e6 --- /dev/null +++ b/src/engine/platform/sound/x1_010/x1_010.cpp @@ -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; +} diff --git a/src/engine/platform/sound/x1_010/x1_010.hpp b/src/engine/platform/sound/x1_010/x1_010.hpp new file mode 100644 index 00000000..d5c429fd --- /dev/null +++ b/src/engine/platform/sound/x1_010/x1_010.hpp @@ -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 +#include + +#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 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(0x1000); + m_wave = std::make_unique(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 m_envelope = nullptr; + std::unique_ptr m_wave = nullptr; + + // output data + s32 m_out[2] = {0}; + + x1_010_mem_intf &m_intf; +}; + +#endif diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp new file mode 100644 index 00000000..42853855 --- /dev/null +++ b/src/engine/platform/x1_010.cpp @@ -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 + +//#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; htick(); + + 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 && samplesong.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].samplesong.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& 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() { +} diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h new file mode 100644 index 00000000..73cbaf6b --- /dev/null +++ b/src/engine/platform/x1_010.h @@ -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 +#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& 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 diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index a6bcf9ac..e7a6e45c 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -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); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 74bcb4f8..f96474a7 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -117,6 +117,16 @@ const char* cmdName[DIV_CMD_MAX]={ "QSOUND_ECHO_DELAY", "QSOUND_ECHO_LEVEL", + "X1_010_ENVELOPE_SHAPE", + "X1_010_ENVELOPE_ENABLE", + "X1_010_ENVELOPE_MODE", + "X1_010_ENVELOPE_PERIOD", + "X1_010_ENVELOPE_SLIDE", + "X1_010_AUTO_ENVELOPE", + + "WS_SWEEP_TIME", + "WS_SWEEP_AMOUNT", + "ALWAYS_SET_VOLUME" }; @@ -251,6 +261,21 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe break; } break; + case DIV_SYSTEM_X1_010: + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // select envelope shape + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SHAPE,ch,effectVal)); + break; + case 0x17: // PCM enable + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); + break; + default: + return false; + } + break; case DIV_SYSTEM_SWAN: switch (effect) { case 0x10: // select waveform @@ -567,6 +592,30 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char } return false; break; + case DIV_SYSTEM_X1_010: + switch (effect) { + case 0x20: // PCM frequency + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal)); + break; + case 0x22: // envelope mode + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_MODE,ch,effectVal)); + break; + case 0x23: // envelope period + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_PERIOD,ch,effectVal)); + break; + case 0x25: // envelope slide up + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,effectVal)); + break; + case 0x26: // envelope slide down + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,-effectVal)); + break; + case 0x29: // auto-envelope + dispatchCmd(DivCommand(DIV_CMD_X1_010_AUTO_ENVELOPE,ch,effectVal)); + break; + default: + return false; + } + break; default: return false; } diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index ec6313ff..571e3511 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -115,8 +115,9 @@ bool DivSample::initInternal(unsigned char d, int count) { case 8: // 8-bit if (data8!=NULL) delete[] data8; length8=count; - data8=new signed char[length8]; - memset(data8,0,length8); + // for padding X1-010 sample + data8=new signed char[(count+4095)&(~0xfff)]; + memset(data8,0,(count+4095)&(~0xfff)); break; case 9: // BRR if (dataBRR!=NULL) delete[] dataBRR; diff --git a/src/engine/sample.h b/src/engine/sample.h index 2915f2ce..8b6d16b1 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -49,7 +49,7 @@ struct DivSample { unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX; unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX; - unsigned int offSegaPCM, offQSound; + unsigned int offSegaPCM, offQSound, offX1_010; unsigned int samples; diff --git a/src/engine/song.h b/src/engine/song.h index fd1df2b9..2eb4fe5d 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -93,6 +93,7 @@ enum DivSystem { DIV_SYSTEM_VERA, DIV_SYSTEM_YM2610B_EXT, DIV_SYSTEM_SEGAPCM_COMPAT, + DIV_SYSTEM_X1_010 }; struct DivSong { @@ -242,6 +243,13 @@ struct DivSong { // - 2: YM2423 // - 3: VRC7 // - 4: custom (TODO) + // - X1-010: + // - bit 0-3: clock rate + // - 0: 16MHz (Seta 1) + // - 1: 16.67MHz (Seta 2) + // - bit 4: stereo + // - 0: mono + // - 1: stereo unsigned int systemFlags[32]; // song information diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 7f46f7fa..883c9a61 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -137,6 +137,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_SEGAPCM_COMPAT; case 0xac: return DIV_SYSTEM_VERA; + case 0xb0: + return DIV_SYSTEM_X1_010; case 0xde: return DIV_SYSTEM_YM2610B_EXT; case 0xe0: @@ -262,6 +264,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0xa9; case DIV_SYSTEM_VERA: return 0xac; + case DIV_SYSTEM_X1_010: + return 0xb0; case DIV_SYSTEM_YM2610B_EXT: return 0xde; case DIV_SYSTEM_QSOUND: @@ -339,7 +343,7 @@ int DivEngine::getChannelCount(DivSystem sys) { case DIV_SYSTEM_OPL3: return 18; case DIV_SYSTEM_MULTIPCM: - return 24; + return 28; case DIV_SYSTEM_PCSPKR: return 1; case DIV_SYSTEM_POKEY: @@ -355,6 +359,7 @@ int DivEngine::getChannelCount(DivSystem sys) { case DIV_SYSTEM_POKEMINI: return 1; case DIV_SYSTEM_SEGAPCM: + case DIV_SYSTEM_X1_010: return 16; case DIV_SYSTEM_VBOY: return 6; @@ -662,6 +667,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "Capcom QSound"; case DIV_SYSTEM_VERA: return "VERA"; + case DIV_SYSTEM_X1_010: + return "Seta/Allumer X1-010"; } return "Unknown"; } @@ -789,6 +796,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "Capcom DL-1425"; case DIV_SYSTEM_VERA: return "VERA"; + case DIV_SYSTEM_X1_010: + return "Seta/Allumer X1-010"; } return "Unknown"; } @@ -871,8 +880,8 @@ bool DivEngine::isSTDSystem(DivSystem sys) { sys!=DIV_SYSTEM_YM2151); } -const char* chanNames[38][24]={ - {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM/VERA +const char* chanNames[38][32]={ + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3) {"Square 1", "Square 2", "Square 3", "Noise"}, // SMS @@ -900,7 +909,7 @@ const char* chanNames[38][24]={ {"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, // OPN {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, // PC-98 {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "FM 10", "FM 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"}, // OPL3 - {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24"}, // MultiPCM + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"}, // MultiPCM {"Square"}, // PC Speaker/Pokémon Mini {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"}, // Virtual Boy/SCC {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B @@ -912,8 +921,8 @@ const char* chanNames[38][24]={ {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3) }; -const char* chanShortNames[38][24]={ - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759/VERA +const char* chanShortNames[38][32]={ + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759 {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3) {"S1", "S2", "S3", "NO"}, // SMS @@ -941,7 +950,7 @@ const char* chanShortNames[38][24]={ {"F1", "F2", "F3", "S1", "S2", "S3"}, // OPN {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"}, // PC-98 {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"}, // OPL3 - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24"}, // MultiPCM + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"}, // MultiPCM {"SQ"}, // PC Speaker/Pokémon Mini {"CH1", "CH2", "CH3", "CH4", "CH5", "NO"}, // Virtual Boy/SCC {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B @@ -953,7 +962,7 @@ const char* chanShortNames[38][24]={ {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3) }; -const int chanTypes[39][24]={ +const int chanTypes[40][32]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759 {0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3) @@ -982,7 +991,7 @@ const int chanTypes[39][24]={ {0, 0, 0, 1, 1, 1}, // OPN {0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 4}, // PC-98 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound {1}, // PC Speaker/Pokémon Mini {3, 3, 3, 3, 3, 2}, // Virtual Boy/SCC {0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B @@ -993,9 +1002,10 @@ const int chanTypes[39][24]={ {3, 3, 3, 3}, //Lynx {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3) {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4}, // VERA + {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, // X1-010 }; -const DivInstrumentType chanPrefType[45][24]={ +const DivInstrumentType chanPrefType[46][28]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759 {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3) @@ -1024,7 +1034,7 @@ const DivInstrumentType chanPrefType[45][24]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, // OPN {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // PC-98 {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, // OPL/OPL2/OPL3 - {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM/QSound + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM/QSound {DIV_INS_STD}, // PC Speaker/Pokémon Mini {DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY}, // Virtual Boy {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B @@ -1041,6 +1051,7 @@ const DivInstrumentType chanPrefType[45][24]={ {DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B (extended channel 3) {DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_AMIGA}, // VERA + {DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010}, // X1-010 }; const char* DivEngine::getChannelName(int chan) { @@ -1145,6 +1156,7 @@ const char* DivEngine::getChannelName(int chan) { case DIV_SYSTEM_MULTIPCM: case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: + case DIV_SYSTEM_X1_010: return chanNames[28][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_PCSPKR: @@ -1287,6 +1299,7 @@ const char* DivEngine::getChannelShortName(int chan) { case DIV_SYSTEM_MULTIPCM: case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: + case DIV_SYSTEM_X1_010: return chanShortNames[28][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_PCSPKR: @@ -1463,6 +1476,9 @@ int DivEngine::getChannelType(int chan) { case DIV_SYSTEM_VERA: return chanTypes[38][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_X1_010: + return chanTypes[39][dispatchChanOfChan[chan]]; + break; } return 1; } @@ -1616,6 +1632,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { case DIV_SYSTEM_VERA: return chanPrefType[44][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_X1_010: + return chanPrefType[45][dispatchChanOfChan[chan]]; + break; } return DIV_INS_FM; } @@ -1644,6 +1663,7 @@ bool DivEngine::isVGMExportable(DivSystem which) { case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: + case DIV_SYSTEM_X1_010: case DIV_SYSTEM_SWAN: return true; default: diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index c3388399..069d5a2e 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -154,6 +154,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(3); } break; + case DIV_SYSTEM_X1_010: + for (int i=0; i<16; i++) { + w->writeC(0xc8); + w->writeS(baseAddr2S+(i<<3)); + w->writeC(0); + } + break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: @@ -395,6 +402,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeS(baseAddr2S|(write.addr&0xffff)); w->writeC(write.val); break; + case DIV_SYSTEM_X1_010: + w->writeC(0xc8); + w->writeS(baseAddr2S|(write.addr&0x1fff)); + w->writeC(write.val); + break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: @@ -497,6 +509,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { int hasOPM=0; int hasSegaPCM=0; int segaPCMOffset=0xf8000d; + int hasX1010=0; int hasRFC=0; int hasOPN=0; int hasOPNA=0; @@ -568,6 +581,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { bool writePCESamples=false; bool writeADPCM=false; bool writeSegaPCM=false; + bool writeX1010=false; bool writeQSound=false; for (int i=0; ichipClock; + willExport[i]=true; + writeX1010=true; + } else if (!(hasX1010&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasX1010|=0x40000000; + howManyChips++; + } + break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: @@ -669,7 +695,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { } if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag hasOPNB|=0x80000000; - } + } break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: @@ -995,6 +1021,16 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { w->write(qsoundMem,blockSize); } + if (writeX1010 && x1_010MemLen>0) { + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0x91); + w->writeI(x1_010MemLen+8); + w->writeI(x1_010MemLen); + w->writeI(0); + w->write(x1_010Mem,x1_010MemLen); + } + // initialize streams int streamID=0; for (int i=0; iinPorta?colorOn:colorOff,">> InPorta"); break; } + case DIV_SYSTEM_X1_010: { + DivPlatformX1_010::Channel* ch=(DivPlatformX1_010::Channel*)data; + ImGui::Text("> X1-010"); + ImGui::Text("* freq: %.4x",ch->freq); + ImGui::Text(" - base: %d",ch->baseFreq); + ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text("- note: %d",ch->note); + ImGui::Text("- wave: %d",ch->wave); + ImGui::Text("- sample: %d",ch->sample); + ImGui::Text("- ins: %d",ch->ins); + ImGui::Text("- pan: %d",ch->pan); + ImGui::Text("* envelope:"); + ImGui::Text(" - shape: %d",ch->env.shape); + ImGui::Text(" - period: %.2x",ch->env.period); + ImGui::Text(" - slide: %.2x",ch->env.slide); + ImGui::Text(" - slidefrac: %.2x",ch->env.slidefrac); + ImGui::Text(" - autoEnvNum: %.2x",ch->autoEnvNum); + ImGui::Text(" - autoEnvDen: %.2x",ch->autoEnvDen); + ImGui::Text("- WaveBank: %d",ch->waveBank); + ImGui::Text("- vol: %.2x",ch->vol); + ImGui::Text("- outVol: %.2x",ch->outVol); + ImGui::Text("- Lvol: %.2x",ch->lvol); + ImGui::Text("- Rvol: %.2x",ch->rvol); + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); + ImGui::TextColored(ch->envChanged?colorOn:colorOff,">> EnvChanged"); + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + ImGui::TextColored(ch->furnacePCM?colorOn:colorOff,">> FurnacePCM"); + ImGui::TextColored(ch->pcm?colorOn:colorOff,">> PCM"); + ImGui::TextColored(ch->env.flag.envEnable?colorOn:colorOff,">> EnvEnable"); + ImGui::TextColored(ch->env.flag.envOneshot?colorOn:colorOff,">> EnvOneshot"); + ImGui::TextColored(ch->env.flag.envSplit?colorOn:colorOff,">> EnvSplit"); + ImGui::TextColored(ch->env.flag.envHinvR?colorOn:colorOff,">> EnvHinvR"); + ImGui::TextColored(ch->env.flag.envVinvR?colorOn:colorOff,">> EnvVinvR"); + ImGui::TextColored(ch->env.flag.envHinvL?colorOn:colorOff,">> EnvHinvL"); + ImGui::TextColored(ch->env.flag.envVinvL?colorOn:colorOff,">> EnvVinvL"); + break; + } default: ImGui::Text("Unknown system! Help!"); break; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 41d08502..2e979ccf 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1194,6 +1194,10 @@ void FurnaceGUI::drawInsList() { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]); name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d\n",i,ins->name,i); break; + case DIV_INS_X1_010: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + break; default: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d\n",i,ins->name,i); @@ -1350,7 +1354,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("notes:"); if (sample->loopStart>=0) { considerations=true; - ImGui::Text("- sample won't loop on Neo Geo ADPCM-A"); + ImGui::Text("- sample won't loop on Neo Geo ADPCM-A and X1-010"); if (sample->loopStart&1) { ImGui::Text("- sample loop start will be aligned to the nearest even sample on Amiga"); } @@ -1366,10 +1370,18 @@ void FurnaceGUI::drawSampleEdit() { considerations=true; ImGui::Text("- sample length will be aligned and padded to 512 sample units on Neo Geo ADPCM."); } + if (sample->samples&4095) { + considerations=true; + ImGui::Text("- sample length will be aligned and padded to 4096 sample units on X1-010."); + } if (sample->samples>65535) { considerations=true; ImGui::Text("- maximum sample length on Sega PCM and QSound is 65536 samples"); } + if (sample->samples>131071) { + considerations=true; + ImGui::Text("- maximum sample length on X1-010 is 131072 samples"); + } if (sample->samples>2097151) { considerations=true; ImGui::Text("- maximum sample length on Neo Geo ADPCM is 2097152 samples"); @@ -2031,6 +2043,7 @@ void FurnaceGUI::drawStats() { String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024); String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024); String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024); + String x1_010Usage=fmt::sprintf("%d/1024KB",e->x1_010MemLen/1024); ImGui::Text("ADPCM-A"); ImGui::SameLine(); ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str()); @@ -2040,6 +2053,9 @@ void FurnaceGUI::drawStats() { ImGui::Text("QSound"); ImGui::SameLine(); ImGui::ProgressBar(((float)e->qsoundMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),qsoundUsage.c_str()); + ImGui::Text("X1-010"); + ImGui::SameLine(); + ImGui::ProgressBar(((float)e->x1_010MemLen)/1048576.0f,ImVec2(-FLT_MIN,0),x1_010Usage.c_str()); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS; ImGui::End(); @@ -4742,6 +4758,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_AY8930); sysAddOption(DIV_SYSTEM_LYNX); sysAddOption(DIV_SYSTEM_QSOUND); + sysAddOption(DIV_SYSTEM_X1_010); sysAddOption(DIV_SYSTEM_SWAN); sysAddOption(DIV_SYSTEM_VERA); ImGui::EndMenu(); @@ -5036,6 +5053,23 @@ bool FurnaceGUI::loop() { } rightClickable break; } + case DIV_SYSTEM_X1_010: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("16MHz (Seta 1)",(flags&15)==0)) { + e->setSysFlags(i,(flags&(~16))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("16.67MHz (Seta 2)",(flags&15)==1)) { + e->setSysFlags(i,(flags&(~16))|1,restart); + updateWindowTitle(); + } + bool x1_010Stereo=flags&16; + if (ImGui::Checkbox("Stereo",&x1_010Stereo)) { + e->setSysFlags(i,(flags&(~15))|(x1_010Stereo<<4),restart); + updateWindowTitle(); + } + break; + } case DIV_SYSTEM_GB: case DIV_SYSTEM_SWAN: case DIV_SYSTEM_VERA: @@ -5097,6 +5131,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_AY8930); sysChangeOption(i,DIV_SYSTEM_LYNX); sysChangeOption(i,DIV_SYSTEM_QSOUND); + sysChangeOption(i,DIV_SYSTEM_X1_010); sysChangeOption(i,DIV_SYSTEM_SWAN); sysChangeOption(i,DIV_SYSTEM_VERA); ImGui::EndMenu(); @@ -5673,7 +5708,8 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_INSTR_BEEPER,ImVec4(0.0f,1.0f,0.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_SWAN,ImVec4(0.3f,0.5f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_MIKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VERA,ImVec4(0.4f,0.6f,1.0f,1.0f)) + GET_UI_COLOR(GUI_COLOR_INSTR_VERA,ImVec4(0.4f,0.6f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_X1_010,ImVec4(0.3f,0.5f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN,ImVec4(0.3f,0.3f,0.3f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_FM,ImVec4(0.2f,0.8f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_PULSE,ImVec4(0.4f,1.0f,0.2f,1.0f)); @@ -6408,6 +6444,12 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta/Allumer X1-010", { + DIV_SYSTEM_X1_010, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Game consoles"); @@ -6705,6 +6747,18 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 1", { + DIV_SYSTEM_X1_010, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 2", { + DIV_SYSTEM_X1_010, 64, 0, 1, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("DefleMask-compatible"); diff --git a/src/gui/gui.h b/src/gui/gui.h index 07048cbd..c2f7d58d 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -74,6 +74,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_SWAN, GUI_COLOR_INSTR_MIKEY, GUI_COLOR_INSTR_VERA, + GUI_COLOR_INSTR_X1_010, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_FM, GUI_COLOR_CHANNEL_PULSE, diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 42649ab0..c8eec676 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -27,7 +27,7 @@ #include #include "plot_nolerp.h" -const char* insTypes[25]={ +const char* insTypes[DIV_INS_MAX]={ "Standard", "FM (4-operator)", "Game Boy", @@ -52,7 +52,8 @@ const char* insTypes[25]={ "PC Beeper", "WonderSwan", "Atari Lynx", - "VERA" + "VERA", + "X1-010" }; const char* ssgEnvTypes[8]={ @@ -157,6 +158,10 @@ const char* mikeyFeedbackBits[11] = { "0", "1", "2", "3", "4", "5", "7", "10", "11", "int", NULL }; +const char* x1_010EnvBits[8]={ + "enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL +}; + const char* oneBit[2]={ "on", NULL }; @@ -792,9 +797,9 @@ void FurnaceGUI::drawInsEdit() { } else { DivInstrument* ins=e->song.ins[curIns]; ImGui::InputText("Name",&ins->name); - if (ins->type<0 || ins->type>24) ins->type=DIV_INS_FM; + if (ins->type<0 || ins->type>=DIV_INS_MAX) ins->type=DIV_INS_FM; int insType=ins->type; - if (ImGui::Combo("Type",&insType,insTypes,25,24)) { + if (ImGui::Combo("Type",&insType,insTypes,DIV_INS_MAX,DIV_INS_MAX)) { ins->type=(DivInstrumentType)insType; } @@ -1449,11 +1454,18 @@ void FurnaceGUI::drawInsEdit() { int ex1Max=(ins->type==DIV_INS_AY8930)?8:0; int ex2Max=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?4:0; + bool ex2Bit=true; if (ins->type==DIV_INS_C64) { ex1Max=4; ex2Max=15; } + if (ins->type==DIV_INS_X1_010) { + dutyMax=0; + ex1Max=7; + ex2Max=63; + ex2Bit=false; + } if (ins->type==DIV_INS_SAA1099) ex1Max=8; if (settings.macroView==0) { // modern view @@ -1478,6 +1490,8 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Filter Mode",64,ins->std.ex1MacroOpen,true,filtModeBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else if (ins->type==DIV_INS_SAA1099) { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope",160,ins->std.ex1MacroOpen,true,saaEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); + } else if (ins->type==DIV_INS_X1_010) { + NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope Mode",160,ins->std.ex1MacroOpen,true,x1_010EnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Duty",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } @@ -1486,13 +1500,13 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_C64) { NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Resonance",64,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } else { - NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",64,ins->std.ex2MacroOpen,true,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); + NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",ex2Bit?64:160,ins->std.ex2MacroOpen,ex2Bit,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } } if (ins->type==DIV_INS_C64) { NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,2,"ex3","Special",32,ins->std.ex3MacroOpen,true,c64SpecialBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false); } - if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930) { + if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_X1_010) { NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,15,"ex3","AutoEnv Num",96,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,15,NULL,false); NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,15,"alg","AutoEnv Den",96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,15,NULL,false); } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index cd360898..f3ef2f0e 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -493,6 +493,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_SWAN,"WonderSwan"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_MIKEY,"Lynx"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_VERA,"VERA"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_X1_010,"X1-010"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown"); ImGui::TreePop(); } @@ -1161,6 +1162,7 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_INSTR_SWAN); PUT_UI_COLOR(GUI_COLOR_INSTR_MIKEY); PUT_UI_COLOR(GUI_COLOR_INSTR_VERA); + PUT_UI_COLOR(GUI_COLOR_INSTR_X1_010); PUT_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN); PUT_UI_COLOR(GUI_COLOR_CHANNEL_FM); PUT_UI_COLOR(GUI_COLOR_CHANNEL_PULSE);