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.
This commit is contained in:
cam900 2022-03-07 02:31:03 +09:00
parent 6ce2a6743c
commit 4a83c7c5a7
27 changed files with 1340 additions and 25 deletions

3
.gitmodules vendored
View file

@ -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

View file

@ -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)

1
extern/cam900_vgsound_emu vendored Submodule

@ -0,0 +1 @@
Subproject commit cf88168512c1b32bbea7b7dcc1411c4da429a723

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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,

View file

@ -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

View file

@ -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;

View file

@ -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; i<song.sampleLen; i++) {
DivSample* s=song.sample[i];
int paddedLen=(s->length8+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;
}

View file

@ -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),

View file

@ -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:

View file

@ -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 <math.h>
//#define rWrite(a,v) pendingWrites[a]=v;
#define rWrite(a,v) if (!skipRegisterWrites) { x1_010->ram_w(a,v); if (dumpWrites) { addWrite(a,v); } }
#define chRead(c,a) x1_010->ram_r((c<<3)|(a&7))
#define chWrite(c,a,v) rWrite((c<<3)|(a&7),v)
#define waveWrite(c,a,v) rWrite(0x1000|(chan[c].waveBank<<11)|(c<<7)|(a&0x7f),(v-128)&0xff)
#define envFill(c,a) rWrite(0x800|(c<<7)|(a&0x7f),(chan[c].lvol<<4)|chan[c].rvol)
#define envWrite(c,a,l,r) rWrite(0x800|(c<<7)|(a&0x7f),(((chan[c].lvol*(l))/15)<<4)|((chan[c].rvol*(r))/15))
#define refreshControl(c) chWrite(c,0,chan[c].active?(chan[c].pcm?1:((chan[c].env.flag.envEnable&&chan[c].env.flag.envOneshot)?7:3)):0);
#define CHIP_FREQBASE 4194304
const char* regCheatSheetX1_010[]={
// Channel registers
"Ch00_Control", "0000",
"Ch00_PCMVol_WavSel", "0001",
"Ch00_FreqL", "0002",
"Ch00_FreqH", "0003",
"Ch00_Start_EnvFrq", "0004",
"Ch00_End_EnvSel", "0005",
"Ch01_Control", "0008",
"Ch01_PCMVol_WavSel", "0009",
"Ch01_FreqL", "000A",
"Ch01_FreqH", "000B",
"Ch01_Start_EnvFrq", "000C",
"Ch01_End_EnvSel", "000D",
"Ch02_Control", "0010",
"Ch02_PCMVol_WavSel", "0011",
"Ch02_FreqL", "0012",
"Ch02_FreqH", "0013",
"Ch02_Start_EnvFrq", "0014",
"Ch02_End_EnvSel", "0015",
"Ch03_Control", "0018",
"Ch03_PCMVol_WavSel", "0019",
"Ch03_FreqL", "001A",
"Ch03_FreqH", "001B",
"Ch03_Start_EnvFrq", "001C",
"Ch03_End_EnvSel", "001D",
"Ch04_Control", "0020",
"Ch04_PCMVol_WavSel", "0021",
"Ch04_FreqL", "0022",
"Ch04_FreqH", "0023",
"Ch04_Start_EnvFrq", "0024",
"Ch04_End_EnvSel", "0025",
"Ch05_Control", "0028",
"Ch05_PCMVol_WavSel", "0029",
"Ch05_FreqL", "002A",
"Ch05_FreqH", "002B",
"Ch05_Start_EnvFrq", "002C",
"Ch05_End_EnvSel", "002D",
"Ch06_Control", "0030",
"Ch06_PCMVol_WavSel", "0031",
"Ch06_FreqL", "0032",
"Ch06_FreqH", "0033",
"Ch06_Start_EnvFrq", "0034",
"Ch06_End_EnvSel", "0035",
"Ch07_Control", "0038",
"Ch07_PCMVol_WavSel", "0039",
"Ch07_FreqL", "003A",
"Ch07_FreqH", "003B",
"Ch07_Start_EnvFrq", "003C",
"Ch07_End_EnvSel", "003D",
"Ch08_Control", "0040",
"Ch08_PCMVol_WavSel", "0041",
"Ch08_FreqL", "0042",
"Ch08_FreqH", "0043",
"Ch08_Start_EnvFrq", "0044",
"Ch08_End_EnvSel", "0045",
"Ch09_Control", "0048",
"Ch09_PCMVol_WavSel", "0049",
"Ch09_FreqL", "004A",
"Ch09_FreqH", "004B",
"Ch09_Start_EnvFrq", "004C",
"Ch09_End_EnvSel", "004D",
"Ch10_Control", "0050",
"Ch10_PCMVol_WavSel", "0051",
"Ch10_FreqL", "0052",
"Ch10_FreqH", "0053",
"Ch10_Start_EnvFrq", "0054",
"Ch10_End_EnvSel", "0055",
"Ch11_Control", "0058",
"Ch11_PCMVol_WavSel", "0059",
"Ch11_FreqL", "005A",
"Ch11_FreqH", "005B",
"Ch11_Start_EnvFrq", "005C",
"Ch11_End_EnvSel", "005D",
"Ch12_Control", "0060",
"Ch12_PCMVol_WavSel", "0061",
"Ch12_FreqL", "0062",
"Ch12_FreqH", "0063",
"Ch12_Start_EnvFrq", "0064",
"Ch12_End_EnvSel", "0065",
"Ch13_Control", "0068",
"Ch13_PCMVol_WavSel", "0069",
"Ch13_FreqL", "006A",
"Ch13_FreqH", "006B",
"Ch13_Start_EnvFrq", "006C",
"Ch13_End_EnvSel", "006D",
"Ch14_Control", "0070",
"Ch14_PCMVol_WavSel", "0071",
"Ch14_FreqL", "0072",
"Ch14_FreqH", "0073",
"Ch14_Start_EnvFrq", "0074",
"Ch14_End_EnvSel", "0075",
"Ch15_Control", "0078",
"Ch15_PCMVol_WavSel", "0079",
"Ch15_FreqL", "007A",
"Ch15_FreqH", "007B",
"Ch15_Start_EnvFrq", "007C",
"Ch15_End_EnvSel", "007D",
// Envelope data
"Env01Data", "0080",
"Env02Data", "0100",
"Env03Data", "0180",
"Env04Data", "0200",
"Env05Data", "0280",
"Env06Data", "0300",
"Env07Data", "0380",
"Env08Data", "0400",
"Env09Data", "0480",
"Env10Data", "0500",
"Env11Data", "0580",
"Env12Data", "0600",
"Env13Data", "0680",
"Env14Data", "0700",
"Env15Data", "0780",
"Env16Data", "0800",
"Env17Data", "0880",
"Env18Data", "0900",
"Env19Data", "0980",
"Env20Data", "0A00",
"Env21Data", "0A80",
"Env22Data", "0B00",
"Env23Data", "0B80",
"Env24Data", "0C00",
"Env25Data", "0C80",
"Env26Data", "0D00",
"Env27Data", "0D80",
"Env28Data", "0E00",
"Env29Data", "0E80",
"Env30Data", "0F00",
"Env31Data", "0F80",
// Wavetable data
"Wave00Data", "1000",
"Wave01Data", "1080",
"Wave02Data", "1100",
"Wave03Data", "1180",
"Wave04Data", "1200",
"Wave05Data", "1280",
"Wave06Data", "1300",
"Wave07Data", "1380",
"Wave08Data", "1400",
"Wave09Data", "1480",
"Wave10Data", "1500",
"Wave11Data", "1580",
"Wave12Data", "1600",
"Wave13Data", "1680",
"Wave14Data", "1700",
"Wave15Data", "1780",
"Wave16Data", "1800",
"Wave17Data", "1880",
"Wave18Data", "1900",
"Wave19Data", "1980",
"Wave20Data", "1A00",
"Wave21Data", "1A80",
"Wave22Data", "1B00",
"Wave23Data", "1B80",
"Wave24Data", "1C00",
"Wave25Data", "1C80",
"Wave26Data", "1D00",
"Wave27Data", "1D80",
"Wave28Data", "1E00",
"Wave29Data", "1E80",
"Wave30Data", "1F00",
"Wave31Data", "1F80",
NULL
};
const char** DivPlatformX1_010::getRegisterSheet() {
return regCheatSheetX1_010;
}
const char* DivPlatformX1_010::getEffectName(unsigned char effect) {
switch (effect) {
case 0x10:
return "10xx: Change waveform";
break;
case 0x11:
return "11xx: Change envelope shape";
break;
case 0x17:
return "17xx: Toggle PCM mode";
break;
case 0x20:
return "20xx: Set PCM frequency";
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; h<start+len; h++) {
x1_010->tick();
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 && sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=s->centerRate/8363.0;
}
}
return off*parent->calcBaseFreq(chipClock,8192,note,false);
}
// Wavetable note
return NOTE_FREQUENCY(note);
}
void DivPlatformX1_010::updateWave(int ch) {
DivWavetable* wt=parent->getWave(chan[ch].wave);
if (chan[ch].active) {
chan[ch].waveBank ^= 1;
}
for (int i=0; i<128; i++) {
if (wt->max<1 || wt->len<1) {
waveWrite(ch,i,0);
} else {
waveWrite(ch,i,wt->data[i*wt->len/128]*255/wt->max);
}
}
if (!chan[ch].pcm) {
chWrite(ch,1,(chan[ch].waveBank<<4)|(ch&0xf));
}
}
void DivPlatformX1_010::updateEnvelope(int ch) {
if (!chan[ch].pcm) {
if (isMuted[ch]) {
for (int i=0; i<128; i++) {
rWrite(0x800|(ch<<7)|(i&0x7f),0);
}
} else {
if (!chan[ch].env.flag.envEnable) {
for (int i=0; i<128; i++) {
envFill(ch,i);
}
} else {
DivWavetable* wt=parent->getWave(chan[ch].env.shape);
for (int i=0; i<128; i++) {
if (wt->max<1 || wt->len<1) {
envFill(ch,i);
} else if (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].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
chWrite(c.chan,4,(s->offX1_010>>12)&0xff);
int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded
chWrite(c.chan,5,(0x100-(end>>12))&0xff);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
} else {
chan[c.chan].std.init(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
chWrite(c.chan,0,0); // reset
chWrite(c.chan,1,0);
chWrite(c.chan,2,0);
chWrite(c.chan,4,0);
chWrite(c.chan,5,0);
break;
}
}
} else {
chan[c.chan].std.init(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
chWrite(c.chan,0,0); // reset
chWrite(c.chan,1,0);
chWrite(c.chan,2,0);
chWrite(c.chan,4,0);
chWrite(c.chan,5,0);
break;
}
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
chWrite(c.chan,4,(s->offX1_010>>12)&0xff);
int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded
chWrite(c.chan,5,(0x100-(end>>12))&0xff);
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<4)/(chipClock/512);
chan[c.chan].freqChanged=true;
}
} else if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].envChanged=true;
chan[c.chan].std.init(ins);
refreshControl(c.chan);
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].pcm=false;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].std.init(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.hasVol) {
if (chan[c.chan].outVol!=c.value) {
chan[c.chan].outVol=c.value;
if (!isMuted[c.chan]) {
chan[c.chan].envChanged=true;
}
}
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.hasVol) {
return chan[c.chan].vol;
}
return chan[c.chan].outVol;
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_WAVE:
chan[c.chan].wave=c.value;
updateWave(c.chan);
chan[c.chan].keyOn=true;
break;
case DIV_CMD_X1_010_ENVELOPE_SHAPE:
if (chan[c.chan].env.shape!=c.value) {
chan[c.chan].env.shape=c.value;
if (!chan[c.chan].pcm) {
if (chan[c.chan].env.flag.envEnable&&(!isMuted[c.chan])) {
chan[c.chan].envChanged=true;
}
chan[c.chan].keyOn=true;
}
}
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NoteX1_010(c.chan,c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_SAMPLE_MODE:
if (chan[c.chan].pcm!=(c.value&1)) {
chan[c.chan].pcm=c.value&1;
chan[c.chan].freqChanged=true;
if (!isMuted[c.chan]) {
chan[c.chan].envChanged=true;
}
}
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_PANNING: {
if (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<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
stereo=false;
for (int i=0; i<16; i++) {
isMuted[i]=false;
}
setFlags(flags);
intf.parent=parent;
x1_010=new x1_010_core(intf);
x1_010->reset();
reset();
return 16;
}
void DivPlatformX1_010::quit() {
delete x1_010;
}
DivPlatformX1_010::~DivPlatformX1_010() {
}

View file

@ -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 <queue>
#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<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformX1_010();
};
#endif

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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; i<song.systemLen; i++) {
@ -635,6 +649,18 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
howManyChips++;
}
break;
case DIV_SYSTEM_X1_010:
if (!hasX1010) {
hasX1010=disCont[i].dispatch->chipClock;
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; i<song.systemLen; i++) {

View file

@ -36,6 +36,7 @@
#include "../engine/platform/tia.h"
#include "../engine/platform/saa.h"
#include "../engine/platform/amiga.h"
#include "../engine/platform/x1_010.h"
#include "../engine/platform/dummy.h"
#define GENESIS_DEBUG \
@ -232,6 +233,45 @@ void putDispatchChan(void* data, int chanNum, int type) {
ImGui::TextColored(ch->inPorta?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;

View file

@ -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");

View file

@ -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,

View file

@ -27,7 +27,7 @@
#include <imgui.h>
#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);
}

View file

@ -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);