From 4a83c7c5a71f125b9c5cd5d450e5a2a822351f22 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 02:31:03 +0900 Subject: [PATCH 01/22] Add Seta/Allumer X1-010 Support its 16 channel wavetable/PCM chip, with (optional) stereo support. Its also has envelope, this feature has similar as AY PSG's one but its shape is also stored at RAM, and each nibble in envelope data is for each output: so i decided to added some feature for more stereo-ish envelope. Split: Envelope shape will be splitted to Left and Right half for each output. HInv, Vinv: Envelope shape will be Horizontally/Vertically mirrored the left one. Max sample length is sample bank size of Seta 2 arcade hardware (currently not emulated yet, nor it doesn't support on VGM). Chip id is temporary, it can be changed with to suggestions. --- .gitmodules | 3 + CMakeLists.txt | 6 +- extern/cam900_vgsound_emu | 1 + papers/doc/4-instrument/README.md | 3 +- papers/doc/4-instrument/x1_010.md | 11 + papers/doc/6-sample/README.md | 3 +- papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/x1_010.md | 34 ++ papers/format.md | 1 + src/engine/dispatch.h | 7 + src/engine/dispatchContainer.cpp | 6 +- src/engine/engine.cpp | 34 +- src/engine/engine.h | 2 + src/engine/instrument.h | 1 + src/engine/platform/x1_010.cpp | 862 ++++++++++++++++++++++++++++++ src/engine/platform/x1_010.h | 138 +++++ src/engine/playback.cpp | 36 ++ src/engine/sample.cpp | 5 +- src/engine/sample.h | 2 +- src/engine/song.h | 10 +- src/engine/sysDef.cpp | 38 +- src/engine/vgmOps.cpp | 36 ++ src/gui/debug.cpp | 40 ++ src/gui/gui.cpp | 56 +- src/gui/gui.h | 1 + src/gui/insEdit.cpp | 26 +- src/gui/settings.cpp | 2 + 27 files changed, 1340 insertions(+), 25 deletions(-) create mode 160000 extern/cam900_vgsound_emu create mode 100644 papers/doc/4-instrument/x1_010.md create mode 100644 papers/doc/7-systems/x1_010.md create mode 100644 src/engine/platform/x1_010.cpp create mode 100644 src/engine/platform/x1_010.h diff --git a/.gitmodules b/.gitmodules index 435f43c75..56f11c097 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "extern/adpcm"] path = extern/adpcm url = https://github.com/superctr/adpcm +[submodule "extern/cam900_vgsound_emu"] + path = extern/cam900_vgsound_emu + url = https://github.com/cam900/vgsound_emu diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bcb8ce98..53aebac00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,9 @@ extern/adpcm/ymz_codec.c extern/Nuked-OPN2/ym3438.c extern/opm/opm.c extern/Nuked-OPLL/opll.c + +extern/cam900_vgsound_emu/x1_010/x1_010.cpp + src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/saa1099.cpp @@ -304,8 +307,9 @@ src/engine/platform/amiga.cpp src/engine/platform/pcspkr.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp -src/engine/platform/dummy.cpp +src/engine/platform/x1_010.cpp src/engine/platform/lynx.cpp +src/engine/platform/dummy.cpp ) if (WIN32) diff --git a/extern/cam900_vgsound_emu b/extern/cam900_vgsound_emu new file mode 160000 index 000000000..cf8816851 --- /dev/null +++ b/extern/cam900_vgsound_emu @@ -0,0 +1 @@ +Subproject commit cf88168512c1b32bbea7b7dcc1411c4da429a723 diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index b9d4b8f40..f6da9d26c 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -21,8 +21,9 @@ depending on the instrument type, there are currently 10 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. +- [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. +- [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 000000000..8f3691ee6 --- /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/6-sample/README.md b/papers/doc/6-sample/README.md index 78eb322c3..87ddf0f8f 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 78aeb8cea..1a0d28096 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -18,5 +18,6 @@ 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) 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 000000000..952e42316 --- /dev/null +++ b/papers/doc/7-systems/x1_010.md @@ -0,0 +1,34 @@ +# Seta/Allumer X1-010 + +One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-88 to early-2000s. +it has 2 output channel, 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 it's 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 - its simulates early 'incorrect' emulation on some mono hardware but it is also based on the assumption that each channel is connected to each output. + +# effects + +- `10xx`: change wave. +- `11xx`: change envelope shape. (also wavetable) +- `17xx`: toggle PCM mode. +- `20xx`: set PCM frequency.* +- `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 sets whether the right shape will mirror the left one. + - bit 4 sets whether the right output will mirror the left 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/papers/format.md b/papers/format.md index 4df49ed02..ff068d830 100644 --- a/papers/format.md +++ b/papers/format.md @@ -174,6 +174,7 @@ size | description | - 0xaa: MSM6295 - 4 channels | - 0xab: MSM6258 - 1 channel | - 0xac: Commander X16 (VERA) - 17 channels + | - 0xb0: Seta/Allumer X1-010 - 16 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - (compound!) means that the system is composed of two or more chips, diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 300b0033b..7e1a56081 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -103,6 +103,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_ALWAYS_SET_VOLUME, DIV_CMD_MAX diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 0682bbd71..84b8315f3 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -40,8 +40,9 @@ #include "platform/pcspkr.h" #include "platform/segapcm.h" #include "platform/qsound.h" -#include "platform/dummy.h" +#include "platform/x1_010.h" #include "platform/lynx.h" +#include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -230,6 +231,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; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a8b023d7d..e484baf31 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -532,6 +532,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,7 +980,9 @@ int DivEngine::getEffectiveSampleRate(int rate) { 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; + return 18518; // TODO: support ADPCM-B case + 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 19d589dba..d16a987cb 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/instrument.h b/src/engine/instrument.h index 27ce2a4ce..da7356f1e 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -48,6 +48,7 @@ enum DivInstrumentType { DIV_INS_BEEPER=21, DIV_INS_SWAN=22, DIV_INS_MIKEY=23, + DIV_INS_X1_010=24, }; // FM operator structure: diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp new file mode 100644 index 000000000..6d072d730 --- /dev/null +++ b/src/engine/platform/x1_010.cpp @@ -0,0 +1,862 @@ +/** + * 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"; + break; + case 0x22: + return "22xx: Set envelope mode (bit 0: enable, bit 1: one-shot, bit 2: split shape to L/R, bit 3: H.invert right, bit 4: V.invert right)"; + 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 short tempL=x1_010->output(0); + signed short 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 (stereo&&(chan[ch].env.flag.envHinv||chan[ch].env.flag.envSplit||chan[ch].env.flag.envVinv)) { // Stereo config + int la = i, ra = i; + int lo, ro; + if (chan[ch].env.flag.envHinv) { ra = 127-i; } // horizontal invert right 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.envVinv) { ro = 15-ro; } // vertical invert right 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); + } + } + if (stereo) { + 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 nextHinv=(chan[i].std.ex1&8); + if (nextHinv!=(chan[i].env.flag.envHinv)) { + chan[i].env.flag.envHinv=nextHinv; + if (!isMuted[i] && !chan[i].pcm) { + chan[i].envChanged=true; + } + } + bool nextVinv=(chan[i].std.ex1&16); + if (nextVinv!=(chan[i].env.flag.envVinv)) { + chan[i].env.flag.envVinv=nextVinv; + 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]) { + if (stereo) { + 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; + } else { + chan[i].lvol=chan[i].rvol=chan[i].outVol; + } + } + 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 (stereo&&(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=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); + } + } + if (stereo) { + 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 nextHinv=c.value&8; + if (nextHinv!=(chan[c.chan].env.flag.envHinv)) { + chan[c.chan].env.flag.envHinv=nextHinv; + if (!isMuted[c.chan] && !chan[c.chan].pcm) { + chan[c.chan].envChanged=true; + } + } + bool nextVinv=c.value&16; + if (nextVinv!=(chan[c.chan].env.flag.envVinv)) { + chan[c.chan].env.flag.envVinv=nextVinv; + 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 000000000..27bf4f4a6 --- /dev/null +++ b/src/engine/platform/x1_010.h @@ -0,0 +1,138 @@ +/** + * 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 "../../../extern/cam900_vgsound_emu/x1_010/x1_010.hpp" + +class DivX1_010Interface: public x1_010_intf { + public: + DivEngine* parent; + int sampleBank; + virtual u8 read_rom(uint32_t 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 envHinv : 1; + unsigned char envVinv : 1; + void reset() { + envEnable=0; + envOneshot=0; + envSplit=0; + envHinv=0; + envVinv=0; + } + EnvFlag(): + envEnable(0), + envOneshot(0), + envSplit(0), + envHinv(0), + envVinv(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/playback.cpp b/src/engine/playback.cpp index f0f43dc1a..2fba35336 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -248,6 +248,18 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } 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; } break; default: @@ -533,6 +545,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 ec6313fff..b802bf791 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 2915f2cef..8b6d16b19 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 d1e255d95..34d77386a 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -91,7 +91,8 @@ enum DivSystem { DIV_SYSTEM_LYNX, DIV_SYSTEM_QSOUND, DIV_SYSTEM_YM2610B_EXT, - DIV_SYSTEM_SEGAPCM_COMPAT + DIV_SYSTEM_SEGAPCM_COMPAT, + DIV_SYSTEM_X1_010 }; struct DivSong { @@ -239,6 +240,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 3c988ceb8..48de06c7f 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -135,6 +135,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_LYNX; case 0xa9: return DIV_SYSTEM_SEGAPCM_COMPAT; + case 0xb0: + return DIV_SYSTEM_X1_010; case 0xde: return DIV_SYSTEM_YM2610B_EXT; case 0xe0: @@ -258,6 +260,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0xa8; case DIV_SYSTEM_SEGAPCM_COMPAT: return 0xa9; + case DIV_SYSTEM_X1_010: + return 0xb0; case DIV_SYSTEM_YM2610B_EXT: return 0xde; case DIV_SYSTEM_QSOUND: @@ -335,7 +339,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: @@ -351,6 +355,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; @@ -650,6 +655,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "Taito Arcade Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom QSound"; + case DIV_SYSTEM_X1_010: + return "Seta/Allumer X1-010"; } return "Unknown"; } @@ -775,6 +782,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "Yamaha YM2610B Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom DL-1425"; + case DIV_SYSTEM_X1_010: + return "Seta/Allumer X1-010"; } return "Unknown"; } @@ -857,7 +866,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) { sys!=DIV_SYSTEM_YM2151); } -const char* chanNames[38][24]={ +const char* chanNames[38][28]={ {"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) @@ -886,7 +895,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 @@ -898,7 +907,7 @@ 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]={ +const char* chanShortNames[38][28]={ {"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) @@ -927,7 +936,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 @@ -939,7 +948,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[38][24]={ +const int chanTypes[39][28]={ {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) @@ -968,7 +977,7 @@ const int chanTypes[38][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 @@ -978,9 +987,10 @@ const int chanTypes[38][24]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums {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) + {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, // X1-010 }; -const DivInstrumentType chanPrefType[44][24]={ +const DivInstrumentType chanPrefType[45][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) @@ -1009,7 +1019,7 @@ const DivInstrumentType chanPrefType[44][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 @@ -1025,6 +1035,7 @@ const DivInstrumentType chanPrefType[44][24]={ {DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // Z {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_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) { @@ -1129,6 +1140,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: @@ -1268,6 +1280,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: @@ -1438,6 +1451,9 @@ int DivEngine::getChannelType(int chan) { case DIV_SYSTEM_LYNX: return chanTypes[36][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_X1_010: + return chanTypes[38][dispatchChanOfChan[chan]]; + break; } return 1; } @@ -1588,6 +1604,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { case DIV_SYSTEM_LYNX: return chanPrefType[42][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_X1_010: + return chanPrefType[44][dispatchChanOfChan[chan]]; + break; } return DIV_INS_FM; } @@ -1616,6 +1635,7 @@ bool DivEngine::isVGMExportable(DivSystem which) { case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: + case DIV_SYSTEM_X1_010: return true; default: return false; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 97f427372..3c63f65c9 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -150,6 +150,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((isSecond?0x8000:0x0)+(i<<3)); + w->writeC(0); + } + break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: @@ -391,6 +398,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeS((isSecond?0x8000:0)|(write.addr&0xffff)); w->writeC(write.val); break; + case DIV_SYSTEM_X1_010: + w->writeC(0xc8); + w->writeS((isSecond?0x8000:0)|(write.addr&0x1fff)); + w->writeC(write.val); + break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: @@ -481,6 +493,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; @@ -552,6 +565,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: @@ -964,6 +990,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.envHinv?colorOn:colorOff,">> EnvHinv"); + ImGui::TextColored(ch->env.flag.envVinv?colorOn:colorOff,">> EnvVinv"); + break; + } default: ImGui::Text("Unknown system! Help!"); break; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ed9900e15..cb0c6dc4a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1191,6 +1191,10 @@ void FurnaceGUI::drawInsList() { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); name=fmt::sprintf(ICON_FA_BAR_CHART " %.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); @@ -1347,7 +1351,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"); } @@ -1363,10 +1367,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"); @@ -2027,6 +2039,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()); @@ -2036,6 +2049,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(); @@ -4635,6 +4651,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_AY8930); sysAddOption(DIV_SYSTEM_LYNX); sysAddOption(DIV_SYSTEM_QSOUND); + sysAddOption(DIV_SYSTEM_X1_010); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -4921,6 +4938,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_YM2610: case DIV_SYSTEM_YM2610_EXT: @@ -4974,6 +5008,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); ImGui::EndMenu(); } } @@ -5557,6 +5592,7 @@ 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_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)); @@ -6286,6 +6322,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"); @@ -6570,6 +6612,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 7d7f6b4a0..7f200e381 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -73,6 +73,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_BEEPER, GUI_COLOR_INSTR_SWAN, GUI_COLOR_INSTR_MIKEY, + 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 4afa67a5c..f8306c200 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -27,7 +27,7 @@ #include #include "plot_nolerp.h" -const char* insTypes[24]={ +const char* insTypes[25]={ "Standard", "FM (4-operator)", "Game Boy", @@ -51,7 +51,8 @@ const char* insTypes[24]={ "POKEY", "PC Beeper", "WonderSwan", - "Atari Lynx" + "Atari Lynx", + "X1-010" }; const char* ssgEnvTypes[8]={ @@ -148,6 +149,10 @@ const char* mikeyFeedbackBits[11] = { "0", "1", "2", "3", "4", "5", "7", "10", "11", "int", NULL }; +const char* x1_010EnvBits[6]={ + "enable", "oneshot", "split L/R", "Hinv", "Vinv", NULL +}; + const char* oneBit[2]={ "on", NULL }; @@ -783,9 +788,9 @@ void FurnaceGUI::drawInsEdit() { } else { DivInstrument* ins=e->song.ins[curIns]; ImGui::InputText("Name",&ins->name); - if (ins->type<0 || ins->type>23) ins->type=DIV_INS_FM; + if (ins->type<0 || ins->type>24) ins->type=DIV_INS_FM; int insType=ins->type; - if (ImGui::Combo("Type",&insType,insTypes,24,24)) { + if (ImGui::Combo("Type",&insType,insTypes,25,25)) { ins->type=(DivInstrumentType)insType; } @@ -1380,11 +1385,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=5; + ex2Max=63; + ex2Bit=false; + } if (ins->type==DIV_INS_SAA1099) ex1Max=8; if (settings.macroView==0) { // modern view @@ -1409,6 +1421,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); } @@ -1417,13 +1431,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 3f0e532db..f34f67ac5 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -492,6 +492,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_BEEPER,"PC Beeper"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_SWAN,"WonderSwan"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_MIKEY,"Lynx"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_X1_010,"X1-010"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown"); ImGui::TreePop(); } @@ -1159,6 +1160,7 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_INSTR_BEEPER); PUT_UI_COLOR(GUI_COLOR_INSTR_SWAN); PUT_UI_COLOR(GUI_COLOR_INSTR_MIKEY); + 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); From d0c32a56be05446915c1c24a3e635c7ebd67ddde Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 03:06:01 +0900 Subject: [PATCH 02/22] Fix panning --- src/engine/platform/x1_010.cpp | 88 ++++++++++++++++------------------ src/engine/playback.cpp | 2 + 2 files changed, 42 insertions(+), 48 deletions(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 6d072d730..7119a67f2 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -306,7 +306,7 @@ void DivPlatformX1_010::updateEnvelope(int ch) { for (int i=0; i<128; i++) { if (wt->max<1 || wt->len<1) { envFill(ch,i); - } else if (stereo&&(chan[ch].env.flag.envHinv||chan[ch].env.flag.envSplit||chan[ch].env.flag.envVinv)) { // Stereo config + } else if (chan[ch].env.flag.envHinv||chan[ch].env.flag.envSplit||chan[ch].env.flag.envVinv) { // Stereo config int la = i, ra = i; int lo, ro; if (chan[ch].env.flag.envHinv) { ra = 127-i; } // horizontal invert right envelope @@ -386,29 +386,27 @@ void DivPlatformX1_010::tick() { refreshControl(i); } } - if (stereo) { - 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 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 nextHinv=(chan[i].std.ex1&8); - if (nextHinv!=(chan[i].env.flag.envHinv)) { - chan[i].env.flag.envHinv=nextHinv; - if (!isMuted[i] && !chan[i].pcm) { - chan[i].envChanged=true; - } + } + bool nextHinv=(chan[i].std.ex1&8); + if (nextHinv!=(chan[i].env.flag.envHinv)) { + chan[i].env.flag.envHinv=nextHinv; + if (!isMuted[i] && !chan[i].pcm) { + chan[i].envChanged=true; } - bool nextVinv=(chan[i].std.ex1&16); - if (nextVinv!=(chan[i].env.flag.envVinv)) { - chan[i].env.flag.envVinv=nextVinv; - if (!isMuted[i] && !chan[i].pcm) { - chan[i].envChanged=true; - } + } + bool nextVinv=(chan[i].std.ex1&16); + if (nextVinv!=(chan[i].env.flag.envVinv)) { + chan[i].env.flag.envVinv=nextVinv; + if (!isMuted[i] && !chan[i].pcm) { + chan[i].envChanged=true; } - } + } } if (chan[i].std.hadEx2) { if (chan[i].env.shape!=chan[i].std.ex2) { @@ -437,12 +435,8 @@ void DivPlatformX1_010::tick() { } if (chan[i].envChanged) { if (!isMuted[i]) { - if (stereo) { - 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; - } else { - chan[i].lvol=chan[i].rvol=chan[i].outVol; - } + 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; @@ -654,7 +648,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } break; case DIV_CMD_PANNING: { - if (stereo&&(chan[c.chan].pan!=c.value)) { + if (chan[c.chan].pan!=c.value) { chan[c.chan].pan=c.value; if (!isMuted[c.chan]) { chan[c.chan].envChanged=true; @@ -700,29 +694,27 @@ int DivPlatformX1_010::dispatch(DivCommand c) { refreshControl(c.chan); } } - if (stereo) { - 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 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 nextHinv=c.value&8; - if (nextHinv!=(chan[c.chan].env.flag.envHinv)) { - chan[c.chan].env.flag.envHinv=nextHinv; - if (!isMuted[c.chan] && !chan[c.chan].pcm) { - chan[c.chan].envChanged=true; - } + } + bool nextHinv=c.value&8; + if (nextHinv!=(chan[c.chan].env.flag.envHinv)) { + chan[c.chan].env.flag.envHinv=nextHinv; + if (!isMuted[c.chan] && !chan[c.chan].pcm) { + chan[c.chan].envChanged=true; } - bool nextVinv=c.value&16; - if (nextVinv!=(chan[c.chan].env.flag.envVinv)) { - chan[c.chan].env.flag.envVinv=nextVinv; - if (!isMuted[c.chan] && !chan[c.chan].pcm) { - chan[c.chan].envChanged=true; - } + } + bool nextVinv=c.value&16; + if (nextVinv!=(chan[c.chan].env.flag.envVinv)) { + chan[c.chan].env.flag.envVinv=nextVinv; + if (!isMuted[c.chan] && !chan[c.chan].pcm) { + chan[c.chan].envChanged=true; } - } + } break; } case DIV_CMD_X1_010_ENVELOPE_PERIOD: diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 2fba35336..9e4542211 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -260,6 +260,8 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe case 0x17: // PCM enable dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); break; + default: + return false; } break; default: From 666b061c8b0d3bc28be233c9ded6fb95181398c7 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 03:08:47 +0900 Subject: [PATCH 03/22] Fix year info --- papers/doc/7-systems/x1_010.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index 952e42316..83587a2b8 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -1,6 +1,6 @@ # Seta/Allumer X1-010 -One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-88 to early-2000s. +One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-80s to early-2000s. it has 2 output channel, 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. From 789be838e34e2ce762f9f603f9a2ec058d08c0fe Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 03:43:44 +0900 Subject: [PATCH 04/22] Submodule update --- extern/cam900_vgsound_emu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/cam900_vgsound_emu b/extern/cam900_vgsound_emu index cf8816851..cf4ba11ad 160000 --- a/extern/cam900_vgsound_emu +++ b/extern/cam900_vgsound_emu @@ -1 +1 @@ -Subproject commit cf88168512c1b32bbea7b7dcc1411c4da429a723 +Subproject commit cf4ba11ad786f13c374afb77616d509cb4c751bf From 8da59211961b85dc0af8b677d5c778adc7902ecb Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 03:50:15 +0900 Subject: [PATCH 05/22] step 2 --- src/engine/platform/x1_010.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 7119a67f2..74bfdb13a 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -241,8 +241,8 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l for (size_t h=start; htick(); - signed short tempL=x1_010->output(0); - signed short tempR=x1_010->output(1); + signed int tempL=x1_010->output(0); + signed int tempR=x1_010->output(1); if (tempL<-32768) tempL=-32768; if (tempL>32767) tempL=32767; From 6c897722dbf439ed79acdc36812921317373df81 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 04:03:45 +0900 Subject: [PATCH 06/22] Compile fix Take 3 --- src/engine/playback.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 9e4542211..511e55e17 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -249,6 +249,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe } break; } + break; case DIV_SYSTEM_X1_010: switch (effect) { case 0x10: // select waveform From 458f8c5881bec3a6eca3a12ac352ed6989afa9b0 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 12:21:51 +0900 Subject: [PATCH 07/22] Fix instrument allocation --- src/engine/instrument.h | 4 +++- src/gui/insEdit.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index da7356f1e..abee4223a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -48,7 +48,9 @@ enum DivInstrumentType { DIV_INS_BEEPER=21, DIV_INS_SWAN=22, DIV_INS_MIKEY=23, - DIV_INS_X1_010=24, + DIV_INS_VERA=24, + DIV_INS_X1_010=25, + DIV_INS_MAX, }; // FM operator structure: diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 29085c561..35059804d 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -788,9 +788,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,25)) { + if (ImGui::Combo("Type",&insType,insTypes,DIV_INS_MAX,DIV_INS_MAX)) { ins->type=(DivInstrumentType)insType; } From 36647ac81d88d21a48d866ca087c6b96692c50a2 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 13:03:39 +0900 Subject: [PATCH 08/22] Update submodule --- .gitmodules | 1 + extern/cam900_vgsound_emu | 2 +- src/engine/platform/x1_010.h | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 075769005..8c4c6b0dd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,6 +22,7 @@ [submodule "extern/cam900_vgsound_emu"] path = extern/cam900_vgsound_emu url = https://github.com/cam900/vgsound_emu + branch = main [submodule "extern/Nuked-OPL3"] path = extern/Nuked-OPL3 url = https://github.com/nukeykt/Nuked-OPL3.git diff --git a/extern/cam900_vgsound_emu b/extern/cam900_vgsound_emu index cf4ba11ad..3f8b5c5c6 160000 --- a/extern/cam900_vgsound_emu +++ b/extern/cam900_vgsound_emu @@ -1 +1 @@ -Subproject commit cf4ba11ad786f13c374afb77616d509cb4c751bf +Subproject commit 3f8b5c5c6b996588afec7e4ce7469abccf16ecbe diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 27bf4f4a6..fd00fd1c9 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -26,11 +26,11 @@ #include "../macroInt.h" #include "../../../extern/cam900_vgsound_emu/x1_010/x1_010.hpp" -class DivX1_010Interface: public x1_010_intf { +class DivX1_010Interface: public vgsound_emu_mem_intf { public: DivEngine* parent; int sampleBank; - virtual u8 read_rom(uint32_t address) override { + virtual u8 read_byte(u32 address) override { if (parent->x1_010Mem==NULL) return 0; return parent->x1_010Mem[address & 0xfffff]; } From 55934bc044f17b03cea4d97c036461b1174e32d5 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 13:09:25 +0900 Subject: [PATCH 09/22] Fix crash --- src/gui/insEdit.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 35059804d..a66a7f585 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,6 +52,7 @@ const char* insTypes[25]={ "PC Beeper", "WonderSwan", "Atari Lynx", + "VERA", "X1-010" }; From bc26fbaa3d1ac7d066f4fc7ab12a38fe300febef Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 13:34:13 +0900 Subject: [PATCH 10/22] Add cmdName for X1-010 commands --- src/engine/playback.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index acc4e7607..665409d67 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -117,6 +117,13 @@ 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", + "ALWAYS_SET_VOLUME" }; From b270513639dc06acef4b9d0de3c62871476cf8f2 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 19:41:26 +0900 Subject: [PATCH 11/22] Frequency range limit --- papers/doc/7-systems/x1_010.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index 83587a2b8..7d5778713 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -15,7 +15,7 @@ In furnace, this chip is can be configurable for original arcade mono output or - `10xx`: change wave. - `11xx`: change envelope shape. (also wavetable) - `17xx`: toggle PCM mode. -- `20xx`: set PCM frequency.* +- `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. From 65149a466f5ec69a9fe5a20b409c844ec956318a Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 8 Mar 2022 00:15:21 +0900 Subject: [PATCH 12/22] Fix accidently auto-generated spaces --- src/engine/engine.cpp | 4 +-- src/engine/fileOps.cpp | 8 +++--- src/engine/platform/x1_010.cpp | 44 ++++++++++++++++----------------- src/engine/platform/x1_010.h | 6 ++--- src/engine/platform/ym2610b.cpp | 2 +- src/engine/playback.cpp | 2 +- src/engine/sample.cpp | 2 +- src/engine/vgmOps.cpp | 2 +- src/gui/gui.cpp | 2 +- src/gui/insEdit.cpp | 12 ++++----- 10 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e484baf31..87594f339 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -541,7 +541,7 @@ void DivEngine::renderSamples() { for (int i=0; ilength8+4095)&(~0xfff); - // fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!) + // fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!) if (paddedLen>131072) { paddedLen=131072; } @@ -977,7 +977,7 @@ 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; // TODO: support ADPCM-B case diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index ec7cd1cb8..3a79cebed 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/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 74bfdb13a..1705e8272 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -342,7 +342,7 @@ void DivPlatformX1_010::tick() { chan[i].envChanged=true; } } - if ((!chan[i].pcm) || chan[i].furnacePCM) { + if ((!chan[i].pcm) || chan[i].furnacePCM) { if (chan[i].std.hadArp) { if (!chan[i].inPorta) { if (chan[i].std.arpMode) { @@ -358,7 +358,7 @@ void DivPlatformX1_010::tick() { 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; @@ -369,7 +369,7 @@ void DivPlatformX1_010::tick() { } } if (chan[i].std.hadEx1) { - bool nextEnable=(chan[i].std.ex1&1); + bool nextEnable=(chan[i].std.ex1&1); if (nextEnable!=(chan[i].env.flag.envEnable)) { chan[i].env.flag.envEnable=nextEnable; if (!chan[i].pcm) { @@ -379,28 +379,28 @@ void DivPlatformX1_010::tick() { refreshControl(i); } } - bool nextOneshot=(chan[i].std.ex1&2); + 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); + 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 nextHinv=(chan[i].std.ex1&8); + bool nextHinv=(chan[i].std.ex1&8); if (nextHinv!=(chan[i].env.flag.envHinv)) { chan[i].env.flag.envHinv=nextHinv; if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } - bool nextVinv=(chan[i].std.ex1&16); + bool nextVinv=(chan[i].std.ex1&16); if (nextVinv!=(chan[i].env.flag.envVinv)) { chan[i].env.flag.envVinv=nextVinv; if (!isMuted[i] && !chan[i].pcm) { @@ -435,7 +435,7 @@ void DivPlatformX1_010::tick() { } if (chan[i].envChanged) { if (!isMuted[i]) { - chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; + 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); @@ -500,10 +500,10 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } if (skipRegisterWrites) break; if (chan[c.chan].furnacePCM) { - chan[c.chan].pcm=true; + 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) { + 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 @@ -513,7 +513,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note); chan[c.chan].freqChanged=true; } - } else { + } else { chan[c.chan].std.init(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { @@ -524,7 +524,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chWrite(c.chan,5,0); break; } - } + } } else { chan[c.chan].std.init(NULL); chan[c.chan].outVol=chan[c.chan].vol; @@ -552,7 +552,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].keyOn=true; chan[c.chan].envChanged=true; chan[c.chan].std.init(ins); - refreshControl(c.chan); + refreshControl(c.chan); break; } case DIV_CMD_NOTE_OFF: @@ -598,7 +598,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { updateWave(c.chan); chan[c.chan].keyOn=true; break; - case DIV_CMD_X1_010_ENVELOPE_SHAPE: + 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) { @@ -677,7 +677,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } break; case DIV_CMD_X1_010_ENVELOPE_MODE: { - bool nextEnable=c.value&1; + 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) { @@ -687,28 +687,28 @@ int DivPlatformX1_010::dispatch(DivCommand c) { refreshControl(c.chan); } } - bool nextOneshot=c.value&2; + 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; + 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 nextHinv=c.value&8; + bool nextHinv=c.value&8; if (nextHinv!=(chan[c.chan].env.flag.envHinv)) { chan[c.chan].env.flag.envHinv=nextHinv; if (!isMuted[c.chan] && !chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } - bool nextVinv=c.value&16; + bool nextVinv=c.value&16; if (nextVinv!=(chan[c.chan].env.flag.envVinv)) { chan[c.chan].env.flag.envVinv=nextVinv; if (!isMuted[c.chan] && !chan[c.chan].pcm) { @@ -716,7 +716,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } } break; - } + } case DIV_CMD_X1_010_ENVELOPE_PERIOD: chan[c.chan].env.period=c.value; if (!chan[c.chan].pcm) { @@ -816,7 +816,7 @@ void DivPlatformX1_010::setFlags(unsigned int flags) { case 1: // 16.67MHz (later hardwares) chipClock=50000000.0/3.0; break; - // Other clock is used? + // Other clock is used? } rate=chipClock/512; stereo=flags&16; diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index fd00fd1c9..8e03109ba 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -31,9 +31,9 @@ class DivX1_010Interface: public vgsound_emu_mem_intf { DivEngine* parent; int sampleBank; virtual u8 read_byte(u32 address) override { - if (parent->x1_010Mem==NULL) return 0; - return parent->x1_010Mem[address & 0xfffff]; - } + if (parent->x1_010Mem==NULL) return 0; + return parent->x1_010Mem[address & 0xfffff]; + } DivX1_010Interface(): parent(NULL), sampleBank(0) {} }; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index a6bcf9ac7..e7a6e45c2 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 665409d67..b1a820952 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -256,7 +256,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; - } + } break; case DIV_SYSTEM_X1_010: switch (effect) { diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index b802bf791..571e35118 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -115,7 +115,7 @@ bool DivSample::initInternal(unsigned char d, int count) { case 8: // 8-bit if (data8!=NULL) delete[] data8; length8=count; - // for padding X1-010 sample + // for padding X1-010 sample data8=new signed char[(count+4095)&(~0xfff)]; memset(data8,0,(count+4095)&(~0xfff)); break; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 3c63f65c9..74654f6ea 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -679,7 +679,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: diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 743f91083..50a2db78f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4959,7 +4959,7 @@ bool FurnaceGUI::loop() { updateWindowTitle(); } break; - } + } case DIV_SYSTEM_GB: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index a66a7f585..7ce11531e 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1392,12 +1392,12 @@ void FurnaceGUI::drawInsEdit() { ex1Max=4; ex2Max=15; } - if (ins->type==DIV_INS_X1_010) { - dutyMax=0; - ex1Max=5; - ex2Max=63; - ex2Bit=false; - } + if (ins->type==DIV_INS_X1_010) { + dutyMax=0; + ex1Max=5; + ex2Max=63; + ex2Bit=false; + } if (ins->type==DIV_INS_SAA1099) ex1Max=8; if (settings.macroView==0) { // modern view From 26470d594e27cfb8f67026b83a45d45871b8284c Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 8 Mar 2022 00:43:16 +0900 Subject: [PATCH 13/22] Actually PCM frequency limit --- src/engine/platform/x1_010.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 1705e8272..8fce448e8 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -669,7 +669,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { break; case DIV_CMD_SAMPLE_FREQ: if (chan[c.chan].pcm) { - chan[c.chan].freq=c.value&0xff; + 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); From 3f4966096ab24348c84acfd6e903abcdd27fb1d4 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 8 Mar 2022 00:44:37 +0900 Subject: [PATCH 14/22] Fix info --- src/engine/platform/x1_010.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 8fce448e8..331b38420 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -216,7 +216,7 @@ const char* DivPlatformX1_010::getEffectName(unsigned char effect) { return "17xx: Toggle PCM mode"; break; case 0x20: - return "20xx: Set PCM frequency"; + 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: H.invert right, bit 4: V.invert right)"; From 8b1e557b5c9983f2ca1166c526781d9cf93059b6 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 8 Mar 2022 21:34:12 +0900 Subject: [PATCH 15/22] Sync with master --- src/engine/vgmOps.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index dc825f6a7..069d5a2ef 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -157,7 +157,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_X1_010: for (int i=0; i<16; i++) { w->writeC(0xc8); - w->writeS((isSecond?0x8000:0x0)+(i<<3)); + w->writeS(baseAddr2S+(i<<3)); w->writeC(0); } break; @@ -404,7 +404,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; case DIV_SYSTEM_X1_010: w->writeC(0xc8); - w->writeS((isSecond?0x8000:0)|(write.addr&0x1fff)); + w->writeS(baseAddr2S|(write.addr&0x1fff)); w->writeC(write.val); break; case DIV_SYSTEM_YM2610: From 6c432bc42e6ec1f2280c5931c1568b155fd2bd56 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 00:50:10 +0900 Subject: [PATCH 16/22] Allow Left waveform can be invertable, Improvement documents --- papers/doc/4-instrument/README.md | 2 +- papers/doc/5-wave/README.md | 2 +- papers/doc/7-systems/x1_010.md | 16 +++- src/engine/platform/x1_010.cpp | 120 +++++++++++++++++++----------- src/engine/platform/x1_010.h | 18 +++-- src/gui/debug.cpp | 6 +- src/gui/insEdit.cpp | 6 +- 7 files changed, 110 insertions(+), 60 deletions(-) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index 2b907634d..9a31cf65f 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. diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index 005739257..5e234bf65 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/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index 7d5778713..e2e44f183 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -10,6 +10,18 @@ Wavetable needs to paired with envelope, this feature is similar as AY PSG but i In furnace, this chip is can be configurable for original arcade mono output or stereo output - its 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 8KB RAM for use these feature: + +One is signed 8 bit mono waveform, its operated like other wavetable based sound systems. +These are stored at bottom half of RAM at common case. + +Another one ("Envelope") is 4 bit stereo waveform, its multiplied with above and calculates final output, Each nibble is used for each output channels. +These are stored at upper half of RAM at common case. + +Both waveforms are 128 byte fixed size, its freely allocated at each half of RAM except channel register area: each half can be stored total 32/31 waveforms at once. + # effects - `10xx`: change wave. @@ -20,8 +32,8 @@ In furnace, this chip is can be configurable for original arcade mono output or - 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 sets whether the right shape will mirror the left one. - - bit 4 sets whether the right output will mirror the left one. + - 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. diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 331b38420..e6b07d01b 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -219,7 +219,7 @@ const char* DivPlatformX1_010::getEffectName(unsigned char effect) { 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: H.invert right, bit 4: V.invert right)"; + 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"; @@ -258,8 +258,8 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l 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) { + int sample=chan[ch].sample; + if (sample>=0&&samplesong.sampleLen) { DivSample* s=parent->getSample(sample); if (s->centerRate<1) { off=1.0; @@ -279,7 +279,7 @@ void DivPlatformX1_010::updateWave(int ch) { chan[ch].waveBank ^= 1; } for (int i=0; i<128; i++) { - if (wt->max<1 || wt->len<1) { + if (wt->max<1||wt->len<1) { waveWrite(ch,i,0); } else { waveWrite(ch,i,wt->data[i*wt->len/128]*255/wt->max); @@ -304,23 +304,25 @@ void DivPlatformX1_010::updateEnvelope(int ch) { } else { DivWavetable* wt=parent->getWave(chan[ch].env.shape); for (int i=0; i<128; i++) { - if (wt->max<1 || wt->len<1) { + if (wt->max<1||wt->len<1) { envFill(ch,i); - } else if (chan[ch].env.flag.envHinv||chan[ch].env.flag.envSplit||chan[ch].env.flag.envVinv) { // Stereo config - int la = i, ra = i; - int lo, ro; - if (chan[ch].env.flag.envHinv) { ra = 127-i; } // horizontal invert right envelope + } 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; + 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; + 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.envVinv) { ro = 15-ro; } // vertical invert right envelope + 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; + int out=wt->data[i*wt->len/128]*15/wt->max; envWrite(ch,i,out,out); } } @@ -342,7 +344,7 @@ void DivPlatformX1_010::tick() { chan[i].envChanged=true; } } - if ((!chan[i].pcm) || chan[i].furnacePCM) { + if ((!chan[i].pcm)||chan[i].furnacePCM) { if (chan[i].std.hadArp) { if (!chan[i].inPorta) { if (chan[i].std.arpMode) { @@ -353,13 +355,13 @@ void DivPlatformX1_010::tick() { } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + 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].std.hadWave&&!chan[i].pcm) { if (chan[i].wave!=chan[i].std.wave) { chan[i].wave=chan[i].std.wave; if (!chan[i].pcm) { @@ -389,21 +391,35 @@ void DivPlatformX1_010::tick() { 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) { + if (!isMuted[i]&&!chan[i].pcm) { chan[i].envChanged=true; } } - bool nextHinv=(chan[i].std.ex1&8); - if (nextHinv!=(chan[i].env.flag.envHinv)) { - chan[i].env.flag.envHinv=nextHinv; - if (!isMuted[i] && !chan[i].pcm) { + 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 nextVinv=(chan[i].std.ex1&16); - if (nextVinv!=(chan[i].env.flag.envVinv)) { - chan[i].env.flag.envVinv=nextVinv; - if (!isMuted[i] && !chan[i].pcm) { + 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; } } @@ -412,7 +428,7 @@ void DivPlatformX1_010::tick() { 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])) { + if (chan[i].env.flag.envEnable&&(!isMuted[i])) { chan[i].envChanged=true; } if (!chan[i].keyOff) chan[i].keyOn=true; @@ -441,7 +457,7 @@ void DivPlatformX1_010::tick() { updateEnvelope(i); chan[i].envChanged=false; } - if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + 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; @@ -451,12 +467,12 @@ void DivPlatformX1_010::tick() { 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) { + 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)) { + if (chan[i].keyOn||chan[i].keyOff||(chRead(i,0)&1)) { refreshControl(i); } if (chan[i].keyOn) chan[i].keyOn=false; @@ -492,7 +508,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { 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].pcm) { if (ins->type==DIV_INS_AMIGA) { chan[c.chan].furnacePCM=true; } else { @@ -503,7 +519,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { 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) { + 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 @@ -566,7 +582,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].std.release(); break; case DIV_CMD_INSTRUMENT: - if (chan[c.chan].ins!=c.value || c.value2==1) { + if (chan[c.chan].ins!=c.value||c.value2==1) { chan[c.chan].ins=c.value; } break; @@ -658,11 +674,11 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } 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].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 (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; @@ -697,21 +713,35 @@ int DivPlatformX1_010::dispatch(DivCommand c) { 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) { + if (!isMuted[c.chan]&&!chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } - bool nextHinv=c.value&8; - if (nextHinv!=(chan[c.chan].env.flag.envHinv)) { - chan[c.chan].env.flag.envHinv=nextHinv; - if (!isMuted[c.chan] && !chan[c.chan].pcm) { + 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 nextVinv=c.value&16; - if (nextVinv!=(chan[c.chan].env.flag.envVinv)) { - chan[c.chan].env.flag.envVinv=nextVinv; - if (!isMuted[c.chan] && !chan[c.chan].pcm) { + 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; } } diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 8e03109ba..cb9ad7b42 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -44,21 +44,27 @@ class DivPlatformX1_010: public DivDispatch { unsigned char envEnable : 1; unsigned char envOneshot : 1; unsigned char envSplit : 1; - unsigned char envHinv : 1; - unsigned char envVinv : 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; - envHinv=0; - envVinv=0; + envHinvR=0; + envVinvR=0; + envHinvL=0; + envVinvL=0; } EnvFlag(): envEnable(0), envOneshot(0), envSplit(0), - envHinv(0), - envVinv(0) {} + envHinvR(0), + envVinvR(0), + envHinvL(0), + envVinvL(0) {} }; int shape, period, slide, slidefrac; EnvFlag flag; diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index fc2d51ea1..ddbfb5b57 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -268,8 +268,10 @@ void putDispatchChan(void* data, int chanNum, int type) { 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.envHinv?colorOn:colorOff,">> EnvHinv"); - ImGui::TextColored(ch->env.flag.envVinv?colorOn:colorOff,">> EnvVinv"); + 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: diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 0b7ce0ab2..260713e99 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -150,8 +150,8 @@ const char* mikeyFeedbackBits[11] = { "0", "1", "2", "3", "4", "5", "7", "10", "11", "int", NULL }; -const char* x1_010EnvBits[6]={ - "enable", "oneshot", "split L/R", "Hinv", "Vinv", NULL +const char* x1_010EnvBits[8]={ + "enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL }; const char* oneBit[2]={ @@ -1411,7 +1411,7 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_X1_010) { dutyMax=0; - ex1Max=5; + ex1Max=7; ex2Max=63; ex2Bit=false; } From 66eb40e55e75b0f603cfcb240a381f3d25fc7279 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 01:00:09 +0900 Subject: [PATCH 17/22] Extract X1-010 core from submodule --- .gitmodules | 4 - CMakeLists.txt | 4 +- extern/cam900_vgsound_emu | 1 - src/engine/platform/sound/x1_010/x1_010.cpp | 224 ++++++++++++++++++++ src/engine/platform/sound/x1_010/x1_010.hpp | 127 +++++++++++ src/engine/platform/x1_010.h | 4 +- 6 files changed, 355 insertions(+), 9 deletions(-) delete mode 160000 extern/cam900_vgsound_emu create mode 100644 src/engine/platform/sound/x1_010/x1_010.cpp create mode 100644 src/engine/platform/sound/x1_010/x1_010.hpp diff --git a/.gitmodules b/.gitmodules index 8c4c6b0dd..d63fd70b5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,10 +19,6 @@ [submodule "extern/adpcm"] path = extern/adpcm url = https://github.com/superctr/adpcm -[submodule "extern/cam900_vgsound_emu"] - path = extern/cam900_vgsound_emu - url = https://github.com/cam900/vgsound_emu - branch = main [submodule "extern/Nuked-OPL3"] path = extern/Nuked-OPL3 url = https://github.com/nukeykt/Nuked-OPL3.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 23c8dffcf..364875867 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,8 +228,6 @@ extern/opm/opm.c extern/Nuked-OPLL/opll.c extern/Nuked-OPL3/opl3.c -extern/cam900_vgsound_emu/x1_010/x1_010.cpp - src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/saa1099.cpp @@ -268,6 +266,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 diff --git a/extern/cam900_vgsound_emu b/extern/cam900_vgsound_emu deleted file mode 160000 index 3f8b5c5c6..000000000 --- a/extern/cam900_vgsound_emu +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3f8b5c5c6b996588afec7e4ce7469abccf16ecbe 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 000000000..ea1e52ace --- /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 (int i = 0; i < 16; i++) + m_voice[i].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 000000000..d5c429fda --- /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.h b/src/engine/platform/x1_010.h index cb9ad7b42..73cbaf6b1 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -24,9 +24,9 @@ #include "../dispatch.h" #include "../engine.h" #include "../macroInt.h" -#include "../../../extern/cam900_vgsound_emu/x1_010/x1_010.hpp" +#include "sound/x1_010/x1_010.hpp" -class DivX1_010Interface: public vgsound_emu_mem_intf { +class DivX1_010Interface: public x1_010_mem_intf { public: DivEngine* parent; int sampleBank; From 75b635229ce50c08edd8dcede663acc2919c208c Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 01:01:40 +0900 Subject: [PATCH 18/22] Unnecessary changes --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 364875867..eafb87623 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,7 +227,6 @@ extern/Nuked-OPN2/ym3438.c extern/opm/opm.c extern/Nuked-OPLL/opll.c extern/Nuked-OPL3/opl3.c - src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/saa1099.cpp From ba68ad6ed52e5b79e2b226665f890f15a1a2f242 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 01:06:47 +0900 Subject: [PATCH 19/22] More info in waveform size --- papers/doc/7-systems/x1_010.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index e2e44f183..76517b642 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -21,6 +21,7 @@ Another one ("Envelope") is 4 bit stereo waveform, its multiplied with above and These are stored at upper half of RAM at common case. Both waveforms are 128 byte fixed size, its 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 splitted to left half and right half for each outputs. each max sizes are 128 bytes, total 256 bytes. # effects From a32781bb1a1d02d4bf45775247f78d58f5d1d327 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 01:17:16 +0900 Subject: [PATCH 20/22] grammar --- papers/doc/7-systems/x1_010.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index 76517b642..759d519c9 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -1,27 +1,27 @@ # 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 channel, 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. +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 it's shape are stored at RAM: it means it is user-definable. +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 - its simulates early 'incorrect' emulation on some mono hardware but it is also based on the assumption that each channel is connected to each output. +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 8KB RAM for use these feature: +This chip supports 2 type waveforms, needs to paired external 8 KB RAM for use these features: -One is signed 8 bit mono waveform, its operated like other wavetable based sound systems. -These are stored at bottom half of RAM at common case. +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, its multiplied with above and calculates final output, Each nibble is used for each output channels. -These are stored at upper 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, its 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 splitted to left half and right half for each outputs. each max sizes are 128 bytes, total 256 bytes. +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 From 8d447542e108feebe52af00a715020c9ff77dd52 Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 11 Mar 2022 02:42:27 +0900 Subject: [PATCH 21/22] Use lamda --- src/engine/platform/sound/x1_010/x1_010.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/sound/x1_010/x1_010.cpp b/src/engine/platform/sound/x1_010/x1_010.cpp index ea1e52ace..150928e66 100644 --- a/src/engine/platform/sound/x1_010/x1_010.cpp +++ b/src/engine/platform/sound/x1_010/x1_010.cpp @@ -215,8 +215,8 @@ void x1_010_core::voice_t::reset() void x1_010_core::reset() { - for (int i = 0; i < 16; i++) - m_voice[i].reset(); + for (auto & elem : m_voice) + elem.reset(); std::fill_n(&m_envelope[0], 0x1000, 0); std::fill_n(&m_wave[0], 0x1000, 0); From b42ceae1cbaec8fd70bf9a3300eefd516bf80c3f Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 11 Mar 2022 04:15:04 +0900 Subject: [PATCH 22/22] Code style --- src/engine/platform/x1_010.cpp | 56 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index e6b07d01b..42853855a 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -30,7 +30,7 @@ #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 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 @@ -259,7 +259,7 @@ 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) { + if (sample>=0 && samplesong.sampleLen) { DivSample* s=parent->getSample(sample); if (s->centerRate<1) { off=1.0; @@ -279,7 +279,7 @@ void DivPlatformX1_010::updateWave(int ch) { chan[ch].waveBank ^= 1; } for (int i=0; i<128; i++) { - if (wt->max<1||wt->len<1) { + if (wt->max<1 || wt->len<1) { waveWrite(ch,i,0); } else { waveWrite(ch,i,wt->data[i*wt->len/128]*255/wt->max); @@ -304,9 +304,9 @@ void DivPlatformX1_010::updateEnvelope(int ch) { } else { DivWavetable* wt=parent->getWave(chan[ch].env.shape); for (int i=0; i<128; i++) { - if (wt->max<1||wt->len<1) { + 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 + } 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 @@ -339,12 +339,12 @@ void DivPlatformX1_010::tick() { 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)) { + 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].pcm) || chan[i].furnacePCM) { if (chan[i].std.hadArp) { if (!chan[i].inPorta) { if (chan[i].std.arpMode) { @@ -355,13 +355,13 @@ void DivPlatformX1_010::tick() { } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode&&chan[i].std.finishedArp) { + 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].std.hadWave && !chan[i].pcm) { if (chan[i].wave!=chan[i].std.wave) { chan[i].wave=chan[i].std.wave; if (!chan[i].pcm) { @@ -391,35 +391,35 @@ void DivPlatformX1_010::tick() { 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) { + 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) { + 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) { + 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) { + 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) { + if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } @@ -428,7 +428,7 @@ void DivPlatformX1_010::tick() { 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])) { + if (chan[i].env.flag.envEnable && (!isMuted[i])) { chan[i].envChanged=true; } if (!chan[i].keyOff) chan[i].keyOn=true; @@ -457,7 +457,7 @@ void DivPlatformX1_010::tick() { updateEnvelope(i); chan[i].envChanged=false; } - if (chan[i].freqChanged||chan[i].keyOn||chan[i].keyOff) { + 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; @@ -467,12 +467,12 @@ void DivPlatformX1_010::tick() { 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) { + 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)) { + if (chan[i].keyOn || chan[i].keyOff || (chRead(i,0)&1)) { refreshControl(i); } if (chan[i].keyOn) chan[i].keyOn=false; @@ -508,7 +508,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { 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].pcm) { if (ins->type==DIV_INS_AMIGA) { chan[c.chan].furnacePCM=true; } else { @@ -519,7 +519,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { 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) { + 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 @@ -582,7 +582,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].std.release(); break; case DIV_CMD_INSTRUMENT: - if (chan[c.chan].ins!=c.value||c.value2==1) { + if (chan[c.chan].ins!=c.value || c.value2==1) { chan[c.chan].ins=c.value; } break; @@ -618,7 +618,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { 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])) { + if (chan[c.chan].env.flag.envEnable && (!isMuted[c.chan])) { chan[c.chan].envChanged=true; } chan[c.chan].keyOn=true; @@ -678,7 +678,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; case DIV_CMD_PRE_PORTA: - if (chan[c.chan].active&&c.value2) { + 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; @@ -713,35 +713,35 @@ int DivPlatformX1_010::dispatch(DivCommand c) { 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) { + 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) { + 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) { + 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) { + 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) { + if (!isMuted[c.chan] && !chan[c.chan].pcm) { chan[c.chan].envChanged=true; } }