Merge branch 'master' into ay_divider

This commit is contained in:
cam900 2022-05-14 22:09:25 +09:00 committed by GitHub
commit 8e50ad8c90
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
87 changed files with 8037 additions and 649 deletions

View file

@ -187,8 +187,6 @@ jobs:
export USE_WAE=ON
export CMAKE_EXTRA_ARGS=()
if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then
# 1. Go to hell
export USE_WAE=OFF
CMAKE_EXTRA_ARGS+=('-DCMAKE_GENERATOR_PLATFORM=${{ steps.windows-identify.outputs.msvc-target }}')
# Force static linking

View file

@ -45,7 +45,13 @@ option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendor
option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF)
set(DEPENDENCIES_INCLUDE_DIRS "")
if (ANDROID)
set(DEPENDENCIES_DEFINES "IS_MOBILE")
else()
set(DEPENDENCIES_DEFINES "")
endif()
set(DEPENDENCIES_COMPILE_OPTIONS "")
set(DEPENDENCIES_LIBRARIES "")
set(DEPENDENCIES_LIBRARY_DIRS "")
@ -310,6 +316,10 @@ src/engine/platform/sound/vic20sound.c
src/engine/platform/sound/vrcvi/vrcvi.cpp
src/engine/platform/sound/scc/scc.cpp
src/engine/platform/oplAInterface.cpp
src/engine/platform/ym2608Interface.cpp
src/engine/platform/ym2610Interface.cpp
src/engine/blip_buf.c
@ -343,6 +353,8 @@ src/engine/platform/nes.cpp
src/engine/platform/c64.cpp
src/engine/platform/arcade.cpp
src/engine/platform/tx81z.cpp
src/engine/platform/ym2203.cpp
src/engine/platform/ym2608.cpp
src/engine/platform/ym2610.cpp
src/engine/platform/ym2610ext.cpp
src/engine/platform/ym2610b.cpp
@ -362,11 +374,13 @@ src/engine/platform/lynx.cpp
src/engine/platform/su.cpp
src/engine/platform/swan.cpp
src/engine/platform/vera.cpp
src/engine/platform/zxbeeper.cpp
src/engine/platform/bubsyswsg.cpp
src/engine/platform/n163.cpp
src/engine/platform/pet.cpp
src/engine/platform/vic20.cpp
src/engine/platform/vrc6.cpp
src/engine/platform/scc.cpp
src/engine/platform/dummy.cpp
)

View file

@ -126,7 +126,7 @@ Available options:
| Name | Default | Description |
| :--: | :-----: | ----------- |
| `BUILD_GUI` | `ON` if not building for Android, otherwise `OFF` | Build the tracker (disable to build only a headless player) |
| `BUILD_GUI` | `ON` | Build the tracker (disable to build only a headless player) |
| `WITH_JACK` | `ON` if system-installed JACK detected, otherwise `OFF` | Whether to build with JACK support. Auto-detects if JACK is available |
| `SYSTEM_FMT` | `OFF` | Use a system-installed version of fmt instead of the vendored one |
| `SYSTEM_LIBSNDFILE` | `OFF` | Use a system-installed version of libsndfile instead of the vendored one |

View file

@ -5,12 +5,7 @@
- input pad
- settings
- RF5C68 system
- OPN system
- OPNA system
- ZX beeper system
- Y8950 system
- SCC/SCC+ system
- maybe YMU759 ADPCM channel
- ZX beeper system overlay percussion
- ADPCM chips
- Game Boy envelope macro/sequence
- rewrite the system name detection function anyway
@ -21,8 +16,6 @@
- try to find out why does VSlider not accept keyboard input
- finish lock layout
- if macros have release, note off should release them
- add ability to select entire row when clicking on row number
- store edit/followOrders/followPattern state in config
- add ability to select a column by double clicking
- add ability to move selection by dragging
- Apply button in settings

View file

@ -17,6 +17,7 @@ this is a list of systems that Furnace supports, including each system's effects
- [Yamaha YM2612 standalone](ym2612.md)
- [Yamaha YM2151 standalone](ym2151.md)
- [SegaPCM](segapcm.md)
- [Capcom QSound](qsound.md)
- [Atari 2600](tia.md)
- [Philips SAA1099](saa1099.md)
- [Microchip AY8930](ay8930.md)
@ -28,6 +29,7 @@ this is a list of systems that Furnace supports, including each system's effects
- [Yamaha OPL](opl.md)
- [PC Speaker](pcspkr.md)
- [Commodore PET](pet.md)
- [Konami SCC](scc.md)
- [Commodore VIC-20](vic20.md)
- [Konami VRC6](vrc6.md)
- [Famicom Disk System](fds.md)

View file

@ -1,14 +1,12 @@
# Bubble System WSG
a Konami's 2 channel wavetable sound generator logic used at their arcade hardware Bubble System.
a Konami-made 2 channel wavetable sound generator logic used on the Bubble System arcade board, configured with K005289, a 4-bit PROM and DAC.
It's configured with K005289, 4 bit PROM and DAC.
however, the K005289 is just part of the logic used for pitch and wavetable ROM address.
waveform select and volume control are tied with single AY-3-8910 IO for both channels.
another AY-3-8910 IO is used for reading sound hardware status.
Also known as K005289, but that's just part of the logic used for pitch and wavetable ROM address.
Waveform select and Volume control are tied with single AY-3-8910 IO for both channels.
Another AY-3-8910 IO is used for reading sound hardware status.
Furnace emulates this configurations as single system, waveform format is 15 level and 32 width.
Furnace emulates this configuration as single system with 32x16 wavetables.
# effects

View file

@ -0,0 +1,11 @@
# Konami SCC/SCC+
the Sound Creative Chip (SCC) adds 5 channels of wavetable to your MSX!
it was used in (of course) several Konami games, which had better audio quality due to the extra channels provided by this chip (poor AY since nobody used the envelope for bass).
the only problem? the waveform of the fourth channel is shared with the fifth one due to not enough memory in the chip!
the SCC+ fixes this issue though (while being compatible with SCC games).
# effects
- `10xx`: change wave.

View file

@ -4,7 +4,7 @@
yep, that's right! 16 channels of PCM!
a chip used in the Sega OutRun/X/Y arcade boards. eventually the MultiPCM surpassed it with 24 channels, and later they joined the software mixing gang.
a chip used in the Sega OutRun/X/Y arcade boards. eventually the MultiPCM surpassed it with 28 channels, and later they joined the software mixing gang.
# effects
- `20xx`: set PCM frequency.

View file

@ -0,0 +1,101 @@
# Yamaha YM2203 (OPN)
a cost-reduced version of the YM2151 (OPM).
it only has 3 FM channels instead of 8 and removes stereo, the LFO and DT2 (coarse detune).
however it does contain an AY/SSG part which provides 3 channels of square wave with noise and envelope.
this chip was used in the NEC PC-88/PC-98 series of computers, the Fujitsu FM-7AV and in some arcade boards.
several variants of this chip were released as well, with more features.
# effects
- `11xx`: set feedback of channel.
- `12xx`: set operator 1 level.
- `13xx`: set operator 2 level.
- `14xx`: set operator 3 level.
- `15xx`: set operator 4 level.
- `16xy`: set multiplier of operator.
- `x` is the operator (1-4).
- `y` is the mutliplier.
- `18xx`: toggle extended channel 3 mode.
- 0 disables it and 1 enables it.
- only in extended channel 3 system.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.
- `1Cxx`: set attack of operator 3.
- `1Dxx`: set attack of operator 4.
- `20xx`: set SSG channel mode. `xx` may be one of the following:
- `00`: square
- `01`: noise
- `02`: square and noise
- `03`: nothing (apparently)
- `04`: envelope and square
- `05`: envelope and noise
- `06`: envelope and square and noise
- `07`: nothing
- `21xx`: set noise frequency. `xx` is a value between 00 and 1F.
- `22xy`: set envelope mode.
- `x` sets the envelope shape, which may be one of the following:
- `0: \___` decay
- `4: /___` attack once
- `8: \\\\` saw
- `9: \___` decay
- `A: \/\/` inverse obelisco
- `B: \¯¯¯` decay once
- `C: ////` inverse saw
- `D: /¯¯¯` attack
- `E: /\/\` obelisco
- `F: /___` attack once
- if `y` is 1 then the envelope will affect this channel.
- `23xx`: set envelope period low byte.
- `24xx`: set envelope period high byte.
- `25xx`: slide envelope period up.
- `26xx`: slide envelope period down.
- `29xy`: enable SSG 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.
- `30xx`: enable envelope hard reset.
- this works by inserting a quick release and tiny delay before a new note.
- `50xy`: set AM of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` determines whether AM is on.
- `51xy` set SL of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `52xy` set RR of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `53xy` set DT of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value:
- 0: +0
- 1: +1
- 2: +2
- 3: +3
- 4: -0
- 5: -3
- 6: -2
- 7: -1
- `54xy` set RS of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `55xy` set SSG-EG of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value (0-8).
- values between 0 and 7 set SSG-EG.
- value 8 disables it.
- `56xx`: set DR of all operators.
- `57xx`: set DR of operator 1.
- `58xx`: set DR of operator 2.
- `59xx`: set DR of operator 3.
- `5Axx`: set DR of operator 4.
- `5Bxx`: set D2R/SR of all operators.
- `5Cxx`: set D2R/SR of operator 1.
- `5Dxx`: set D2R/SR of operator 2.
- `5Exx`: set D2R/SR of operator 3.
- `5Fxx`: set D2R/SR of operator 4.

View file

@ -0,0 +1,101 @@
# Yamaha YM2608 (OPNA)
like YM2203, but with twice the FM channels, stereo, an ADPCM channel and built-in drums ("rhythm")!
it was one of the available sound chips for the NEC PC-88VA and PC-98 series of computers.
the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built-in drums have been replaced with 6 sample channels.
# effects
- `10xy`: set LFO parameters.
- `x` toggles the LFO.
- `y` sets its speed.
- `11xx`: set feedback of channel.
- `12xx`: set operator 1 level.
- `13xx`: set operator 2 level.
- `14xx`: set operator 3 level.
- `15xx`: set operator 4 level.
- `16xy`: set multiplier of operator.
- `x` is the operator (1-4).
- `y` is the mutliplier.
- `18xx`: toggle extended channel 3 mode.
- 0 disables it and 1 enables it.
- only in extended channel 3 system.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.
- `1Cxx`: set attack of operator 3.
- `1Dxx`: set attack of operator 4.
- `20xx`: set SSG channel mode. `xx` may be one of the following:
- `00`: square
- `01`: noise
- `02`: square and noise
- `03`: nothing (apparently)
- `04`: envelope and square
- `05`: envelope and noise
- `06`: envelope and square and noise
- `07`: nothing
- `21xx`: set noise frequency. `xx` is a value between 00 and 1F.
- `22xy`: set envelope mode.
- `x` sets the envelope shape, which may be one of the following:
- `0: \___` decay
- `4: /___` attack once
- `8: \\\\` saw
- `9: \___` decay
- `A: \/\/` inverse obelisco
- `B: \¯¯¯` decay once
- `C: ////` inverse saw
- `D: /¯¯¯` attack
- `E: /\/\` obelisco
- `F: /___` attack once
- if `y` is 1 then the envelope will affect this channel.
- `23xx`: set envelope period low byte.
- `24xx`: set envelope period high byte.
- `25xx`: slide envelope period up.
- `26xx`: slide envelope period down.
- `29xy`: enable SSG 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.
- `30xx`: enable envelope hard reset.
- this works by inserting a quick release and tiny delay before a new note.
- `50xy`: set AM of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` determines whether AM is on.
- `51xy` set SL of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `52xy` set RR of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `53xy` set DT of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value:
- 0: +0
- 1: +1
- 2: +2
- 3: +3
- 4: -0
- 5: -3
- 6: -2
- 7: -1
- `54xy` set RS of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `55xy` set SSG-EG of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value (0-8).
- values between 0 and 7 set SSG-EG.
- value 8 disables it.
- `56xx`: set DR of all operators.
- `57xx`: set DR of operator 1.
- `58xx`: set DR of operator 2.
- `59xx`: set DR of operator 3.
- `5Axx`: set DR of operator 4.
- `5Bxx`: set D2R/SR of all operators.
- `5Cxx`: set D2R/SR of operator 1.
- `5Dxx`: set D2R/SR of operator 2.
- `5Exx`: set D2R/SR of operator 3.
- `5Fxx`: set D2R/SR of operator 4.

View file

@ -16,9 +16,9 @@ it is backward compatible with the original chip.
- `16xy`: set multiplier of operator.
- `x` is the operator (1-4).
- `y` is the mutliplier.
- `18xx`: toggle extended channel 2 mode.
- `18xx`: toggle extended channel 3 mode.
- 0 disables it and 1 enables it.
- only in extended channel 2 system.
- only in extended channel 3 system.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.

View file

@ -232,6 +232,9 @@ size | description
4f | A-4 tuning
1 | limit slides (>=36) or reserved
1 | linear pitch (>=36) or reserved
| - 0: non-linaer
| - 1: only pitch change (04xy/E5xx) linear
| - 2: full linear (>=94)
1 | loop modality (>=36) or reserved
1 | proper noise layout (>=42) or reserved
1 | wave duty is volume (>=42) or reserved
@ -286,7 +289,8 @@ size | description
1 | weird f-num/block-based chip pitch slides (>=85) or reserved
1 | SN duty macro always resets phase (>=86) or reserved
1 | pitch macro is linear (>=90) or reserved
19 | reserved
1 | pitch slide speed in full linear pitch mode (>=94) or reserved
18 | reserved
```
# instrument

View file

@ -29,6 +29,8 @@
#include "platform/c64.h"
#include "platform/arcade.h"
#include "platform/tx81z.h"
#include "platform/ym2203.h"
#include "platform/ym2608.h"
#include "platform/ym2610.h"
#include "platform/ym2610ext.h"
#include "platform/ym2610b.h"
@ -54,8 +56,10 @@
#include "platform/vrc6.h"
#include "platform/fds.h"
#include "platform/mmc5.h"
#include "platform/scc.h"
#include "platform/dummy.h"
#include "../ta-log.h"
#include "platform/zxbeeper.h"
#include "song.h"
void DivDispatchContainer::setRates(double gotRate) {
@ -233,6 +237,12 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_TIA:
dispatch=new DivPlatformTIA;
break;
case DIV_SYSTEM_OPN:
dispatch=new DivPlatformYM2203;
break;
case DIV_SYSTEM_PC98:
dispatch=new DivPlatformYM2608;
break;
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7:
@ -264,6 +274,14 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(3,true);
break;
case DIV_SYSTEM_Y8950:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(8950,false);
break;
case DIV_SYSTEM_Y8950_DRUMS:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(8950,true);
break;
case DIV_SYSTEM_OPZ:
dispatch=new DivPlatformTX81Z;
break;
@ -277,6 +295,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_PCSPKR:
dispatch=new DivPlatformPCSpeaker;
break;
case DIV_SYSTEM_SFX_BEEPER:
dispatch=new DivPlatformZXBeeper;
break;
case DIV_SYSTEM_LYNX:
dispatch=new DivPlatformLynx;
break;
@ -314,6 +335,14 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_MMC5:
dispatch=new DivPlatformMMC5;
break;
case DIV_SYSTEM_SCC:
dispatch=new DivPlatformSCC;
((DivPlatformSCC*)dispatch)->setChipModel(false);
break;
case DIV_SYSTEM_SCC_PLUS:
dispatch=new DivPlatformSCC;
((DivPlatformSCC*)dispatch)->setChipModel(true);
break;
case DIV_SYSTEM_SOUND_UNIT:
dispatch=new DivPlatformSoundUnit;
break;

View file

@ -491,6 +491,86 @@ void DivEngine::notifyWaveChange(int wave) {
BUSY_END;
}
int DivEngine::loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret) {
ret = NULL;
if (path.empty()) {
return 0;
}
logI("loading ROM %s...",path);
FILE* f=ps_fopen(path.c_str(),"rb");
if (f==NULL) {
logE("error: %s",strerror(errno));
lastError=strerror(errno);
return -1;
}
if (fseek(f,0,SEEK_END)<0) {
logE("size error: %s",strerror(errno));
lastError=fmt::sprintf("on seek: %s",strerror(errno));
fclose(f);
return -1;
}
ssize_t len=ftell(f);
if (len==(SIZE_MAX>>1)) {
logE("could not get file length: %s",strerror(errno));
lastError=fmt::sprintf("on pre tell: %s",strerror(errno));
fclose(f);
return -1;
}
if (len<1) {
if (len==0) {
logE("that file is empty!");
lastError="file is empty";
} else {
logE("tell error: %s",strerror(errno));
lastError=fmt::sprintf("on tell: %s",strerror(errno));
}
fclose(f);
return -1;
}
if (len!=expectedSize) {
logE("ROM size mismatch, expected: %d bytes, was: %d bytes", expectedSize, len);
lastError=fmt::sprintf("ROM size mismatch, expected: %d bytes, was: %d", expectedSize, len);
return -1;
}
if (fseek(f,0,SEEK_SET)<0) {
logE("size error: %s",strerror(errno));
lastError=fmt::sprintf("on get size: %s",strerror(errno));
fclose(f);
return -1;
}
unsigned char* file=new unsigned char[len];
if (fread(file,1,(size_t)len,f)!=(size_t)len) {
logE("read error: %s",strerror(errno));
lastError=fmt::sprintf("on read: %s",strerror(errno));
fclose(f);
delete[] file;
return -1;
}
fclose(f);
ret = file;
return 0;
}
int DivEngine::loadSampleROMs() {
if (yrw801ROM!=NULL) {
delete[] yrw801ROM;
yrw801ROM=NULL;
}
if (tg100ROM!=NULL) {
delete[] tg100ROM;
tg100ROM=NULL;
}
if (mu5ROM!=NULL) {
delete[] mu5ROM;
mu5ROM=NULL;
}
int error = 0;
error += loadSampleROM(getConfString("yrw801Path",""), 0x200000, yrw801ROM);
error += loadSampleROM(getConfString("tg100Path",""), 0x200000, tg100ROM);
error += loadSampleROM(getConfString("mu5Path",""), 0x200000, mu5ROM);
return error;
}
void DivEngine::renderSamplesP() {
BUSY_BEGIN;
renderSamples();
@ -986,38 +1066,66 @@ int DivEngine::calcBaseFreq(double clock, double divider, int note, bool period)
}*/
double DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) {
if (song.linearPitch==2) { // full linear
return (note<<7);
}
double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0);
return period?
(clock/base)/divider:
base*(divider/clock);
}
unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) {
int bf=calcBaseFreq(clock,divider,note,false);
int block=note/12;
if (block<0) block=0;
if (block>7) block=7;
bf>>=block;
if (bf<0) bf=0;
// octave boundaries
while (bf>0 && bf<644 && block>0) {
bf<<=1;
block--;
}
if (bf>1288) {
while (block<7) {
bf>>=1;
block++;
}
if (bf>((1<<bits)-1)) {
bf=(1<<bits)-1;
}
}
#define CONVERT_FNUM_BLOCK(bf,bits,note) \
double tuning=song.tuning; \
if (tuning<400.0) tuning=400.0; \
if (tuning>500.0) tuning=500.0; \
int boundaryBottom=tuning*pow(2.0,0.25)*(divider/clock); \
int boundaryTop=2.0*tuning*pow(2.0,0.25)*(divider/clock); \
int block=(note)/12; \
if (block<0) block=0; \
if (block>7) block=7; \
bf>>=block; \
if (bf<0) bf=0; \
/* octave boundaries */ \
while (bf>0 && bf<boundaryBottom && block>0) { \
bf<<=1; \
block--; \
} \
if (bf>boundaryTop) { \
while (block<7 && bf>boundaryTop) { \
bf>>=1; \
block++; \
} \
if (bf>((1<<bits)-1)) { \
bf=(1<<bits)-1; \
} \
} \
/* logV("f-num: %d block: %d",bf,block); */ \
return bf|(block<<bits);
int DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) {
if (song.linearPitch==2) { // full linear
return (note<<7);
}
int bf=calcBaseFreq(clock,divider,note,false);
CONVERT_FNUM_BLOCK(bf,bits,note)
}
int DivEngine::calcFreq(int base, int pitch, bool period, int octave, int pitch2) {
if (song.linearPitch) {
int DivEngine::calcFreq(int base, int pitch, bool period, int octave, int pitch2, double clock, double divider, int blockBits) {
if (song.linearPitch==2) {
// do frequency calculation here
int nbase=base+pitch+pitch2;
double fbase=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(nbase+384)/(128.0*12.0));
int bf=period?
round((clock/fbase)/divider):
round(fbase*(divider/clock));
if (blockBits>0) {
CONVERT_FNUM_BLOCK(bf,blockBits,nbase>>7)
} else {
return bf;
}
}
if (song.linearPitch==1) {
// global pitch multiplier
int whatTheFuck=(1024+(globalPitch<<6)-(globalPitch<0?globalPitch-6:0));
if (whatTheFuck<1) whatTheFuck=1; // avoids division by zero but please kill me
@ -1209,7 +1317,7 @@ void DivEngine::reset() {
chan[i]=DivChannelState();
if (i<chans) chan[i].volMax=(disCont[dispatchOfChan[i]].dispatch->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff;
chan[i].volume=chan[i].volMax;
if (!song.linearPitch) chan[i].vibratoFine=4;
if (song.linearPitch==0) chan[i].vibratoFine=4;
}
extValue=0;
extValuePresent=0;
@ -1504,7 +1612,9 @@ int DivEngine::addInstrument(int refChan) {
*ins=song.nullInsQSound;
}
ins->name=fmt::sprintf("Instrument %d",insCount);
if (prefType!=DIV_INS_NULL) {
ins->type=prefType;
}
saveLock.lock();
song.ins.push_back(ins);
song.insLen=insCount+1;
@ -2202,9 +2312,9 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
// 1. check which channels are viable for this instrument
DivInstrument* insInst=getIns(ins);
if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type) notInViableChannel=true;
if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type && getPreferInsType(finalChan)!=DIV_INS_NULL) notInViableChannel=true;
for (int i=0; i<chans; i++) {
if (ins==-1 || ins>=song.insLen || getPreferInsType(i)==insInst->type || getPreferInsSecondType(i)==insInst->type) {
if (ins==-1 || ins>=song.insLen || getPreferInsType(i)==insInst->type || (getPreferInsType(i)==DIV_INS_NULL && finalChanType==DIV_CH_NOISE) || getPreferInsSecondType(i)==insInst->type) {
if (insInst->type==DIV_INS_OPL) {
if (insInst->fm.ops==2 || getChannelType(i)==DIV_CH_OP) {
isViable[i]=true;
@ -2680,6 +2790,8 @@ bool DivEngine::init() {
loadConf();
loadSampleROMs();
// set default system preset
if (!hasLoadedSomething) {
logD("setting default preset");
@ -2753,5 +2865,8 @@ bool DivEngine::quit() {
active=false;
delete[] oscBuf[0];
delete[] oscBuf[1];
if (yrw801ROM!=NULL) delete[] yrw801ROM;
if (tg100ROM!=NULL) delete[] tg100ROM;
if (mu5ROM!=NULL) delete[] mu5ROM;
return true;
}

View file

@ -45,8 +45,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev93"
#define DIV_ENGINE_VERSION 93
#define DIV_VERSION "dev94"
#define DIV_ENGINE_VERSION 94
// for imports
#define DIV_VERSION_MOD 0xff01
@ -188,6 +188,7 @@ typedef std::function<bool(int,unsigned char,unsigned char)> EffectProcess;
struct DivSysDef {
const char* name;
const char* nameJ;
const char* description;
unsigned char id;
unsigned char id_DMF;
int channels;
@ -203,7 +204,7 @@ struct DivSysDef {
EffectProcess postEffectFunc;
DivSysDef(
const char* sysName, const char* sysNameJ, unsigned char fileID, unsigned char fileID_DMF, int chans,
bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound,
bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound, const char* desc,
std::initializer_list<const char*> chNames,
std::initializer_list<const char*> chShortNames,
std::initializer_list<int> chTypes,
@ -213,6 +214,7 @@ struct DivSysDef {
EffectProcess postFxHandler=[](int,unsigned char,unsigned char) -> bool {return false;}):
name(sysName),
nameJ(sysNameJ),
description(desc),
id(fileID),
id_DMF(fileID_DMF),
channels(chans),
@ -397,6 +399,8 @@ class DivEngine {
void loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
bool initAudioBackend();
bool deinitAudioBackend();
@ -481,10 +485,10 @@ class DivEngine {
double calcBaseFreq(double clock, double divider, int note, bool period);
// calculate base frequency in f-num/block format
unsigned short calcBaseFreqFNumBlock(double clock, double divider, int note, int bits);
int calcBaseFreqFNumBlock(double clock, double divider, int note, int bits);
// calculate frequency/period
int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0);
int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0, double clock=1.0, double divider=1.0, int blockBits=0);
// convert panning formats
int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range);
@ -783,6 +787,9 @@ class DivEngine {
// get register cheatsheet
const char** getRegisterSheet(int sys);
// load sample ROMs
int loadSampleROMs();
// UNSAFE render samples - only execute when locked
void renderSamples();
@ -856,6 +863,10 @@ class DivEngine {
// terminate the engine.
bool quit();
unsigned char* yrw801ROM;
unsigned char* tg100ROM;
unsigned char* mu5ROM;
DivEngine():
output(NULL),
exportThread(NULL),
@ -930,7 +941,10 @@ class DivEngine {
oscReadPos(0),
oscWritePos(0),
tickMult(1),
processTime(0) {
processTime(0),
yrw801ROM(NULL),
tg100ROM(NULL),
mu5ROM(NULL) {
memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool));
memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool));
memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int));

View file

@ -139,7 +139,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
// compatibility flags
ds.limitSlides=true;
ds.linearPitch=true;
ds.linearPitch=1;
ds.loopModality=0;
ds.properNoiseLayout=false;
ds.waveDutyIsVol=false;
@ -173,12 +173,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.legacyVolumeSlides=false;
}
// Neo Geo detune
// Neo Geo detune is caused by Defle running Neo Geo at the wrong clock.
/*
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.tuning=443.23;
}
*/
logI("reading module data...");
if (ds.version>0x0c) {
@ -652,7 +654,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
}
for (int j=0; j<ds.ordersLen; j++) {
DivPattern* pat=chan.getPattern(ds.orders.ord[i][j],true);
if (ds.version>0x05) { // current pattern format
if (ds.version>0x08) { // current pattern format
for (int k=0; k<ds.patLen; k++) {
// note
pat->data[k][0]=reader.readS();
@ -739,11 +741,15 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
unsigned char vol=reader.readC();
unsigned char fx=reader.readC();
unsigned char fxVal=reader.readC();
pat->data[k][3]=(vol==0x80)?-1:vol;
pat->data[k][3]=(vol==0x80 || vol==0xff)?-1:vol;
// effect
pat->data[k][4]=(fx==0x80)?-1:fx;
pat->data[k][5]=(fxVal==0x80)?-1:fxVal;
// instrument wasn't stored back then
pat->data[k][4]=(fx==0x80 || fx==0xff)?-1:fx;
pat->data[k][5]=(fxVal==0x80 || fx==0xff)?-1:fxVal;
// instrument
if (ds.version>0x05) {
pat->data[k][2]=reader.readC();
if (pat->data[k][2]==0x80 || pat->data[k][2]==0xff) pat->data[k][2]=-1;
}
}
}
}
@ -948,7 +954,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<37) { // compat flags not stored back then
ds.limitSlides=true;
ds.linearPitch=true;
ds.linearPitch=1;
ds.loopModality=0;
}
if (ds.version<43) {
@ -1390,7 +1396,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
} else {
reader.readC();
}
for (int i=0; i<19; i++) {
if (ds.version>=94) {
ds.pitchSlideSpeed=reader.readC();
} else {
reader.readC();
}
for (int i=0; i<18; i++) {
reader.readC();
}
}
@ -1647,7 +1658,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
DivSong ds;
ds.tuning=436.0;
ds.version=DIV_VERSION_MOD;
ds.linearPitch=false;
ds.linearPitch=0;
ds.noSlidesOnFirstTick=true;
ds.rowResetsArpPos=true;
ds.ignoreJumpAtEnd=false;
@ -2700,7 +2711,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeC(song.fbPortaPause);
w->writeC(song.snDutyReset);
w->writeC(song.pitchMacroIsLinear);
for (int i=0; i<19; i++) {
w->writeC(song.pitchSlideSpeed);
for (int i=0; i<18; i++) {
w->writeC(0);
}

View file

@ -311,6 +311,32 @@ struct DivInstrumentAmiga {
int noteFreq[120];
short noteMap[120];
/**
* get the sample at specified note.
* @return the sample.
*/
inline short getSample(int note) {
if (useNoteMap) {
if (note<0) note=0;
if (note>119) note=119;
return noteMap[note];
}
return initSample;
}
/**
* get the sample frequency at specified note.
* @return the frequency, or -1 if not using note map.
*/
inline int getFreq(int note) {
if (useNoteMap) {
if (note<0) note=0;
if (note>119) note=119;
return noteFreq[note];
}
return -1;
}
DivInstrumentAmiga():
initSample(0),
useNoteMap(false),

View file

@ -220,7 +220,7 @@ void DivPlatformAmiga::tick(bool sysTick) {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].freq>4095) chan[i].freq=4095;
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].keyOn) {
@ -238,7 +238,6 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
double off=1.0;
if (ins->amiga.useWave) {
chan[c.chan].useWave=true;
chan[c.chan].audLen=(ins->amiga.waveLen+1)>>1;
@ -250,19 +249,11 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
}
}
} else {
chan[c.chan].sample=ins->amiga.initSample;
chan[c.chan].sample=ins->amiga.getSample(c.value);
chan[c.chan].useWave=false;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=8363.0/(double)s->centerRate;
}
}
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value));
chan[c.chan].baseFreq=round(NOTE_PERIODIC_NOROUND(c.value));
}
if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1;
@ -328,17 +319,8 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
break;
case DIV_CMD_NOTE_PORTA: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].sample=ins->amiga.initSample;
double off=1.0;
if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=8363.0/(double)s->centerRate;
}
}
int destFreq=round(off*NOTE_PERIODIC_NOROUND(c.value2));
chan[c.chan].sample=ins->amiga.getSample(c.value2);
int destFreq=round(NOTE_PERIODIC_NOROUND(c.value2));
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
@ -361,16 +343,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
break;
}
case DIV_CMD_LEGATO: {
double off=1.0;
if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=8363.0/(double)s->centerRate;
}
}
chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))));
chan[c.chan].baseFreq=round(NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;

View file

@ -251,7 +251,7 @@ void DivPlatformAY8910::tick(bool sysTick) {
if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].freq>4095) chan[i].freq=4095;
if (chan[i].keyOn) {
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));

View file

@ -280,7 +280,7 @@ void DivPlatformAY8930::tick(bool sysTick) {
immWrite(0x1a,ayNoiseOr);
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].freq>65535) chan[i].freq=65535;
if (chan[i].keyOn) {
if (chan[i].insChanged) {

View file

@ -137,7 +137,7 @@ void DivPlatformBubSysWSG::tick(bool sysTick) {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SCC);
chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)+chan[i].pitch2;
chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>4095) chan[i].freq=4095;
k005289->load(i,chan[i].freq);

View file

@ -217,7 +217,7 @@ void DivPlatformC64::tick(bool sysTick) {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
if (chan[i].keyOn) {
rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay));

View file

@ -61,7 +61,7 @@ void DivPlatformDummy::tick(bool sysTick) {
if (chan[i].freqChanged) {
chan[i].freqChanged=false;
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,0,chipClock,CHIP_FREQBASE);
}
}
}

View file

@ -220,7 +220,7 @@ void DivPlatformFDS::tick(bool sysTick) {
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].freq>4095) chan[i].freq=4095;
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].keyOn) {

View file

@ -237,7 +237,7 @@ void DivPlatformGB::tick(bool sysTick) {
if (ntPos>255) ntPos=255;
chan[i].freq=noiseTable[ntPos];
} else {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].freq>2047) chan[i].freq=2047;
if (chan[i].freq<0) chan[i].freq=0;
}

View file

@ -471,7 +471,10 @@ void DivPlatformGenesis::tick(bool sysTick) {
for (int i=0; i<6; i++) {
if (i==2 && extMode) continue;
if (chan[i].freqChanged) {
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2);
if (parent->song.linearPitch==2) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
} else {
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
int block=(chan[i].baseFreq&0xf800)>>11;
if (fNum<0) fNum=0;
if (fNum>2047) {
@ -482,6 +485,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
if (fNum>2047) fNum=2047;
}
chan[i].freq=(block<<11)|fNum;
}
if (chan[i].freq>0x3fff) chan[i].freq=0x3fff;
immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8);
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
@ -495,7 +499,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
off=(double)s->centerRate/8363.0;
}
}
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4)+chan[i].pitch2;
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,1,1);
dacRate=chan[i].freq*off;
if (dacRate<1) dacRate=1;
if (dumpWrites) addWrite(0xffff0001,dacRate);
@ -543,7 +547,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
if (c.chan==5 && dacMode) {
if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA) { // Furnace mode
dacSample=ins->amiga.initSample;
dacSample=ins->amiga.getSample(c.value);
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);
@ -702,6 +706,29 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
if (parent->song.linearPitch==2) {
int destFreq=NOTE_FREQUENCY(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;
}
if (c.chan==5 && chan[c.chan].furnaceDac && dacMode) {
int destFreq=parent->calcBaseFreq(1,1,c.value2,false);
bool return2=false;
@ -725,6 +752,8 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
}
break;
}
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq;
bool return2=false;
@ -747,13 +776,13 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
// check for octave boundary
// what the heck!
if (!chan[c.chan].portaPause) {
if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) {
chan[c.chan].portaPauseFreq=(644)|((newFreq+0x800)&0xf800);
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
chan[c.chan].portaPause=true;
break;
}
if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) {
chan[c.chan].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800);
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
chan[c.chan].portaPause=true;
break;
}

View file

@ -127,6 +127,31 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
if (parent->song.linearPitch==2) {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>opChan[ch].baseFreq) {
opChan[ch].baseFreq+=c.value;
if (opChan[ch].baseFreq>=destFreq) {
opChan[ch].baseFreq=destFreq;
return2=true;
}
} else {
opChan[ch].baseFreq-=c.value;
if (opChan[ch].baseFreq<=destFreq) {
opChan[ch].baseFreq=destFreq;
return2=true;
}
}
opChan[ch].freqChanged=true;
if (return2) {
//opChan[ch].inPorta=false;
return 2;
}
break;
}
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq;
bool return2=false;
@ -148,18 +173,18 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
}
// what the heck!
if (!opChan[ch].portaPause) {
if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) {
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=(644)|((newFreq+0x800)&0xf800);
opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
}
}
if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) {
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800);
opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
@ -435,6 +460,9 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
unsigned char writeMask=2;
if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) {
if (parent->song.linearPitch==2) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
} else {
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
int block=(opChan[i].baseFreq&0xf800)>>11;
if (fNum<0) fNum=0;
@ -446,6 +474,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
if (fNum>2047) fNum=2047;
}
opChan[i].freq=(block<<11)|fNum;
}
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff);

View file

@ -201,7 +201,7 @@ void DivPlatformLynx::tick(bool sysTick) {
WRITE_OTHER(i, ((chan[i].lfsr&0xf00)>>4));
chan[i].lfsr=-1;
}
chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].std.duty.had) {
chan[i].duty=chan[i].std.duty.val;
WRITE_FEEDBACK(i, chan[i].duty.feedback);

View file

@ -146,7 +146,7 @@ void DivPlatformMMC5::tick(bool sysTick) {
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
if (chan[i].freq>2047) chan[i].freq=2047;
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].keyOn) {
@ -192,7 +192,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
if (c.chan==2) { // PCM
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD);
if (ins->type==DIV_INS_AMIGA) {
dacSample=ins->amiga.initSample;
dacSample=ins->amiga.getSample(c.value);
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);

View file

@ -357,7 +357,7 @@ void DivPlatformN163::tick(bool sysTick) {
chan[i].waveUpdated=false;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0,chan[i].pitch2);
chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff;
if (chan[i].keyOn) {

View file

@ -308,7 +308,7 @@ void DivPlatformNES::tick(bool sysTick) {
if (ntPos>252) ntPos=252;
chan[i].freq=(parent->song.properNoiseLayout)?(15-(chan[i].baseFreq&15)):(noiseTable[ntPos]);
} else {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
if (chan[i].freq>2047) chan[i].freq=2047;
if (chan[i].freq<0) chan[i].freq=0;
}
@ -381,7 +381,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
if (c.chan==4) { // PCM
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD);
if (ins->type==DIV_INS_AMIGA) {
dacSample=ins->amiga.initSample;
dacSample=ins->amiga.getSample(c.value);
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);

View file

@ -19,6 +19,7 @@
#include "opl.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <string.h>
#include <math.h>
@ -246,19 +247,53 @@ const char* DivPlatformOPL::getEffectName(unsigned char effect) {
void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) {
static short o[2];
static int os[2];
static ymfm::ymfm_output<2> aOut;
for (size_t h=start; h<start+len; h++) {
os[0]=0; os[1]=0;
if (!writes.empty() && --delay<0) {
delay=1;
QueuedWrite& w=writes.front();
switch (w.addr) {
case 8:
if (adpcmChan>=0) {
adpcmB->write(w.addr-7,(w.val&15)|0x80);
OPL3_WriteReg(&fm,w.addr,w.val&0xc0);
} else {
OPL3_WriteReg(&fm,w.addr,w.val);
}
break;
case 7: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 21: case 22: case 23:
if (adpcmChan>=0) {
adpcmB->write(w.addr-7,w.val);
} else {
OPL3_WriteReg(&fm,w.addr,w.val);
}
break;
default:
OPL3_WriteReg(&fm,w.addr,w.val);
break;
}
regPool[w.addr&511]=w.val;
writes.pop();
}
OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1];
if (adpcmChan>=0) {
adpcmB->clock();
aOut.clear();
adpcmB->output<2>(aOut,0);
if (!isMuted[adpcmChan]) {
os[0]+=aOut.data[0];
os[1]+=aOut.data[0];
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0];
} else {
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0;
}
}
for (int i=0; i<chans; i++) {
unsigned char ch=outChanMap[i];
if (ch==255) continue;
@ -292,6 +327,15 @@ void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len)
//}
}
double DivPlatformOPL::NOTE_ADPCMB(int note) {
if (adpcmChan<0) return 0;
if (chan[adpcmChan].sample>=0 && chan[adpcmChan].sample<parent->song.sampleLen) {
double off=65535.0*(double)(parent->getSample(chan[adpcmChan].sample)->centerRate)/8363.0;
return parent->calcBaseFreq((double)chipClock/144,off,note,false);
}
return 0;
}
void DivPlatformOPL::tick(bool sysTick) {
for (int i=0; i<totalChans; i++) {
int ops=(slots[3][i]!=255 && chan[i].state.ops==4 && oplType==3)?4:2;
@ -501,6 +545,45 @@ void DivPlatformOPL::tick(bool sysTick) {
}
}
// ADPCM
if (adpcmChan>=0) {
if (chan[adpcmChan].furnacePCM) {
chan[adpcmChan].std.next();
if (chan[adpcmChan].std.vol.had) {
chan[adpcmChan].outVol=(chan[adpcmChan].vol*MIN(64,chan[adpcmChan].std.vol.val))/64;
immWrite(18,chan[adpcmChan].outVol);
}
if (chan[adpcmChan].std.arp.had) {
if (!chan[adpcmChan].inPorta) {
if (chan[adpcmChan].std.arp.mode) {
chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].std.arp.val);
} else {
chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note+(signed char)chan[adpcmChan].std.arp.val);
}
}
chan[adpcmChan].freqChanged=true;
} else {
if (chan[adpcmChan].std.arp.mode && chan[adpcmChan].std.arp.finished) {
chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note);
chan[adpcmChan].freqChanged=true;
}
}
}
if (chan[adpcmChan].freqChanged) {
if (chan[adpcmChan].sample>=0 && chan[adpcmChan].sample<parent->song.sampleLen) {
double off=65535.0*(double)(parent->getSample(chan[adpcmChan].sample)->centerRate)/8363.0;
chan[adpcmChan].freq=parent->calcFreq(chan[adpcmChan].baseFreq,chan[adpcmChan].pitch,false,4,chan[adpcmChan].pitch2,(double)chipClock/144,off);
} else {
chan[adpcmChan].freq=0;
}
immWrite(16,chan[adpcmChan].freq&0xff);
immWrite(17,(chan[adpcmChan].freq>>8)&0xff);
chan[adpcmChan].freqChanged=false;
}
}
for (int i=0; i<512; i++) {
if (pendingWrites[i]!=oldWrites[i]) {
immWrite(i,pendingWrites[i]&0xff);
@ -511,7 +594,7 @@ void DivPlatformOPL::tick(bool sysTick) {
bool updateDrums=false;
for (int i=0; i<totalChans; i++) {
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].freq>131071) chan[i].freq=131071;
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
chan[i].freqH=freqt>>8;
@ -588,6 +671,7 @@ int DivPlatformOPL::toFreq(int freq) {
void DivPlatformOPL::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (ch==adpcmChan) return;
if (oplType<3 && ch<melodicChans) {
fm.channel[outChanMap[ch]].muted=mute;
}
@ -625,13 +709,78 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) {
}
int DivPlatformOPL::dispatch(DivCommand c) {
if (c.chan>=totalChans) return 0;
if (c.chan>=totalChans && c.chan!=adpcmChan) return 0;
// ineffective in 4-op mode
if (oplType==3 && c.chan<14 && (c.chan&1) && c.cmd!=DIV_CMD_GET_VOLMAX && c.cmd!=DIV_ALWAYS_SET_VOLUME) {
if (oplType==3 && c.chan!=adpcmChan && c.chan<14 && (c.chan&1) && c.cmd!=DIV_CMD_GET_VOLMAX && c.cmd!=DIV_ALWAYS_SET_VOLUME) {
if (chan[c.chan-1].fourOp) return 0;
}
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
if (c.chan==adpcmChan) { // ADPCM
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
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].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(18,chan[c.chan].outVol);
}
chan[c.chan].sample=ins->amiga.getSample(c.value);
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(9,(s->offB>>2)&0xff);
immWrite(10,(s->offB>>10)&0xff);
int end=s->offB+s->lengthB-1;
immWrite(11,(end>>2)&0xff);
immWrite(12,(end>>10)&0xff);
immWrite(8,2);
immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(7,0x01); // reset
immWrite(9,0);
immWrite(10,0);
immWrite(11,0);
immWrite(12,0);
break;
}
} else {
chan[c.chan].sample=-1;
chan[c.chan].macroInit(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
immWrite(7,0x01); // reset
immWrite(9,0);
immWrite(10,0);
immWrite(11,0);
immWrite(12,0);
break;
}
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
immWrite(9,(s->offB>>2)&0xff);
immWrite(10,(s->offB>>10)&0xff);
int end=s->offB+s->lengthB-1;
immWrite(11,(end>>2)&0xff);
immWrite(12,(end>>10)&0xff);
immWrite(8,2);
immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat
int freq=(65536.0*(double)s->rate)/(double)rate;
immWrite(16,freq&0xff);
immWrite(17,(freq>>8)&0xff);
}
break;
}
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_OPL);
if (chan[c.chan].insChanged) {
@ -714,11 +863,19 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_OFF:
if (c.chan==adpcmChan) {
immWrite(7,0x01); // reset
break;
}
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
break;
case DIV_CMD_NOTE_OFF_ENV:
if (c.chan==adpcmChan) {
immWrite(7,0x01); // reset
break;
}
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
@ -728,7 +885,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
chan[c.chan].std.release();
break;
case DIV_CMD_VOLUME: {
if (pretendYMU) {
if (pretendYMU && c.chan!=adpcmChan) {
c.value=pow(((double)c.value/127.0),0.5)*63.0;
if (c.value<0) c.value=0;
if (c.value>63) c.value=63;
@ -737,6 +894,10 @@ int DivPlatformOPL::dispatch(DivCommand c) {
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
}
if (c.chan==adpcmChan) { // ADPCM-B
immWrite(18,chan[c.chan].outVol);
break;
}
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
for (int i=0; i<ops; i++) {
unsigned char slot=slots[i][c.chan];
@ -757,7 +918,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_GET_VOLUME: {
if (pretendYMU) {
if (pretendYMU && c.chan!=adpcmChan) {
return pow(((double)chan[c.chan].vol/63.0),2.0)*127.0;
}
return chan[c.chan].vol;
@ -771,6 +932,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
case DIV_CMD_PANNING: {
if (oplType!=3) break;
if (c.chan==adpcmChan) break;
if (c.value==0 && c.value2==0) {
chan[c.chan].pan=3;
} else {
@ -791,28 +953,31 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_PITCH: {
if (c.chan==adpcmChan) {
if (!chan[c.chan].furnacePCM) break;
}
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
}
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
int destFreq=(c.chan==adpcmChan)?(NOTE_ADPCMB(c.value2)):(NOTE_FREQUENCY(c.value2));
int newFreq;
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
newFreq=chan[c.chan].baseFreq+c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq));
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
newFreq=chan[c.chan].baseFreq-c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq));
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;
}
}
if (!chan[c.chan].portaPause) {
if (!chan[c.chan].portaPause && parent->song.linearPitch!=2) {
if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) {
chan[c.chan].portaPause=true;
break;
@ -827,13 +992,22 @@ int DivPlatformOPL::dispatch(DivCommand c) {
}
break;
}
case DIV_CMD_SAMPLE_BANK:
if (adpcmChan<0) break;
sampleBank=c.value;
if (sampleBank>(int)(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].baseFreq=(c.chan==adpcmChan)?(NOTE_ADPCMB(c.value)):(NOTE_FREQUENCY(c.value));
chan[c.chan].note=c.value;
chan[c.chan].freqChanged=true;
break;
}
case DIV_CMD_FM_LFO: {
if (c.chan==adpcmChan) break;
if (c.value&2) {
dvb=c.value&1;
} else {
@ -843,6 +1017,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_FB: {
if (c.chan==adpcmChan) break;
chan[c.chan].state.fb=c.value&7;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (isMuted[c.chan]) {
@ -859,6 +1034,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_MULT: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value>=ops) break;
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value];
@ -870,6 +1046,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_TL: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value>=ops) break;
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value];
@ -889,6 +1066,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_AR: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -911,6 +1089,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_DR: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -933,6 +1112,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_SL: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -955,6 +1135,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_RR: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -977,6 +1158,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_AM: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -999,6 +1181,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_VIB: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -1021,6 +1204,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_SUS: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -1043,6 +1227,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_KSR: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -1065,6 +1250,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_WS: {
if (c.chan==adpcmChan) break;
if (oplType<2) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
@ -1088,6 +1274,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_RS: {
if (c.chan==adpcmChan) break;
if (oplType<2) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
@ -1143,12 +1330,14 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_HARD_RESET:
if (c.chan==adpcmChan) break;
chan[c.chan].hardReset=c.value;
break;
case DIV_ALWAYS_SET_VOLUME:
return 0;
break;
case DIV_CMD_GET_VOLMAX:
if (c.chan==adpcmChan) return 255;
if (pretendYMU) return 127;
return 63;
break;
@ -1237,7 +1426,7 @@ unsigned char* DivPlatformOPL::getRegisterPool() {
}
int DivPlatformOPL::getRegisterPoolSize() {
return 512;
return (oplType<3)?256:512;
}
void DivPlatformOPL::reset() {
@ -1273,6 +1462,21 @@ void DivPlatformOPL::reset() {
chan[i].outVol=0x3f;
}
if (adpcmChan>=0) {
chan[adpcmChan]=DivPlatformOPL::Channel();
chan[adpcmChan].std.setEngine(parent);
chan[adpcmChan].vol=0xff;
chan[adpcmChan].outVol=0xff;
adpcmB->reset();
// volume
immWrite(18,0xff);
// ADPCM limit
immWrite(20,0xff);
immWrite(19,0xff);
}
if (oplType<3) for (int i=0; i<melodicChans; i++) {
fm.channel[outChanMap[i]].muted=isMuted[i];
}
@ -1285,6 +1489,7 @@ void DivPlatformOPL::reset() {
lastBusy=60;
lfoValue=8;
drumState=0;
sampleBank=0;
drumVol[0]=0;
drumVol[1]=0;
@ -1349,8 +1554,9 @@ void DivPlatformOPL::setYMFM(bool use) {
void DivPlatformOPL::setOPLType(int type, bool drums) {
pretendYMU=false;
adpcmChan=-1;
switch (type) {
case 1: case 2:
case 1: case 2: case 8950:
slotsNonDrums=slotsOPL2;
slotsDrums=slotsOPL2Drums;
slots=drums?slotsDrums:slotsNonDrums;
@ -1360,6 +1566,9 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
chans=9;
melodicChans=drums?6:9;
totalChans=drums?11:9;
if (type==8950) {
adpcmChan=drums?11:9;
}
break;
case 3: case 759:
slotsNonDrums=slotsOPL3;
@ -1371,11 +1580,16 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
chans=18;
melodicChans=drums?15:18;
totalChans=drums?20:18;
if (type==759) pretendYMU=true;
if (type==759) {
pretendYMU=true;
adpcmChan=16;
}
break;
}
if (type==759) {
oplType=3;
} else if (type==8950) {
oplType=1;
} else {
oplType=type;
}
@ -1425,6 +1639,45 @@ void DivPlatformOPL::setFlags(unsigned int flags) {
}
}
const void* DivPlatformOPL::getSampleMem(int index) {
return (index==0 && adpcmChan>=0) ? adpcmBMem : NULL;
}
size_t DivPlatformOPL::getSampleMemCapacity(int index) {
return (index==0 && adpcmChan>=0) ? 262144 : 0;
}
size_t DivPlatformOPL::getSampleMemUsage(int index) {
return (index==0 && adpcmChan>=0) ? adpcmBMemLen : 0;
}
void DivPlatformOPL::renderSamples() {
if (adpcmChan<0) return;
memset(adpcmBMem,0,getSampleMemCapacity(0));
size_t memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
int paddedLen=(s->lengthB+255)&(~0xff);
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
memPos=(memPos+0xfffff)&0xf00000;
}
if (memPos>=getSampleMemCapacity(0)) {
logW("out of ADPCM memory for sample %d!",i);
break;
}
if (memPos+paddedLen>=getSampleMemCapacity(0)) {
memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(0)-memPos);
logW("out of ADPCM memory for sample %d!",i);
} else {
memcpy(adpcmBMem+memPos,s->dataB,paddedLen);
}
s->offB=memPos;
memPos+=paddedLen;
}
adpcmBMemLen=memPos+256;
}
int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
@ -1437,6 +1690,14 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f
}
setFlags(flags);
if (adpcmChan>=0) {
adpcmBMem=new unsigned char[getSampleMemCapacity(0)];
adpcmBMemLen=0;
iface.adpcmBMem=adpcmBMem;
iface.sampleBank=0;
adpcmB=new ymfm::adpcm_b_engine(iface,2);
}
reset();
return totalChans;
}
@ -1445,6 +1706,10 @@ void DivPlatformOPL::quit() {
for (int i=0; i<18; i++) {
delete oscBuf[i];
}
if (adpcmChan>=0) {
delete adpcmB;
delete[] adpcmBMem;
}
}
DivPlatformOPL::~DivPlatformOPL() {

View file

@ -23,6 +23,16 @@
#include "../macroInt.h"
#include <queue>
#include "../../../extern/opl/opl3.h"
#include "sound/ymfm/ymfm_adpcm.h"
class DivOPLAInterface: public ymfm::ymfm_interface {
public:
unsigned char* adpcmBMem;
int sampleBank;
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address);
void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data);
DivOPLAInterface(): adpcmBMem(NULL), sampleBank(0) {}
};
class DivPlatformOPL: public DivDispatch {
protected:
@ -30,8 +40,8 @@ class DivPlatformOPL: public DivDispatch {
DivInstrumentFM state;
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, note, ins;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, fourOp, hardReset;
int freq, baseFreq, pitch, pitch2, note, ins, sample;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnacePCM, inPorta, fourOp, hardReset;
int vol, outVol;
unsigned char pan;
void macroInit(DivInstrument* which) {
@ -47,13 +57,14 @@ class DivPlatformOPL: public DivDispatch {
pitch2(0),
note(0),
ins(-1),
sample(-1),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
portaPause(false),
furnaceDac(false),
furnacePCM(false),
inPorta(false),
fourOp(false),
hardReset(false),
@ -73,13 +84,18 @@ class DivPlatformOPL: public DivDispatch {
};
std::queue<QueuedWrite> writes;
opl3_chip fm;
unsigned char* adpcmBMem;
size_t adpcmBMemLen;
DivOPLAInterface iface;
ymfm::adpcm_b_engine* adpcmB;
const unsigned char** slotsNonDrums;
const unsigned char** slotsDrums;
const unsigned char** slots;
const unsigned short* chanMap;
const unsigned char* outChanMap;
double chipFreqBase;
int delay, oplType, chans, melodicChans, totalChans;
int delay, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank;
unsigned char lastBusy;
unsigned char drumState;
unsigned char drumVol[5];
@ -97,6 +113,7 @@ class DivPlatformOPL: public DivDispatch {
int octave(int freq);
int toFreq(int freq);
double NOTE_ADPCMB(int note);
friend void putDispatchChan(void*,int,int);
@ -127,6 +144,10 @@ class DivPlatformOPL: public DivDispatch {
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char* getEffectName(unsigned char effect);
const void* getSampleMem(int index);
size_t getSampleMemCapacity(int index);
size_t getSampleMemUsage(int index);
void renderSamples();
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformOPL();

View file

@ -0,0 +1,38 @@
/**
* 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 "sound/ymfm/ymfm.h"
#include "opl.h"
#include "../engine.h"
uint8_t DivOPLAInterface::ymfm_external_read(ymfm::access_class type, uint32_t address) {
switch (type) {
case ymfm::ACCESS_ADPCM_B:
if (adpcmBMem==NULL) {
return 0;
}
return adpcmBMem[address&0xffffff];
default:
return 0;
}
return 0;
}
void DivOPLAInterface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) {
}

View file

@ -300,7 +300,7 @@ void DivPlatformOPLL::tick(bool sysTick) {
for (int i=0; i<11; i++) {
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].freq>262143) chan[i].freq=262143;
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
chan[i].freqL=freqt&0xff;
@ -578,13 +578,13 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
int newFreq;
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
newFreq=chan[c.chan].baseFreq+c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq));
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
newFreq=chan[c.chan].baseFreq-c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq));
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;

View file

@ -227,7 +227,7 @@ void DivPlatformPCE::tick(bool sysTick) {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].furnaceDac) {
double off=1.0;
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
@ -270,7 +270,7 @@ int DivPlatformPCE::dispatch(DivCommand c) {
if (chan[c.chan].pcm) {
if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].dacSample=ins->amiga.initSample;
chan[c.chan].dacSample=ins->amiga.getSample(c.value);
if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);

View file

@ -223,7 +223,7 @@ void DivPlatformPCSpeaker::tick(bool sysTick) {
chan[i].freqChanged=true;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>65535) chan[i].freq=65535;
if (chan[i].keyOn) {

View file

@ -118,7 +118,7 @@ void DivPlatformPET::tick(bool sysTick) {
chan.freqChanged=true;
}
if (chan.freqChanged || chan.keyOn || chan.keyOff) {
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2);
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER);
if (chan.freq>257) chan.freq=257;
if (chan.freq<2) chan.freq=2;
rWrite(8,chan.freq-2);

View file

@ -24,7 +24,7 @@
#include <map>
#define CHIP_DIVIDER (1248*2)
#define QS_NOTE_FREQUENCY(x) parent->calcBaseFreq(440,0x1000,(x)-3,false)
#define QS_NOTE_FREQUENCY(x) parent->calcBaseFreq(440,4096,(x)-3,false)
#define rWrite(a,v) {if(!skipRegisterWrites) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v); }}
#define immWrite(a,v) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v);}
@ -296,14 +296,8 @@ void DivPlatformQSound::tick(bool sysTick) {
uint16_t qsound_addr = 0;
uint16_t qsound_loop = 0;
uint16_t qsound_end = 0;
double off=1.0;
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=(double)s->centerRate/24038.0/16.0;
}
qsound_bank = 0x8000 | (s->offQSound >> 16);
qsound_addr = s->offQSound & 0xffff;
@ -322,15 +316,15 @@ void DivPlatformQSound::tick(bool sysTick) {
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].std.arp.val);
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].std.arp.val);
} else {
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note);
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note);
chan[i].freqChanged=true;
}
}
@ -354,7 +348,16 @@ void DivPlatformQSound::tick(bool sysTick) {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
double off=1.0;
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=(double)s->centerRate/24038.0/16.0;
}
}
chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,440.0,4096.0);
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
if (chan[i].keyOn) {
rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank);
@ -387,18 +390,9 @@ int DivPlatformQSound::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].sample=ins->amiga.initSample;
double off=1.0;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=(double)s->centerRate/24038.0/16.0;
}
}
chan[c.chan].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value);
chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value);
}
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1;
@ -467,16 +461,7 @@ int DivPlatformQSound::dispatch(DivCommand c) {
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
double off=1.0;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=(double)s->centerRate/24038.0/16.0;
}
}
int destFreq=off*QS_NOTE_FREQUENCY(c.value2);
int destFreq=QS_NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
@ -499,16 +484,7 @@ int DivPlatformQSound::dispatch(DivCommand c) {
break;
}
case DIV_CMD_LEGATO: {
double off=1.0;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=(double)s->centerRate/24038.0/16.0;
}
}
chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;

View file

@ -200,7 +200,7 @@ void DivPlatformSAA1099::tick(bool sysTick) {
rWrite(0x18+(i/3),saaEnv[i/3]);
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].freq>65535) chan[i].freq=65535;
if (chan[i].freq>=32768) {
chan[i].freqH=7;
@ -311,13 +311,13 @@ int DivPlatformSAA1099::dispatch(DivCommand c) {
int destFreq=NOTE_PERIODIC(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value*(8-chan[c.chan].freqH);
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(8-chan[c.chan].freqH));
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value*(8-chan[c.chan].freqH);
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:(8-chan[c.chan].freqH));
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;

402
src/engine/platform/scc.cpp Normal file
View file

@ -0,0 +1,402 @@
/**
* 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 "scc.h"
#include "../engine.h"
#include <math.h>
#define CHIP_DIVIDER 16
#define rWrite(a,v) {if (!skipRegisterWrites) {scc->scc_w(true,a,v); regPool[a]=v; if (dumpWrites) addWrite(a,v); }}
const char* regCheatSheetSCC[]={
"Ch1_Wave", "00",
"Ch2_Wave", "20",
"Ch3_Wave", "40",
"Ch4_5_Wave", "60",
"Ch1_FreqL", "80",
"Ch1_FreqH", "81",
"Ch2_FreqL", "82",
"Ch2_FreqH", "83",
"Ch3_FreqL", "84",
"Ch3_FreqH", "85",
"Ch4_FreqL", "86",
"Ch4_FreqH", "87",
"Ch5_FreqL", "88",
"Ch5_FreqH", "89",
"Ch1_Vol", "8a",
"Ch2_Vol", "8b",
"Ch3_Vol", "8c",
"Ch4_Vol", "8d",
"Ch5_Vol", "8e",
"Output", "8f",
"Test", "e0",
NULL
};
const char* regCheatSheetSCCPlus[]={
"Ch1_Wave", "00",
"Ch2_Wave", "20",
"Ch3_Wave", "40",
"Ch4_Wave", "60",
"Ch5_Wave", "80",
"Ch1_FreqL", "a0",
"Ch1_FreqH", "a1",
"Ch2_FreqL", "a2",
"Ch2_FreqH", "a3",
"Ch3_FreqL", "a4",
"Ch3_FreqH", "a5",
"Ch4_FreqL", "a6",
"Ch4_FreqH", "a7",
"Ch5_FreqL", "a8",
"Ch5_FreqH", "a9",
"Ch1_Vol", "aa",
"Ch2_Vol", "ab",
"Ch3_Vol", "ac",
"Ch4_Vol", "ad",
"Ch5_Vol", "ae",
"Output", "af",
"Test", "c0",
NULL
};
const char** DivPlatformSCC::getRegisterSheet() {
return isPlus ? regCheatSheetSCCPlus : regCheatSheetSCC;
}
const char* DivPlatformSCC::getEffectName(unsigned char effect) {
switch (effect) {
case 0x10:
return "10xx: Change waveform";
break;
}
return NULL;
}
void DivPlatformSCC::acquire(short* bufL, short* bufR, size_t start, size_t len) {
for (size_t h=start; h<start+len; h++) {
for (int i=0; i<16; i++) {
scc->tick();
}
short out=(short)scc->out()<<5;
bufL[h]=bufR[h]=out;
for (int i=0; i<5; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=scc->chan_out(i)<<7;
}
}
}
void DivPlatformSCC::updateWave(int ch) {
int dstCh=(!isPlus && ch>=4)?3:ch;
if (ch==3) {
lastUpdated34=3;
} else if (ch==4) {
lastUpdated34=4;
}
for (int i=0; i<32; i++) {
rWrite(dstCh*32+i,(unsigned char)chan[ch].ws.output[i]-128);
}
}
void DivPlatformSCC::tick(bool sysTick) {
for (int i=0; i<5; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol.val))/15;
rWrite(regBase+10+i,chan[i].outVol);
}
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
} else {
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
chan[i].freqChanged=true;
}
}
if (chan[i].std.wave.had) {
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave.val;
chan[i].ws.changeWave1(chan[i].wave);
}
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-2048,2048);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].active) {
if (chan[i].ws.tick()) {
updateWave(i);
}
}
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>4095) chan[i].freq=4095;
rWrite(regBase+0+i*2,chan[i].freq&0xff);
rWrite(regBase+1+i*2,chan[i].freq>>8);
chan[i].freqChanged=false;
}
}
}
int DivPlatformSCC::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SCC);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].macroInit(ins);
if (!isMuted[c.chan]) {
rWrite(regBase+15,regPool[regBase+15]|(1<<c.chan));
}
if (chan[c.chan].wave<0) {
chan[c.chan].wave=0;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
chan[c.chan].ws.init(ins,32,255,chan[c.chan].insChanged);
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false;
chan[c.chan].macroInit(NULL);
rWrite(regBase+15,regPool[regBase+15]&~(1<<c.chan));
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.vol.has) {
chan[c.chan].outVol=c.value;
rWrite(regBase+10+c.chan,c.value);
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.vol.has) {
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;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_PERIODIC(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_LEGATO:
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SCC));
}
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 15;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformSCC::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (mute) {
rWrite(regBase+15,regPool[regBase+15]&~(1<<ch));
} else if (chan[ch].active) {
rWrite(regBase+15,regPool[regBase+15]|(1<<ch));
}
}
void DivPlatformSCC::forceIns() {
for (int i=0; i<5; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
if (isPlus || i<3) {
updateWave(i);
}
}
if (!isPlus) {
if (lastUpdated34>=3) {
updateWave(lastUpdated34);
}
}
}
void* DivPlatformSCC::getChanState(int ch) {
return &chan[ch];
}
DivDispatchOscBuffer* DivPlatformSCC::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformSCC::getRegisterPool() {
return (unsigned char*)regPool;
}
int DivPlatformSCC::getRegisterPoolSize() {
return 225;
}
void DivPlatformSCC::reset() {
memset(regPool,0,225);
scc->reset();
for (int i=0; i<5; i++) {
chan[i]=DivPlatformSCC::Channel();
chan[i].std.setEngine(parent);
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,32,255,false);
chan[i].vol=15;
chan[i].outVol=15;
rWrite(regBase+10+i,15);
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
lastUpdated34=0;
}
bool DivPlatformSCC::isStereo() {
return false;
}
void DivPlatformSCC::notifyWaveChange(int wave) {
for (int i=0; i<5; i++) {
if (chan[i].wave==wave) {
chan[i].ws.changeWave1(chan[i].wave);
if (chan[i].active) {
updateWave(i);
}
}
}
}
void DivPlatformSCC::notifyInsDeletion(void* ins) {
for (int i=0; i<5; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformSCC::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformSCC::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
void DivPlatformSCC::setChipModel(bool isplus) {
isPlus=isplus;
}
int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
writeOscBuf=0;
for (int i=0; i<5; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
chipClock=COLOR_NTSC/2.0;
rate=chipClock/8;
for (int i=0; i<5; i++) {
oscBuf[i]->rate=rate;
}
if (isPlus) {
scc=new k052539_scc_core;
regBase=0xa0;
} else {
scc=new k051649_scc_core;
regBase=0x80;
}
reset();
return 5;
}
void DivPlatformSCC::quit() {
for (int i=0; i<5; i++) {
delete oscBuf[i];
}
if (scc!=NULL) {
delete scc;
}
}
DivPlatformSCC::~DivPlatformSCC() {
}

92
src/engine/platform/scc.h Normal file
View file

@ -0,0 +1,92 @@
/**
* 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 _SCC_H
#define _SCC_H
#include "../dispatch.h"
#include <queue>
#include "../macroInt.h"
#include "../waveSynth.h"
#include "sound/scc/scc.hpp"
class DivPlatformSCC: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2, note, ins;
bool active, insChanged, freqChanged, inPorta;
signed char vol, outVol, wave;
signed char waveROM[32] = {0}; // 4 bit PROM per channel on bubble system
DivMacroInt std;
DivWaveSynth ws;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
Channel():
freq(0),
baseFreq(0),
pitch(0),
pitch2(0),
note(0),
ins(-1),
active(false),
insChanged(true),
freqChanged(false),
inPorta(false),
vol(15),
outVol(15),
wave(-1) {}
};
Channel chan[5];
DivDispatchOscBuffer* oscBuf[5];
bool isMuted[5];
unsigned char writeOscBuf;
int lastUpdated34;
scc_core* scc;
bool isPlus;
unsigned char regBase;
unsigned char regPool[225];
void updateWave(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);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool isStereo();
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 setChipModel(bool isPlus);
void quit();
~DivPlatformSCC();
};
#endif

View file

@ -162,7 +162,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].pcm.sample=ins->amiga.initSample;
chan[c.chan].pcm.sample=ins->amiga.getSample(c.value);
if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) {
chan[c.chan].pcm.sample=-1;
if (dumpWrites) {

View file

@ -121,7 +121,7 @@ void DivPlatformSMS::tick(bool sysTick) {
}
for (int i=0; i<3; i++) {
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,64);
if (chan[i].freq>1023) chan[i].freq=1023;
if (chan[i].freq<8) chan[i].freq=1;
//if (chan[i].actualNote>0x5d) chan[i].freq=0x01;
@ -136,7 +136,7 @@ void DivPlatformSMS::tick(bool sysTick) {
}
}
if (chan[3].freqChanged || updateSNMode) {
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2);
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,isRealSN?60:64);
if (chan[3].freq>1023) chan[3].freq=1023;
if (chan[3].actualNote>0x5d) chan[3].freq=0x01;
if (snNoiseMode&2) { // take period from channel 3

View file

@ -0,0 +1,543 @@
/*
This data is derived from the chip's output - internal ROM can't be read.
It was verified, using real YM2608, that this ADPCM stream produces 100% correct output signal.
*/
const unsigned char YM2608_ADPCM_ROM[0x2000] = {
/* Source: 01BD.ROM */
/* Length: 448 / 0x000001C0 */
0x88,0x08,0x08,0x08,0x00,0x88,0x16,0x76,0x99,0xB8,0x22,0x3A,0x84,0x3C,0xB1,0x54,
0x10,0xA9,0x98,0x32,0x80,0x33,0x9A,0xA7,0x4A,0xB4,0x58,0xBC,0x15,0x29,0x8A,0x97,
0x9B,0x44,0xAC,0x80,0x12,0xDE,0x13,0x1B,0xC0,0x58,0xC8,0x11,0x0A,0xA2,0x1A,0xA0,
0x00,0x98,0x0B,0x93,0x9E,0x92,0x0A,0x88,0xBE,0x14,0x1B,0x98,0x08,0xA1,0x4A,0xC1,
0x30,0xD9,0x33,0x98,0x10,0x89,0x17,0x1A,0x82,0x29,0x37,0x0C,0x83,0x50,0x9A,0x24,
0x1A,0x83,0x10,0x23,0x19,0xB3,0x72,0x8A,0x16,0x10,0x0A,0x93,0x70,0x99,0x23,0x99,
0x02,0x20,0x91,0x18,0x02,0x41,0xAB,0x24,0x18,0x81,0x99,0x4A,0xE8,0x28,0x9A,0x99,
0xA1,0x2F,0xA8,0x9D,0x90,0x08,0xCC,0xA3,0x1D,0xCA,0x82,0x0B,0xD8,0x08,0xB9,0x09,
0xBC,0xB8,0x00,0xBE,0x90,0x1B,0xCA,0x00,0x9B,0x8A,0xA8,0x91,0x0F,0xB3,0x3D,0xB8,
0x31,0x0B,0xA5,0x0A,0x11,0xA1,0x48,0x92,0x10,0x50,0x91,0x30,0x23,0x09,0x37,0x39,
0xA2,0x72,0x89,0x92,0x30,0x83,0x1C,0x96,0x28,0xB9,0x24,0x8C,0xA1,0x31,0xAD,0xA9,
0x13,0x9C,0xBA,0xA8,0x0B,0xBF,0xB8,0x9B,0xCA,0x88,0xDB,0xB8,0x19,0xFC,0x92,0x0A,
0xBA,0x89,0xAB,0xB8,0xAB,0xD8,0x08,0xAD,0xBA,0x33,0x9D,0xAA,0x83,0x3A,0xC0,0x40,
0xB9,0x15,0x39,0xA2,0x52,0x89,0x02,0x63,0x88,0x13,0x23,0x03,0x52,0x02,0x54,0x00,
0x11,0x23,0x23,0x35,0x20,0x01,0x44,0x41,0x80,0x24,0x40,0xA9,0x45,0x19,0x81,0x12,
0x81,0x02,0x11,0x21,0x19,0x02,0x61,0x8A,0x13,0x3A,0x10,0x12,0x23,0x8B,0x37,0x18,
0x91,0x24,0x10,0x81,0x34,0x20,0x05,0x32,0x82,0x53,0x20,0x14,0x33,0x31,0x34,0x52,
0x00,0x43,0x32,0x13,0x52,0x22,0x13,0x52,0x11,0x43,0x11,0x32,0x32,0x32,0x22,0x02,
0x13,0x12,0x89,0x22,0x19,0x81,0x81,0x08,0xA8,0x08,0x8B,0x90,0x1B,0xBA,0x8A,0x9B,
0xB9,0x89,0xCA,0xB9,0xAB,0xCA,0x9B,0xCA,0xB9,0xAB,0xDA,0x99,0xAC,0xBB,0x9B,0xAC,
0xAA,0xBA,0xAC,0xAB,0x9A,0xAA,0xAA,0xBA,0xB8,0xA9,0xBA,0x99,0xA9,0x9A,0xA0,0x8A,
0xA9,0x08,0x8A,0xA9,0x00,0x99,0x89,0x88,0x98,0x08,0x99,0x00,0x89,0x80,0x08,0x98,
0x00,0x88,0x88,0x80,0x90,0x80,0x90,0x80,0x81,0x99,0x08,0x88,0x99,0x09,0x00,0x1A,
0xA8,0x10,0x9A,0x88,0x08,0x0A,0x8A,0x89,0x99,0xA8,0x98,0xA9,0x99,0x99,0xA9,0x99,
0xAA,0x8A,0xAA,0x9B,0x8A,0x9A,0xA9,0x9A,0xBA,0x99,0x9A,0xAA,0x99,0x89,0xA9,0x99,
0x98,0x9A,0x98,0x88,0x09,0x89,0x09,0x08,0x08,0x09,0x18,0x18,0x00,0x12,0x00,0x11,
0x11,0x11,0x12,0x12,0x21,0x21,0x22,0x22,0x22,0x22,0x22,0x22,0x32,0x31,0x32,0x31,
0x32,0x32,0x21,0x31,0x21,0x32,0x21,0x12,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
/* Source: 02SD.ROM */
/* Length: 640 / 0x00000280 */
0x0A,0xDC,0x14,0x0B,0xBA,0xBC,0x01,0x0F,0xF5,0x2F,0x87,0x19,0xC9,0x24,0x1B,0xA1,
0x31,0x99,0x90,0x32,0x32,0xFE,0x83,0x48,0xA8,0xA9,0x23,0x19,0xBC,0x91,0x02,0x41,
0xDE,0x81,0x28,0xA8,0x0A,0xB1,0x72,0xDA,0x23,0xBC,0x04,0x19,0xB8,0x21,0x8A,0x03,
0x29,0xBA,0x14,0x21,0x0B,0xC0,0x43,0x08,0x91,0x50,0x93,0x0F,0x86,0x1A,0x91,0x18,
0x21,0xCB,0x27,0x0A,0xA1,0x42,0x8C,0xA9,0x21,0x10,0x08,0xAB,0x94,0x2A,0xDA,0x02,
0x8B,0x91,0x09,0x98,0xAE,0x80,0xA9,0x02,0x0A,0xE9,0x21,0xBB,0x15,0x20,0xBE,0x92,
0x42,0x09,0xA9,0x11,0x34,0x08,0x12,0x0A,0x27,0x29,0xA1,0x52,0x12,0x8E,0x92,0x28,
0x92,0x2B,0xD1,0x23,0xBF,0x81,0x10,0x99,0xA8,0x0A,0xC4,0x3B,0xB9,0xB0,0x00,0x62,
0xCF,0x92,0x29,0x92,0x2B,0xB1,0x1C,0xB2,0x72,0xAA,0x88,0x11,0x18,0x80,0x13,0x9E,
0x03,0x18,0xB0,0x60,0xA1,0x28,0x88,0x08,0x04,0x10,0x8F,0x96,0x19,0x90,0x01,0x09,
0xC8,0x50,0x91,0x8A,0x01,0xAB,0x03,0x50,0xBA,0x9D,0x93,0x68,0xBA,0x80,0x22,0xCB,
0x41,0xBC,0x92,0x60,0xB9,0x1A,0x95,0x4A,0xC8,0x20,0x88,0x33,0xAC,0x92,0x38,0x83,
0x09,0x80,0x16,0x09,0x29,0xD0,0x54,0x8C,0xA2,0x28,0x91,0x89,0x93,0x60,0xCD,0x85,
0x1B,0xA1,0x49,0x90,0x8A,0x80,0x34,0x0C,0xC9,0x14,0x19,0x98,0xA0,0x40,0xA9,0x21,
0xD9,0x34,0x0A,0xA9,0x10,0x23,0xCB,0x25,0xAA,0x25,0x9B,0x13,0xCD,0x16,0x09,0xA0,
0x80,0x01,0x19,0x90,0x88,0x21,0xAC,0x33,0x8B,0xD8,0x27,0x3B,0xB8,0x81,0x31,0x80,
0xAF,0x97,0x0A,0x82,0x0A,0xA0,0x21,0x89,0x8A,0xA2,0x32,0x8D,0xBB,0x87,0x19,0x21,
0xC9,0xBC,0x45,0x09,0x90,0x09,0xA1,0x24,0x1A,0xD0,0x10,0x08,0x11,0xA9,0x21,0xE8,
0x60,0xA9,0x14,0x0C,0xD1,0x32,0xAB,0x04,0x0C,0x81,0x90,0x29,0x83,0x9B,0x01,0x8F,
0x97,0x0B,0x82,0x18,0x88,0xBA,0x06,0x39,0xC8,0x23,0xBC,0x04,0x09,0x92,0x08,0x1A,
0xBB,0x74,0x8C,0x81,0x18,0x81,0x9D,0x83,0x41,0xCD,0x81,0x40,0x9A,0x90,0x10,0x12,
0x9C,0xA1,0x68,0xD8,0x33,0x9C,0x91,0x01,0x12,0xBE,0x02,0x09,0x12,0x99,0x9A,0x36,
0x0A,0xB0,0x30,0x88,0xA3,0x2D,0x12,0xBC,0x03,0x3A,0x11,0xBD,0x08,0xC8,0x62,0x80,
0x8B,0xD8,0x23,0x38,0xF9,0x12,0x08,0x99,0x91,0x21,0x99,0x85,0x2F,0xB2,0x30,0x90,
0x88,0xD9,0x53,0xAC,0x82,0x19,0x91,0x20,0xCC,0x96,0x29,0xC9,0x24,0x89,0x80,0x99,
0x12,0x08,0x18,0x88,0x99,0x23,0xAB,0x73,0xCB,0x33,0x9F,0x04,0x2B,0xB1,0x08,0x03,
0x1B,0xC9,0x21,0x32,0xFA,0x33,0xDB,0x02,0x33,0xAE,0xB9,0x54,0x8B,0xA1,0x20,0x89,
0x90,0x11,0x88,0x09,0x98,0x23,0xBE,0x37,0x8D,0x81,0x20,0xAA,0x34,0xBB,0x13,0x18,
0xB9,0x40,0xB1,0x18,0x83,0x8E,0xB2,0x72,0xBC,0x82,0x30,0xA9,0x9A,0x24,0x8B,0x27,
0x0E,0x91,0x20,0x90,0x08,0xB0,0x32,0xB9,0x21,0xB0,0xAC,0x45,0x9A,0xA1,0x50,0xA9,
0x80,0x0A,0x26,0x9B,0x11,0xBB,0x23,0x71,0xCB,0x12,0x10,0xB8,0x40,0xA9,0xA5,0x39,
0xC0,0x30,0xB2,0x20,0xAA,0xBA,0x76,0x1C,0xC1,0x48,0x98,0x80,0x18,0x81,0xAA,0x23,
0x9C,0xA2,0x32,0xAC,0x9A,0x43,0x9C,0x12,0xAD,0x82,0x72,0xBC,0x00,0x82,0x39,0xD1,
0x3A,0xB8,0x35,0x9B,0x10,0x40,0xF9,0x22,0x0A,0xC0,0x51,0xB9,0x82,0x18,0x98,0xA3,
0x79,0xD0,0x20,0x88,0x09,0x01,0x99,0x82,0x11,0x38,0xFC,0x33,0x09,0xC8,0x40,0xA9,
0x11,0x29,0xAA,0x94,0x3A,0xC2,0x4A,0xC0,0x89,0x52,0xBC,0x11,0x08,0x09,0xB8,0x71,
0xA9,0x08,0xA8,0x62,0x8D,0x92,0x10,0x00,0x9E,0x94,0x38,0xBA,0x13,0x88,0x90,0x4A,
0xE2,0x30,0xBA,0x02,0x00,0x19,0xD9,0x62,0xBB,0x04,0x0B,0xA3,0x68,0xB9,0x21,0x88,
0x9D,0x04,0x10,0x8C,0xC8,0x62,0x99,0xAA,0x24,0x1A,0x80,0x9A,0x14,0x9B,0x26,0x8C,
0x92,0x30,0xB9,0x09,0xA3,0x71,0xBB,0x10,0x19,0x82,0x39,0xDB,0x02,0x44,0x9F,0x10,
/* Source: 04TOP.ROM */
/* Length: 5952 / 0x00001740 */
0x07,0xFF,0x7C,0x3C,0x31,0xC6,0xC4,0xBB,0x7F,0x7F,0x7B,0x82,0x8A,0x4D,0x5F,0x7C,
0x3E,0x44,0xD2,0xB3,0xA0,0x19,0x1B,0x6C,0x81,0x28,0xC4,0xA1,0x1C,0x4B,0x18,0x00,
0x2A,0xA2,0x0A,0x7C,0x2A,0x00,0x01,0x89,0x98,0x48,0x8A,0x3C,0x28,0x2A,0x5B,0x3E,
0x3A,0x1A,0x3B,0x3D,0x4B,0x3B,0x4A,0x08,0x2A,0x1A,0x2C,0x4A,0x3B,0x82,0x99,0x3C,
0x5D,0x29,0x2B,0x39,0x0B,0x23,0xAB,0x1A,0x4C,0x79,0xA3,0x01,0xC1,0x2A,0x0A,0x38,
0xA7,0xB9,0x12,0x1F,0x29,0x08,0x82,0xA1,0x08,0xA9,0x42,0xAA,0x95,0xB3,0x90,0x81,
0x09,0xD4,0x1A,0x80,0x1B,0x07,0xB8,0x12,0x8E,0x49,0x81,0x92,0xD3,0x90,0xA1,0x2A,
0x02,0xE1,0xA3,0x99,0x02,0xB3,0x94,0xB3,0xB0,0xF4,0x98,0x93,0x90,0x13,0xE1,0x81,
0x99,0x38,0x91,0xA6,0xD3,0x99,0x94,0xC1,0x83,0xB1,0x92,0x98,0x49,0xC4,0xB2,0xA4,
0xA3,0xD0,0x1A,0x30,0xBA,0x59,0x02,0xD4,0xA0,0xA4,0xA2,0x8A,0x01,0x00,0xB7,0xA8,
0x18,0x2A,0x2B,0x1E,0x23,0xC8,0x1A,0x00,0x39,0xA0,0x18,0x92,0x4F,0x2D,0x5A,0x10,
0x89,0x81,0x2A,0x8B,0x6A,0x02,0x09,0xB3,0x8D,0x48,0x1B,0x80,0x19,0x34,0xF8,0x29,
0x0A,0x7B,0x2A,0x28,0x81,0x0C,0x02,0x1E,0x29,0x09,0x12,0xC2,0x94,0xE1,0x18,0x98,
0x02,0xC4,0x89,0x91,0x1A,0x20,0xA9,0x02,0x1B,0x48,0x8E,0x20,0x88,0x2D,0x08,0x59,
0x1B,0x02,0xA3,0xB1,0x8A,0x1E,0x58,0x80,0xC2,0xB6,0x88,0x91,0x88,0x11,0xA1,0xA3,
0xE2,0x01,0xB0,0x19,0x11,0x09,0xF4,0x88,0x09,0x88,0x19,0x89,0x12,0xF1,0x2A,0x28,
0x8C,0x25,0x99,0xA4,0x98,0x39,0xA1,0x00,0xD0,0x58,0xAA,0x59,0x01,0x0C,0x00,0x2B,
0x00,0x08,0x89,0x6B,0x69,0x90,0x01,0x90,0x98,0x12,0xB3,0xF3,0xA0,0x89,0x02,0x3B,
0x0C,0x50,0xA9,0x4E,0x6B,0x19,0x28,0x09,0xA2,0x08,0x2F,0x20,0x88,0x92,0x8A,0x11,
0xC4,0x93,0xF1,0x18,0x88,0x11,0xF2,0x80,0x92,0xA8,0x02,0xA8,0xB7,0xB3,0xA3,0xA0,
0x88,0x1A,0x40,0xE2,0x91,0x19,0x88,0x18,0x91,0x83,0xC1,0xB5,0x92,0xA9,0xC6,0x90,
0x01,0xC2,0x81,0x98,0x03,0xF0,0x00,0x2C,0x2A,0x92,0x2C,0x83,0x1F,0x3A,0x29,0x00,
0xB8,0x70,0xAB,0x69,0x18,0x89,0x10,0x0D,0x12,0x0B,0x88,0x4A,0x3A,0x9B,0x70,0xA8,
0x28,0x2F,0x2A,0x3A,0x1B,0x85,0x88,0x8B,0x6A,0x29,0x00,0x91,0x91,0x1B,0x7C,0x29,
0x01,0x88,0x90,0x19,0x2B,0x2B,0x00,0x39,0xA8,0x5E,0x21,0x89,0x91,0x09,0x3A,0x6F,
0x2A,0x18,0x18,0x8B,0x50,0x89,0x2B,0x19,0x49,0x88,0x29,0xF5,0x89,0x08,0x09,0x12,
0xAA,0x15,0xB0,0x82,0xAC,0x38,0x00,0x3F,0x81,0x10,0xB0,0x49,0xA2,0x81,0x3A,0xC8,
0x87,0x90,0xC4,0xA3,0x99,0x19,0x83,0xE1,0x84,0xE2,0xA2,0x90,0x80,0x93,0xB5,0xC4,
0xB3,0xA1,0x0A,0x18,0x92,0xC4,0xA0,0x93,0x0C,0x3A,0x18,0x01,0x1E,0x20,0xB1,0x82,
0x8C,0x03,0xB5,0x2E,0x82,0x19,0xB2,0x1B,0x1B,0x6B,0x4C,0x19,0x12,0x8B,0x5A,0x11,
0x0C,0x3A,0x2C,0x18,0x3D,0x08,0x2A,0x5C,0x18,0x00,0x88,0x3D,0x29,0x80,0x2A,0x09,
0x00,0x7A,0x0A,0x10,0x0B,0x69,0x98,0x10,0x81,0x3F,0x00,0x18,0x19,0x91,0xB7,0x9A,
0x28,0x8A,0x48,0x92,0xF3,0xA2,0x88,0x98,0x87,0xA1,0x88,0x80,0x81,0x95,0xD1,0xA3,
0x1B,0x1C,0x39,0x10,0xA1,0x2A,0x0B,0x7A,0x4B,0x80,0x13,0xC1,0xD1,0x2B,0x2A,0x85,
0xB2,0xA2,0x93,0xB2,0xD3,0x80,0xD1,0x18,0x08,0x08,0xB7,0x98,0x81,0x3F,0x01,0x88,
0x01,0xE2,0x00,0x9A,0x59,0x08,0x10,0xC3,0x99,0x84,0xA9,0xA5,0x91,0x91,0x91,0x80,
0xB5,0x94,0xC0,0x01,0x98,0x09,0x84,0xB0,0x80,0x7A,0x08,0x18,0x90,0xA8,0x6A,0x1C,
0x39,0x2A,0xB7,0x98,0x19,0x10,0x2A,0xA1,0x10,0xBD,0x39,0x18,0x2D,0x39,0x3F,0x10,
0x3F,0x01,0x09,0x19,0x0A,0x38,0x8C,0x40,0xB3,0xB4,0x93,0xAD,0x20,0x2B,0xD4,0x81,
0xC3,0xB0,0x39,0xA0,0x23,0xD8,0x04,0xB1,0x9B,0xA7,0x1A,0x92,0x08,0xA5,0x88,0x81,
0xE2,0x01,0xB8,0x01,0x81,0xC1,0xC7,0x90,0x92,0x80,0xA1,0x97,0xA0,0xA2,0x82,0xB8,
0x18,0x00,0x9C,0x78,0x98,0x83,0x0B,0x0B,0x32,0x7D,0x19,0x10,0xA1,0x19,0x09,0x0A,
0x78,0xA8,0x10,0x1B,0x29,0x29,0x1A,0x14,0x2F,0x88,0x4A,0x1B,0x10,0x10,0xAB,0x79,
0x0D,0x49,0x18,0xA0,0x02,0x1F,0x19,0x3A,0x2B,0x11,0x8A,0x88,0x79,0x8A,0x20,0x49,
0x9B,0x58,0x0B,0x28,0x18,0xA9,0x3A,0x7D,0x00,0x29,0x88,0x82,0x3D,0x1A,0x38,0xBA,
0x15,0x09,0xAA,0x51,0x8B,0x83,0x3C,0x8A,0x58,0x1B,0xB5,0x01,0xBB,0x50,0x19,0x99,
0x24,0xCA,0x21,0x1B,0xA2,0x87,0xA8,0xB1,0x68,0xA1,0xA6,0xA2,0xA8,0x29,0x8B,0x24,
0xB4,0xE2,0x92,0x8A,0x00,0x19,0x93,0xB5,0xB4,0xB1,0x81,0xB1,0x03,0x9A,0x82,0xA7,
0x90,0xD6,0xA0,0x80,0x1B,0x29,0x01,0xA4,0xE1,0x18,0x0A,0x2A,0x29,0x92,0xC7,0xA8,
0x81,0x19,0x89,0x30,0x10,0xE0,0x30,0xB8,0x10,0x0C,0x1A,0x79,0x1B,0xA7,0x80,0xA0,
0x00,0x0B,0x28,0x18,0xB1,0x85,0x1E,0x00,0x20,0xA9,0x18,0x18,0x1C,0x13,0xBC,0x15,
0x99,0x2E,0x12,0x00,0xE1,0x00,0x0B,0x3B,0x21,0x90,0x06,0xC9,0x2A,0x49,0x0A,0x18,
0x20,0xD1,0x3C,0x08,0x00,0x83,0xC9,0x41,0x8E,0x18,0x08,0x02,0xA0,0x09,0xA4,0x7B,
0x90,0x19,0x2A,0x10,0x2A,0xA8,0x71,0xBA,0x10,0x4A,0x0E,0x22,0xB2,0xB2,0x1B,0x8C,
0x78,0x1A,0xB5,0x93,0xA9,0x1B,0x49,0x19,0x29,0xA3,0xC6,0x88,0xAA,0x32,0x0D,0x1B,
0x22,0x08,0xC2,0x18,0xB9,0x79,0x3F,0x01,0x10,0xA9,0x84,0x1C,0x09,0x21,0xB0,0xA7,
0x0A,0x99,0x50,0x0C,0x81,0x28,0x8B,0x48,0x2E,0x00,0x08,0x99,0x38,0x5B,0x88,0x14,
0xA9,0x08,0x11,0xAA,0x72,0xC1,0xB3,0x09,0x8A,0x05,0x91,0xF2,0x81,0xA1,0x09,0x02,
0xF2,0x92,0x99,0x1A,0x49,0x80,0xC5,0x90,0x90,0x18,0x09,0x12,0xA1,0xF2,0x81,0x98,
0xC6,0x91,0xA0,0x11,0xA0,0x94,0xB4,0xF2,0x81,0x8B,0x03,0x80,0xD2,0x93,0xA8,0x88,
0x69,0xA0,0x03,0xB8,0x88,0x32,0xBC,0x97,0x80,0xB1,0x3B,0x1A,0xA6,0x00,0xD1,0x01,
0x0B,0x3B,0x30,0x9B,0x31,0x3E,0x92,0x19,0x8A,0xD3,0x5C,0x1B,0x41,0xA0,0x93,0xA2,
0xAF,0x39,0x4C,0x01,0x92,0xA8,0x81,0x3C,0x0D,0x78,0x98,0x00,0x19,0x0A,0x20,0x2D,
0x29,0x3C,0x1B,0x48,0x88,0x99,0x7A,0x2D,0x29,0x2A,0x82,0x80,0xA8,0x49,0x3E,0x19,
0x11,0x98,0x82,0x9A,0x3B,0x28,0x2F,0x20,0x4C,0x90,0x29,0x19,0x9A,0x7A,0x29,0x28,
0x98,0x88,0x33,0xCD,0x11,0x3A,0xC1,0xA4,0xA0,0xC4,0x82,0xC8,0x50,0x98,0xB2,0x21,
0xC0,0xB6,0x98,0x82,0x80,0x9C,0x23,0x00,0xF8,0x30,0xA8,0x1A,0x68,0xA8,0x86,0x9A,
0x01,0x2A,0x0A,0x97,0x91,0xC1,0x18,0x89,0x02,0x83,0xE0,0x01,0x8B,0x29,0x30,0xE2,
0x91,0x0B,0x18,0x3B,0x1C,0x11,0x28,0xAC,0x78,0x80,0x93,0x91,0xA9,0x49,0x8B,0x87,
0x90,0x99,0x3D,0x5A,0x81,0x08,0xA1,0x11,0x2F,0x1A,0x21,0x9B,0x15,0xA2,0xB0,0x11,
0xC0,0x91,0x5B,0x98,0x24,0xA2,0xF2,0x92,0x8B,0x6A,0x18,0x81,0xB5,0xB1,0x88,0x4C,
0x00,0x00,0xA4,0xC1,0x2B,0x1A,0x59,0x0A,0x02,0x80,0x1E,0x02,0x08,0xB3,0x80,0x9A,
0x23,0xB8,0xF2,0x84,0xAB,0x01,0x48,0x90,0xA7,0x90,0x0A,0x29,0x09,0x95,0x99,0xA0,
0x59,0x2B,0x00,0x97,0xB0,0x29,0x89,0x2A,0x03,0xD0,0xB7,0x1B,0x81,0x00,0xA6,0xB1,
0x90,0x09,0x48,0xC0,0x11,0x00,0x8A,0x00,0x5B,0x83,0x9A,0x18,0x2F,0x3C,0x18,0x11,
0xA9,0x04,0x1A,0x4F,0x01,0x98,0x81,0x09,0x09,0x4A,0x18,0xB4,0xA2,0x0B,0x59,0x90,
0x3B,0x49,0xBC,0x40,0x6A,0x88,0x3A,0x08,0x3E,0x3A,0x80,0x93,0xB0,0xE1,0x5A,0x00,
0xA4,0xB3,0xE3,0x90,0x0D,0x38,0x09,0x82,0xC4,0xA1,0xB1,0x4C,0x18,0x10,0x91,0xB2,
0x13,0xEA,0x34,0x99,0x88,0xA6,0x89,0x92,0x91,0xC1,0x20,0xB2,0xC2,0x86,0xD2,0xB3,
0x80,0xB2,0x08,0x09,0x87,0x91,0xC0,0x11,0x89,0x90,0x28,0xB9,0x79,0x19,0xA4,0x82,
0xD0,0x03,0x0C,0xA3,0xA5,0xB2,0xB2,0x1B,0x29,0x13,0xF1,0xB4,0x81,0x9D,0x38,0x00,
0xC4,0xA1,0x89,0x59,0x1A,0x81,0xA4,0xA9,0x1C,0x6A,0x19,0x02,0xB1,0x1A,0x4A,0x0B,
0x78,0x89,0x81,0x1C,0x2A,0x29,0x4A,0xA3,0x3E,0x1C,0x49,0x1A,0x08,0x21,0xAE,0x28,
0x4B,0x19,0x20,0x8C,0x10,0x3A,0xAB,0x26,0x8B,0x18,0x59,0x99,0x13,0xA2,0xAB,0x79,
0x2F,0x18,0x10,0xB2,0x80,0x1B,0x4D,0x5A,0x80,0x82,0x98,0x81,0x80,0x09,0xA5,0x90,
0x91,0x03,0xC2,0xE2,0x81,0xA8,0x82,0x09,0xC6,0xA3,0xB1,0x08,0x5B,0x08,0x05,0xD1,
0xA2,0x89,0x2A,0x28,0x91,0xA6,0x88,0xB0,0x49,0x80,0x09,0x08,0x88,0x07,0xB8,0x05,
0x99,0x81,0x88,0x18,0xE2,0x00,0xC3,0x18,0x0D,0x10,0x30,0xD0,0x93,0x8A,0x09,0x10,
0x2F,0x11,0x90,0xA1,0x20,0x9B,0xB1,0x73,0xC8,0x94,0x98,0x3B,0x01,0x0C,0x30,0x19,
0xF8,0x12,0x90,0xBA,0x78,0x0A,0x11,0x98,0xA0,0x79,0x8A,0x30,0x2B,0xC2,0x11,0x0D,
0x09,0x7A,0x00,0x82,0xB9,0x01,0x7A,0x89,0x21,0x09,0xA1,0x0A,0x7C,0x10,0x88,0xB5,
0x88,0x0A,0x2B,0x69,0x1A,0x10,0xA0,0x5B,0x19,0x1A,0x10,0x19,0x1A,0x6C,0x20,0x90,
0xA5,0x98,0x1B,0x0A,0x69,0x82,0xD1,0x18,0x09,0x19,0x2A,0x93,0xD4,0x9A,0x01,0x49,
0xA2,0xA2,0x82,0xD8,0x22,0xAA,0x97,0xA9,0x2D,0x38,0x2A,0xB6,0x80,0x90,0x0A,0x3C,
0x82,0x94,0xB8,0x21,0x0E,0x2A,0x22,0xB8,0x00,0x4F,0x2B,0x3A,0x81,0xA1,0x29,0x2C,
0x6A,0x13,0xD1,0xA2,0x98,0x28,0x0C,0x01,0xD5,0x08,0xA9,0x31,0xB3,0xB0,0xA7,0xB0,
0x29,0x1B,0x87,0xA2,0xA1,0xB2,0x4A,0x89,0x11,0xC3,0xF3,0x98,0x08,0x03,0xA0,0xA3,
0xC5,0x90,0xB3,0xB5,0xB4,0xB8,0x02,0x91,0x91,0xD3,0xA4,0xC1,0x1B,0x82,0x28,0xA4,
0xD1,0x94,0x8A,0x28,0x08,0x03,0xE0,0x80,0xD4,0x90,0x91,0xA1,0x3B,0x3D,0x02,0xE4,
0xA1,0x92,0x89,0x1A,0x4B,0x95,0xB3,0x90,0x99,0x6A,0x0A,0x30,0xA1,0x93,0xA6,0xA9,
0x85,0x8B,0x82,0x10,0xB1,0xA3,0x94,0xF8,0x38,0x9A,0x30,0x1A,0x8B,0xA7,0x89,0x01,
0x5B,0x19,0x18,0x11,0xF0,0x18,0x1C,0x39,0x19,0x0C,0x12,0x1C,0x2A,0x7B,0x3A,0x88,
0x2B,0x18,0x2B,0x5C,0x20,0x92,0x8D,0x38,0x8A,0x3A,0x5B,0x2E,0x3A,0x2B,0x10,0x12,
0xBB,0x6A,0x4D,0x18,0x10,0xB1,0x81,0x2A,0x8B,0x79,0x80,0x01,0x0A,0x09,0x5B,0x2D,
0x84,0x8A,0x08,0x02,0xA2,0x91,0x82,0xE8,0x50,0x9B,0x85,0xA3,0xB0,0xA3,0x1B,0x02,
0x18,0xF3,0xA2,0x88,0xAB,0x53,0xD1,0xB4,0xA3,0x09,0x09,0x18,0xD4,0x08,0xB0,0x09,
0x58,0xD1,0x82,0x89,0x81,0x1A,0x18,0x05,0xB9,0xC3,0x30,0xC0,0x95,0x80,0xC3,0x89,
0x89,0x13,0x88,0xF2,0x93,0x0E,0x18,0x01,0x92,0xA5,0xB8,0x2A,0x39,0xAA,0x33,0x9A,
0xB1,0x11,0xF5,0xA1,0xA1,0x0A,0x50,0xB8,0x03,0xC4,0xA0,0x4E,0x29,0x10,0x88,0xC2,
0x1A,0x39,0x1D,0x28,0x98,0x94,0x0E,0x10,0x2A,0x3C,0x02,0x2D,0x1B,0x4B,0x3B,0x49,
0x19,0xA9,0x48,0x2F,0x29,0x10,0x89,0x02,0x0C,0x10,0x09,0xB9,0x70,0x1B,0x8A,0x50,
0xA8,0x2B,0x49,0x89,0x69,0x88,0x95,0x89,0x90,0x92,0x4C,0x19,0x82,0xC1,0x01,0x80,
0xA0,0x2B,0x7A,0x81,0x10,0xC2,0xB7,0x98,0x88,0x19,0x2C,0x03,0xB1,0xA4,0xA1,0x0C,
0x3B,0x78,0x88,0x85,0xB1,0xA0,0x1B,0x3A,0x4A,0x08,0x94,0x81,0xF1,0x80,0x00,0x0C,
0x59,0x09,0x18,0x90,0xA6,0x92,0x8C,0x1A,0x79,0x92,0xA8,0x00,0x81,0x2E,0x2A,0x13,
0xA2,0xB0,0xA5,0x88,0x88,0x89,0x11,0x19,0xA0,0xF3,0x82,0xB0,0x83,0x5F,0x2A,0x01,
0xA1,0x94,0xB0,0x09,0x78,0x98,0xA3,0xA6,0xA0,0x91,0x80,0x93,0x98,0xC1,0x12,0x18,
0xC9,0x17,0xA0,0xA0,0x1A,0x21,0x80,0x99,0xD4,0x30,0x9D,0x00,0x10,0x2F,0x08,0x1C,
0x21,0x08,0xB4,0xC3,0x2B,0xA9,0x52,0xD2,0xA3,0xD1,0x09,0x10,0x8B,0x24,0x92,0xD1,
0x80,0x19,0xA0,0x2C,0x12,0x49,0xAA,0xB6,0x95,0xB8,0x08,0x3A,0x2B,0x01,0xF3,0xB3,
0x0B,0x09,0x79,0x18,0xA2,0xA4,0xA0,0x18,0x0C,0x20,0x08,0xA9,0x16,0x0C,0x00,0x1B,
0x08,0x2B,0x7B,0x01,0x01,0xB9,0x59,0x19,0x8B,0x45,0xA8,0x80,0x0C,0x1A,0x41,0x1E,
0x00,0x28,0xA8,0x5A,0x00,0xC1,0x49,0x99,0x21,0x1D,0x08,0x85,0x99,0x95,0x89,0x90,
0x11,0x90,0xD1,0x28,0xB2,0xA7,0x99,0x81,0x02,0xAC,0x13,0x81,0xB2,0xA6,0xA9,0x28,
0x1C,0xB1,0x33,0xD1,0xC1,0x58,0xA8,0x14,0xB0,0xB7,0x91,0xA0,0x82,0x89,0xC2,0x28,
0xA1,0xB2,0x49,0xD2,0x94,0xC8,0x12,0x80,0x99,0x85,0x08,0xD3,0x09,0xA2,0xB3,0x1E,
0x08,0x21,0xB9,0x23,0xB4,0xAB,0x41,0xAC,0x87,0x09,0xA2,0xC5,0x0B,0x2A,0x5A,0x91,
0x20,0x9A,0x89,0x78,0x9B,0x31,0x89,0x80,0x29,0x0A,0xB7,0x3C,0x98,0x48,0x1D,0x00,
0x01,0xB0,0x20,0x2F,0x29,0x4A,0x89,0x94,0x1C,0x88,0x28,0x2B,0x10,0x88,0x9A,0x71,
0x9A,0x08,0x4A,0x2F,0x18,0x2B,0x18,0x02,0xA8,0x4B,0x7A,0x99,0x48,0x80,0xA8,0x20,
0x1D,0x40,0xA8,0x10,0x08,0xA8,0xC5,0x88,0xC2,0x18,0x88,0x2A,0x12,0xF3,0x82,0xD8,
0x20,0x0A,0x09,0xA6,0x98,0x04,0xB9,0x11,0x18,0xC3,0xE1,0x29,0xA1,0x11,0xC1,0x03,
0xE2,0x9A,0x33,0xA9,0xB5,0x98,0x92,0xA1,0x02,0xF8,0x21,0xA8,0x10,0x02,0xC1,0xB7,
0x1B,0x90,0x5B,0x3C,0x83,0x93,0xE0,0x19,0x1A,0x11,0x11,0xF1,0x92,0x89,0x19,0x2C,
0x2C,0x41,0x99,0x92,0x90,0x3F,0x18,0x4B,0x00,0x08,0xD2,0x01,0xB2,0xAA,0x78,0x09,
0x01,0x91,0xA2,0x98,0x2F,0x3A,0x2C,0x01,0x00,0x93,0xE0,0x28,0x2C,0x2B,0x01,0x12,
0xE1,0x80,0xB3,0x3D,0x3A,0x0A,0x50,0x98,0xC2,0xA0,0x11,0xAA,0x30,0x87,0x90,0xC2,
0x29,0x88,0x38,0xC8,0xB5,0x90,0xBA,0x70,0x1A,0x02,0x94,0xD0,0x80,0x1A,0x82,0xA6,
0xB0,0x91,0x18,0xB3,0x00,0x13,0xF1,0xA2,0xC1,0x82,0xB0,0x00,0x15,0x0B,0xD3,0x02,
0xA8,0x91,0x2B,0x1F,0x49,0x88,0xA6,0x80,0x88,0x08,0x1B,0xA5,0x80,0xB9,0x06,0x0B,
0x90,0x21,0x9D,0x48,0x18,0xA0,0x15,0xC9,0x82,0x2B,0x1A,0x42,0x9A,0xC4,0x39,0xBC,
0x69,0x00,0xA0,0x29,0x8C,0x39,0x59,0x08,0x09,0x49,0xA9,0x6B,0x81,0x00,0x98,0xB0,
0x68,0x3D,0x81,0x88,0x18,0x19,0x1D,0x12,0x80,0xB2,0x3A,0x3F,0x85,0x92,0xD0,0x00,
0x0A,0x19,0x12,0xF1,0x02,0x9B,0x19,0x40,0xB9,0x11,0x02,0xF2,0x1A,0x08,0x94,0x0A,
0xC2,0x83,0x0B,0xB4,0xA4,0xC0,0x32,0xD8,0x86,0x98,0x90,0x95,0x89,0xA3,0x83,0xC2,
0x92,0xE1,0x92,0x82,0xD9,0x03,0x08,0xA9,0x85,0x92,0xA2,0x80,0xE0,0x30,0x8B,0xB3,
0x87,0x89,0x90,0x83,0xA0,0x08,0x92,0x93,0x3E,0xAB,0x43,0x89,0xE3,0x80,0x83,0x2F,
0x00,0xA3,0x80,0xC9,0x22,0x3F,0x08,0x81,0x0B,0x33,0x9A,0xA3,0x7B,0x0C,0x29,0x4A,
0x1B,0x21,0xAA,0x70,0x1B,0x0D,0x48,0x1A,0x81,0x88,0xB1,0x39,0x3F,0x08,0x58,0xA0,
0x81,0x1A,0x1A,0x2B,0x6D,0x11,0x0A,0x91,0x01,0x1A,0x98,0x5A,0x0C,0x03,0xB1,0x84,
0xA3,0xAD,0x58,0x2A,0xA1,0x84,0xB1,0xA0,0x5C,0x2B,0x13,0xA8,0x95,0x83,0xE8,0x10,
0x81,0xB0,0x00,0xC2,0x96,0xA0,0x91,0x00,0x2C,0x90,0x30,0xF2,0x80,0xA8,0x39,0x21,
0xC1,0x03,0xAC,0x39,0x7C,0x29,0x91,0x1A,0x00,0x19,0x2C,0x3A,0x93,0xB0,0x29,0x8F,
0x28,0x02,0x93,0xF3,0xA9,0x01,0x03,0xE0,0x08,0x09,0x1D,0x58,0xA1,0x83,0xA9,0x6B,
0x2A,0x3C,0x21,0x89,0xC2,0x2C,0x4B,0x8A,0x50,0x81,0x98,0xA8,0x32,0x0C,0x8E,0x24,
0x0B,0x1A,0x81,0x92,0xA1,0x4F,0x18,0x3A,0x0A,0xB4,0x18,0x2E,0x39,0x82,0x19,0xD3,
0xD0,0x28,0x1B,0x11,0x98,0x07,0xAA,0x28,0x00,0x88,0xB4,0x89,0x1B,0x1F,0x22,0x00,
0xB3,0xC9,0x33,0xAB,0x2B,0xB5,0x48,0x98,0x98,0xA7,0x10,0xD2,0xC1,0x23,0xCA,0x93,
0xC6,0x80,0xA1,0x88,0x02,0x89,0xE2,0x09,0x38,0xBA,0x40,0x89,0x21,0xD8,0x49,0x10,
0x8D,0x02,0x90,0xC3,0x9A,0x24,0x89,0x08,0x84,0xA5,0x9C,0x10,0x11,0x9C,0x88,0x30,
0x3C,0xA1,0x94,0x58,0x8C,0x0B,0x69,0x29,0x9A,0x81,0x12,0x2B,0x8B,0x79,0x94,0xB0,
0xC1,0x84,0xC2,0x99,0x25,0x99,0x11,0xA2,0x93,0xE4,0x99,0x80,0x0A,0x00,0x10,0xB7,
0xB0,0x31,0xBA,0x3C,0x21,0xB3,0xF1,0x18,0xA0,0x2A,0x20,0xA3,0x06,0xE8,0x28,0xA1,
0xB4,0x08,0x0B,0x11,0x4B,0xB7,0x90,0xA5,0x98,0x3D,0x19,0x02,0xA1,0xC4,0xB2,0x19,
0x28,0xC0,0xA5,0x92,0xB1,0xA3,0x0A,0x0A,0x08,0x2B,0x70,0xC4,0xB3,0x00,0xBC,0x4B,
0x39,0x12,0xE3,0xA0,0x00,0x3F,0x18,0x29,0x94,0xD1,0x19,0x09,0x00,0xA1,0x83,0x99,
0x9B,0x35,0x80,0xC4,0xB1,0x6A,0x1A,0x1C,0x29,0x38,0x0E,0x19,0x5A,0x1A,0x82,0x8A,
0x59,0x2A,0x2E,0x20,0x88,0xA8,0x3A,0x38,0x3D,0x00,0xB3,0x29,0xAD,0x49,0x10,0x0C,
0x01,0x01,0xA3,0x8F,0x85,0x09,0x1B,0x88,0x10,0xA3,0xD2,0x90,0x3C,0x5C,0x39,0x03,
0xD1,0xA0,0x00,0x2A,0x0B,0x04,0xA7,0x90,0xA0,0x11,0x90,0x99,0x83,0xB4,0xB1,0xF1,
0x84,0x88,0x90,0x18,0x18,0xD3,0xD2,0xB3,0xA0,0x1A,0x21,0xA7,0xB2,0xB3,0x92,0x9A,
0x22,0xB9,0x28,0x38,0xBD,0x87,0x2A,0xB1,0x13,0x0D,0x0A,0x38,0xC9,0x24,0xC0,0x19,
0x23,0x0F,0x01,0x88,0xC0,0x2A,0x82,0x18,0x28,0xF0,0x18,0x2A,0x29,0x4B,0x35,0xB8,
0xA3,0x9D,0x18,0x1B,0x40,0x00,0x9A,0x5C,0x3A,0x09,0x2F,0x38,0x8A,0x3B,0x3B,0x11,
0x5C,0x19,0x2B,0x4A,0x08,0x0A,0x3D,0x20,0x4F,0x3A,0x19,0x2A,0x18,0x4D,0x1B,0x3A,
0x11,0x0D,0x3A,0x3C,0x4B,0x93,0x81,0xAA,0x6B,0x4A,0x18,0x00,0xC3,0xC3,0x9A,0x59,
0x2A,0x1B,0xA7,0xA1,0x81,0x88,0x88,0x58,0xB2,0xB1,0x2B,0x83,0xD4,0x81,0x08,0x0F,
0x00,0x20,0xC2,0xE2,0x80,0x08,0x1C,0x29,0x04,0xB1,0xA2,0x01,0x1C,0x91,0x00,0x0C,
0x49,0xB0,0x43,0xF2,0x99,0x39,0x3F,0x00,0x81,0x94,0xC1,0x09,0x1A,0x69,0x90,0x80,
0x94,0xAA,0x20,0x2A,0x91,0xB1,0x39,0x7A,0x38,0xD1,0x10,0x8A,0x8C,0x5A,0x01,0xB5,
0x98,0x80,0x2A,0x0B,0x32,0x92,0xF1,0x81,0x9A,0x23,0x8A,0xA3,0xB7,0x09,0x03,0x08,
0xD0,0x94,0x9A,0x09,0x01,0x93,0xB7,0xC2,0x8C,0x3A,0x83,0x99,0x05,0xA0,0x0B,0x29,
0x93,0xE5,0x80,0x89,0x38,0x90,0x8A,0xD7,0xA1,0x19,0x1B,0x48,0x98,0x92,0xC3,0xA1,
0x09,0x3F,0x02,0x0C,0x22,0xC3,0xB2,0xA1,0x01,0x9F,0x4A,0x01,0xA3,0xD3,0xB0,0x28,
0x3F,0x29,0x20,0xA2,0xC2,0xB1,0x08,0x5A,0x98,0x13,0xD2,0xC1,0x01,0xB2,0x80,0x3D,
0x03,0xC1,0x89,0x96,0x90,0x90,0x3A,0x1A,0x9A,0x32,0xB6,0xA2,0x8E,0x4A,0x28,0x8A,
0x84,0xA2,0x8A,0x2D,0x49,0x09,0x88,0x18,0x30,0x9D,0x2C,0x23,0xB1,0x0C,0x92,0x2D,
0x39,0x82,0xC4,0x2E,0x10,0x1A,0x10,0xB9,0x48,0x19,0x39,0xBA,0x34,0xDA,0x2D,0x48,
0x1A,0xA6,0x98,0x83,0x9A,0x1D,0x38,0x04,0xD0,0x18,0x90,0x2C,0x11,0x93,0xD3,0x9A,
0x11,0x08,0x82,0xF1,0x01,0xA0,0x2A,0x93,0xD3,0xB4,0xB8,0x82,0x2F,0x11,0xA3,0xB3,
0xA8,0x3B,0x09,0x23,0x96,0xC8,0x3B,0x3F,0x93,0x82,0xA1,0x90,0x3F,0x28,0x81,0xD1,
0x93,0x08,0x2D,0x18,0x91,0xB3,0xB5,0x98,0x2A,0x2B,0x84,0xB1,0x5B,0x8A,0x31,0x18,
0x80,0x8B,0x7E,0x39,0x2B,0x02,0xC1,0x8B,0x6C,0x49,0x09,0x10,0xA1,0x08,0x01,0x0C,
0x20,0xA1,0x09,0x4F,0x18,0x00,0x01,0xA0,0x5C,0x1B,0x5B,0x10,0x92,0x90,0x2B,0x5A,
0x3D,0x18,0x91,0x19,0x98,0x2D,0x39,0x89,0x2D,0x3A,0x48,0x2C,0x11,0xB5,0x9A,0x19,
0x5B,0x28,0x90,0x95,0x98,0x89,0x2B,0x40,0x08,0x90,0xF3,0x0A,0x08,0xA6,0x80,0x91,
0xB2,0xA0,0x02,0xF2,0xA1,0xB7,0x89,0x81,0x82,0x91,0xB1,0x21,0xAB,0x32,0xE9,0x04,
0xA2,0x8D,0x12,0x91,0xA3,0xA3,0xD2,0x8B,0x39,0xD1,0x84,0xE2,0x90,0x00,0x2B,0x29,
0xA3,0xD4,0xA1,0x91,0x1D,0x5A,0x08,0x19,0x11,0x99,0x08,0x18,0x49,0x0F,0x18,0x10,
0x82,0xF1,0x00,0x89,0x2F,0x3A,0x01,0xB3,0xC2,0x81,0x3F,0x29,0x08,0x10,0xA1,0xA1,
0x3B,0x5D,0x19,0x28,0x0B,0x38,0x82,0x91,0x19,0xBD,0x3B,0x7A,0x80,0x12,0xB3,0xE0,
0x0B,0x6A,0x01,0x88,0xA4,0x08,0x0B,0x08,0x59,0x80,0x80,0x1D,0x49,0x89,0x00,0x84,
0x99,0x1A,0x2B,0x32,0xE3,0xB4,0xA9,0x3A,0x99,0x31,0xE3,0xAA,0x58,0x3B,0x88,0x95,
0xC0,0x18,0x4A,0x09,0x30,0xF2,0xA3,0x1C,0x1B,0x49,0x00,0xD3,0xB2,0xA0,0x18,0x11,
0x92,0xD3,0xB2,0x91,0x80,0xE7,0xA1,0x91,0x98,0x19,0x22,0xC2,0xD2,0x18,0x8D,0x3B,
0x10,0xA5,0x91,0x98,0x02,0x3E,0x80,0x01,0x90,0xAA,0x13,0xF1,0x02,0xD1,0x08,0x19,
0x49,0xB4,0x91,0xB4,0x99,0x2A,0x0C,0x32,0xC0,0x05,0x88,0x0B,0x80,0x2C,0x81,0x10,
0x0B,0x51,0xA9,0x19,0x05,0xBF,0x28,0x20,0xE1,0x90,0x80,0x28,0x19,0x08,0x26,0xB1,
0xA1,0x18,0x88,0x2A,0xF0,0x12,0x8A,0xB3,0x14,0x1B,0xD4,0xD8,0x10,0x08,0x8A,0x17,
0xA0,0x98,0x2B,0x3A,0x29,0x48,0xA4,0x99,0x0E,0x4A,0x12,0x8B,0x31,0x8B,0x4E,0x1A,
0x11,0xB5,0x89,0x91,0x29,0x89,0xC2,0x97,0x90,0x0A,0x19,0x11,0x91,0xC1,0xD5,0x08,
0x89,0x20,0x91,0xB1,0x1A,0x2D,0x18,0x29,0xD2,0x3B,0x3E,0x3A,0x2A,0x90,0x82,0x1C,
0x49,0x3B,0x93,0xB6,0xC8,0x4C,0x02,0x91,0x93,0xF2,0x88,0x2D,0x28,0x81,0x82,0xC1,
0x89,0x2D,0x6B,0x19,0x82,0x80,0x18,0x8B,0x39,0x39,0xC8,0x3A,0x6A,0x0A,0x22,0xD2,
0x09,0x2C,0x1A,0x68,0x92,0xE2,0x89,0x2A,0x2A,0x30,0xC2,0xA3,0xB4,0x1D,0x2A,0x09,
0x93,0x18,0xF2,0x89,0x28,0xB3,0x01,0x8F,0x18,0x11,0xA1,0x93,0x90,0xD1,0x7A,0x20,
0xC3,0xA2,0xA8,0x88,0x1D,0x28,0xA5,0xA2,0xA2,0x0B,0x29,0x2B,0x87,0xC1,0x80,0x0A,
0x19,0x01,0x12,0xF1,0x10,0x80,0x0A,0x18,0x08,0x2F,0x4A,0x02,0x89,0x1B,0x29,0x5D,
0x4C,0x08,0x82,0xA1,0x0A,0x3A,0x4B,0x29,0xC6,0xC3,0x09,0x09,0x88,0x39,0x98,0x82,
0xA5,0x1A,0x30,0x11,0xBD,0x3F,0x12,0x8B,0x28,0xC3,0x88,0x3F,0x2B,0x3B,0x48,0xA1,
0x80,0x8A,0x4D,0x39,0x01,0x93,0xA2,0xF1,0x19,0x19,0x0A,0x02,0xB2,0x8B,0x24,0xD2,
0x4B,0x12,0xC8,0x2E,0x10,0xB5,0x89,0x01,0x09,0x1C,0x2A,0x03,0xD4,0x91,0x98,0x99,
0x11,0x2B,0xE4,0x00,0x00,0x01,0xE0,0xA5,0x89,0x99,0x31,0x18,0xD0,0xB7,0x98,0x18,
0x0A,0x10,0x94,0xC2,0x90,0x18,0x00,0x99,0x87,0xA0,0x90,0x2A,0x3C,0x02,0xB8,0xC1,
0x79,0x1A,0x20,0x08,0xA1,0xD2,0x1C,0x29,0x03,0xD1,0x29,0x99,0x2C,0x50,0xB3,0xD1,
0x08,0x09,0x3C,0x10,0x04,0xB2,0x0D,0x2B,0x59,0x80,0x90,0x01,0x0F,0x3A,0x18,0x01,
0xA2,0x9B,0x5B,0x3D,0x81,0x03,0xD2,0x98,0x59,0x90,0x81,0x92,0xB4,0x8B,0x1B,0x40,
0xB2,0xB5,0x08,0x4B,0x01,0x09,0xD1,0x91,0x8B,0x7A,0x10,0xB3,0xC3,0x99,0x49,0x1A,
0x29,0xB5,0xA2,0xAB,0x40,0x81,0x19,0xB7,0xB0,0x20,0x2B,0xD4,0x88,0xA1,0x91,0x3C,
0x82,0x37,0xD3,0xB1,0x8A,0x1B,0x30,0xB3,0xF4,0xA1,0x91,0x09,0x10,0x03,0xD0,0x83,
0xA9,0x8F,0x10,0x01,0x90,0x18,0x80,0x20,0x2B,0xF1,0x28,0x99,0x2A,0x41,0xF0,0x12,
0xAA,0x83,0x82,0xD1,0xC1,0x08,0x89,0x59,0x09,0x83,0x87,0xB0,0x2A,0x4D,0x18,0x09,
0x19,0xB3,0x4B,0x3F,0x39,0x19,0x09,0x01,0x89,0x03,0x1F,0x00,0x1A,0x0B,0x10,0x68,
0xA0,0x18,0x8C,0x6A,0x09,0x08,0x97,0xA1,0x81,0x1B,0x2B,0x4C,0x03,0xB4,0xA8,0x92,
0x4B,0x3C,0xA1,0x81,0x95,0xA8,0x81,0x12,0xBB,0x92,0x45,0xB9,0x93,0xF4,0x88,0x0A,
0x2D,0x28,0x00,0xA3,0xA3,0x8A,0x3F,0x48,0xB1,0x92,0xB4,0xA8,0x30,0x80,0xD3,0x80,
0xD1,0x19,0x3B,0xC4,0x81,0xC1,0x29,0x0D,0x20,0x13,0xC8,0xB4,0x4C,0x09,0x00,0x82,
0xC2,0x3B,0x0D,0x30,0x0B,0x12,0xF0,0x1B,0x20,0x0A,0xA6,0x80,0x0A,0x4A,0x4A,0x80,
0x94,0xB1,0x2E,0x3B,0x1A,0x10,0x93,0x10,0x4C,0x3D,0x08,0x82,0xC9,0x19,0x6A,0x2B,
0x38,0xD1,0x08,0x19,0x2A,0x5A,0x82,0xB1,0x8D,0x29,0x78,0x09,0x82,0x0A,0x2C,0x1B,
0x19,0x41,0xB8,0x8C,0x79,0x2B,0x11,0x88,0x82,0x91,0xDC,0x28,0x11,0xB0,0x11,0x18,
0xC9,0x62,0xA1,0x91,0x98,0x3B,0x3A,0xB0,0xF4,0x01,0xC0,0x29,0x39,0xF8,0x95,0x91,
0x88,0x88,0x91,0x03,0xA1,0xE2,0x18,0x82,0xD1,0xA2,0xD1,0x80,0x19,0x20,0x83,0xB1,
0xE3,0x80,0x91,0x4D,0x1A,0x03,0xB2,0x09,0x18,0xD1,0x19,0x09,0x92,0xA6,0xA0,0xB6,
0xB2,0x8B,0x38,0x10,0x42,0xD3,0xD0,0xA8,0x20,0x2C,0x10,0x01,0xB1,0xB4,0xAB,0x5B,
0x79,0x80,0x10,0x1A,0xA8,0x3D,0x18,0x20,0xB3,0x8F,0x18,0x01,0x00,0x09,0xF3,0x89,
0x69,0x88,0x81,0x91,0x08,0xE1,0x1A,0x08,0x11,0x81,0x1E,0x29,0xA0,0x01,0x00,0x90,
0x3E,0x7B,0x18,0x82,0xC3,0xA1,0x2A,0x2C,0x5B,0x81,0xA5,0x90,0x81,0x00,0x0B,0x1A,
0x1C,0x2C,0x32,0xC0,0xF3,0x80,0x2D,0x2A,0x10,0x02,0xE4,0xC1,0x89,0x4A,0x09,0x01,
0x03,0xD2,0x98,0x2A,0x39,0x8A,0x89,0x26,0xB1,0xB2,0x12,0xC0,0x0A,0x5A,0x18,0x98,
0xF3,0x92,0x99,0x99,0x79,0x01,0xB5,0xA1,0x80,0x80,0x90,0x83,0xA0,0xE2,0x81,0x29,
0x93,0x8A,0x0A,0x6A,0x1F,0x18,0x02,0xC8,0x01,0x19,0x3B,0x4A,0x98,0x17,0xA8,0x0D,
0x38,0xA1,0x91,0x10,0xA2,0x2B,0x4C,0xA6,0x81,0xBA,0x21,0x4C,0x80,0x21,0xD1,0x92,
0x2C,0x08,0x30,0x9F,0x93,0x2A,0x89,0x03,0x8B,0x87,0x0A,0x0D,0x12,0x98,0xA4,0x93,
0xBB,0x59,0x18,0xA1,0x32,0xE9,0x84,0x08,0x8A,0x02,0xA1,0x91,0x4B,0xB4,0x20,0x88,
0xF0,0x3A,0x1A,0x88,0x87,0xB1,0x92,0x0A,0x08,0x6B,0x83,0xC3,0x91,0xC0,0x2B,0x79,
0x08,0x8A,0x84,0xA0,0x89,0x40,0x1B,0xA1,0x39,0x98,0x17,0xC2,0xA2,0x12,0xCD,0x20,
0x89,0x92,0x25,0xB0,0x2D,0x3A,0x8B,0x58,0x2A,0xA0,0x4C,0x08,0x30,0xAE,0x82,0x59,
0x89,0x1A,0x10,0xC2,0x18,0x2C,0x40,0x1E,0x01,0xA3,0x8A,0x81,0x2C,0x29,0x29,0xA9,
0x13,0x51,0xAD,0x12,0x89,0x8F,0x18,0x2C,0x39,0x00,0xC1,0x10,0x3C,0x2A,0x41,0xC8,
0xA2,0x91,0x0A,0x6C,0x10,0x12,0x88,0xE8,0x30,0x91,0x81,0xD8,0x01,0x1B,0x0D,0x07,
0x00,0xA8,0x92,0x0A,0x28,0xD2,0xC3,0x02,0xAA,0x94,0x81,0xB4,0xB3,0x1A,0x0B,0x13,
0xF9,0x16,0xA1,0x8A,0x59,0x19,0x02,0xC1,0x91,0x8B,0x3D,0x18,0x3B,0xA4,0x94,0x80,
0x99,0x88,0x1C,0x79,0x0A,0x02,0x03,0xF8,0x90,0x39,0x5B,0x19,0x02,0xC3,0x90,0xBB,
0x58,0x6A,0x09,0x02,0x89,0x91,0x88,0x1A,0x69,0x8A,0x19,0x15,0xA0,0xA2,0x00,0x9A,
0x6B,0x49,0x88,0xA3,0x92,0xBB,0x6B,0x3D,0x38,0x01,0x98,0x91,0x3F,0x09,0x18,0x20,
0x90,0x80,0xAC,0x70,0x91,0x9B,0x51,0x09,0x88,0x99,0x14,0x8B,0x98,0x83,0x79,0xA0,
0x99,0x13,0x01,0x19,0xE0,0x83,0x0B,0xB0,0x0C,0x31,0x95,0xB5,0xC2,0x8A,0x39,0x20,
0x80,0x39,0xF3,0xB1,0x10,0x88,0x5E,0x18,0x94,0xA1,0x88,0xA1,0x98,0x15,0xAA,0x39,
0xD4,0x84,0xC0,0xA2,0xA2,0x0C,0x81,0x86,0xB5,0xA1,0xB1,0x14,0x1B,0xB1,0x02,0x92,
0xC3,0xE0,0x88,0x11,0xAA,0x69,0x18,0x81,0xA3,0xB0,0x01,0xBF,0x2A,0x31,0x93,0xF1,
0x00,0x89,0x18,0x19,0x11,0xD3,0xE0,0x10,0x18,0xB1,0x18,0x24,0x9A,0x2B,0xA4,0xC0,
0xB0,0x31,0x6C,0x19,0xB4,0x12,0xA8,0xEA,0x58,0x10,0x8B,0x93,0x82,0x88,0x9A,0x41,
0x10,0xC3,0xEA,0x41,0xA9,0x9C,0x34,0xA1,0x2A,0x79,0xA2,0x01,0xA8,0xB3,0x28,0xCC,
0x41,0x9A,0xB3,0x4B,0xB3,0x27,0x8B,0x83,0x2B,0x2F,0x08,0x28,0xB2,0x80,0x2C,0x30,
0x5E,0x09,0x12,0x9B,0x09,0x22,0x5B,0x19,0x8A,0x11,0x59,0x99,0xA4,0x32,0xCD,0x18,
0x08,0x10,0x85,0xB3,0xB4,0x1E,0x88,0x28,0x8A,0x11,0x09,0xC0,0x79,0x80,0x91,0x3B,
0x80,0x10,0x0F,0x01,0x80,0x91,0x19,0x3D,0x92,0x28,0xA8,0x37,0x9A,0x0A,0x3A,0x8A,
0x45,0xA9,0xA4,0x00,0xAA,0x09,0x3D,0x59,0x20,0xE1,0x08,0x98,0x90,0x59,0x10,0x09,
0xA3,0xC3,0x93,0x99,0x2B,0x69,0x11,0xD1,0xB1,0xA4,0x91,0x3C,0x89,0x83,0xF0,0x10,
0x91,0xA1,0x89,0x59,0x05,0x99,0x93,0x94,0xC8,0x08,0x0A,0x09,0x17,0xB1,0x83,0xC1,
0x91,0x40,0xA2,0xC2,0x98,0xC3,0xBA,0x28,0x23,0x0F,0x80,0x50,0xB8,0x19,0x10,0x96,
0x98,0x8C,0x05,0x98,0x19,0x29,0x2B,0x3B,0x0A,0xE2,0x01,0x0F,0x3C,0x38,0x08,0x09,
0x81,0x4A,0x6C,0x08,0x00,0x88,0x98,0x38,0x2C,0x5A,0x1B,0x20,0x1A,0x39,0xB0,0x09,
0xCB,0x5B,0x49,0x09,0x71,0x00,0xC1,0x0E,0x08,0x38,0x0C,0x02,0x10,0x0E,0x10,0x8A,
0x48,0x19,0x90,0x92,0x0D,0xA3,0x98,0x3B,0x79,0x19,0x01,0x10,0xE1,0x80,0x19,0x2B,
0x10,0xF2,0x02,0xAB,0x84,0x9A,0x29,0xB4,0x80,0x92,0x03,0x88,0x95,0xD0,0x03,0x90,
0xA0,0xC7,0xA1,0xB0,0xA2,0x02,0x18,0xB5,0xD4,0x01,0xC0,0x08,0xA2,0x93,0xA8,0xA0,
0xC3,0x20,0xF3,0x90,0x00,0xD5,0x08,0x89,0xA5,0x80,0xA0,0x81,0x82,0xC2,0x09,0xD1,
0x13,0xCB,0x03,0x84,0x91,0xE1,0x1B,0x12,0x08,0xAB,0x87,0x18,0xAB,0x58,0x89,0x28,
0x81,0xC9,0x33,0xA9,0x80,0x2E,0x20,0x83,0xB9,0x20,0x3B,0x9E,0x7A,0x08,0x81,0x18,
0x0B,0x88,0x79,0x80,0x8B,0x00,0x12,0x0E,0x89,0x51,0x1B,0x81,0xA0,0x3A,0x01,0xAF,
0x11,0x28,0xBA,0x35,0x98,0x88,0x52,0xC0,0x83,0x2F,0xA9,0x11,0x0A,0x19,0x25,0xD0,
0x30,0x9C,0x08,0x21,0x98,0x81,0x2A,0xF3,0x2A,0x80,0xB6,0x2B,0x08,0x93,0xE9,0x02,
0x81,0x8C,0x21,0x00,0xA6,0xA9,0x94,0x01,0x8F,0x80,0x94,0x98,0x93,0xB4,0x00,0x08,
0xC0,0x14,0x98,0xB3,0xB4,0xC1,0x09,0x18,0xA7,0x00,0xA3,0xC8,0x0A,0x3C,0x19,0x96,
0x83,0xC1,0x99,0x19,0x4A,0x85,0x80,0xC1,0x91,0x99,0x90,0x2A,0x17,0x95,0x99,0x88,
0x12,0xAE,0x39,0x08,0x92,0x84,0xB0,0xA8,0x79,0x09,0x19,0x01,0xB2,0xA3,0x8F,0x28,
0x2B,0xA2,0x40,0x82,0xA0,0x4C,0xA9,0x39,0x8D,0x81,0x70,0x88,0xA0,0x1A,0x49,0x2D,
0x1A,0x26,0xA8,0x98,0x08,0x29,0x0B,0x12,0x96,0xB1,0xB2,0x3A,0x13,0x9B,0x60,0xA0,
0x88,0xB2,0x34,0xEA,0x1A,0x2A,0x79,0x98,0x10,0x04,0x8C,0x1C,0x81,0x04,0x8C,0x83,
0x19,0x2F,0x81,0x93,0x98,0x10,0x08,0x30,0x2A,0xFA,0x05,0x08,0x2A,0x89,0x91,0xA3,
0xFA,0x11,0x11,0x00,0x8C,0x04,0x8A,0x2A,0xB5,0x10,0xA9,0xC2,0x3D,0x1B,0x32,0x04,
0x0A,0x1A,0x09,0x40,0x1F,0x92,0x1D,0x2A,0x91,0x10,0x30,0x2F,0x0B,0x68,0x99,0xA2,
0x92,0x88,0x78,0xA9,0x20,0x28,0xE2,0x92,0x1A,0x99,0x4B,0x19,0x22,0xA1,0xE2,0x21,
0x2F,0x98,0x29,0x18,0x91,0x08,0xB0,0x79,0x1A,0x82,0x3B,0xB1,0xA7,0x8A,0xB3,0x98,
0x5B,0x23,0xCA,0x42,0x83,0xF0,0x90,0x18,0x98,0x08,0xB4,0x20,0xA3,0xC0,0x43,0xD8,
0x80,0x81,0xA3,0x99,0xD9,0xA7,0x19,0x90,0x10,0x05,0xB1,0x8B,0x02,0xA4,0xBD,0x23,
0x93,0x8A,0x99,0x4B,0x03,0xC1,0xF8,0x38,0x09,0x2B,0x14,0xD0,0x03,0x8A,0x2A,0x39,
0xB9,0x97,0x90,0xAA,0x50,0x01,0x99,0x51,0xD1,0x09,0x1A,0xB5,0x00,0x8B,0x93,0x08,
0x98,0x11,0xF9,0x85,0x2B,0x08,0x96,0x89,0x90,0x2A,0x12,0x4A,0xD8,0x85,0x2B,0x0E,
0x10,0x00,0x01,0xB1,0x9B,0x69,0x1A,0x90,0x40,0xB8,0x01,0x08,0x0A,0x2C,0x09,0x14,
0x4B,0xE2,0x82,0x88,0xB1,0x78,0x0A,0x01,0xC2,0x93,0x19,0xCE,0x20,0x3C,0x82,0xB4,
0x1B,0x20,0x8C,0x3B,0x29,0xAB,0x86,0x23,0xD8,0x81,0x9A,0x5A,0x49,0xB0,0x16,0xA0,
0xB0,0x28,0x1B,0x13,0x93,0xE4,0xA2,0xA9,0x08,0x5A,0xB3,0x12,0xC1,0xE1,0x10,0x88,
0x01,0x0C,0x92,0x08,0x89,0xB7,0x88,0x81,0x10,0x9A,0x17,0xA0,0xB0,0x13,0x99,0xE0,
0x39,0x31,0xD2,0xB2,0x80,0x0B,0x2D,0x49,0x80,0x01,0xB0,0x06,0x09,0x0C,0x3A,0x69,
0xA0,0x08,0xB2,0xA1,0x69,0x2B,0x5A,0x81,0x92,0xBA,0x21,0xB1,0x7D,0x10,0x80,0x08,
0x88,0x82,0x32,0x0D,0xB0,0x1A,0x1C,0x21,0x94,0xA9,0x58,0xB9,0x5A,0x4A,0xA0,0x13,
0xA9,0x80,0x7C,0x00,0x20,0x8A,0x04,0x0C,0x00,0x82,0x2A,0xB2,0xAC,0x4B,0x69,0xA0,
0xA6,0x81,0x9B,0x19,0x38,0x8B,0x17,0xB2,0x81,0x2A,0xBB,0x94,0x29,0xA2,0x15,0xBA,
0x97,0xA3,0xB9,0x79,0x01,0xB2,0x02,0xF1,0x90,0x0A,0x29,0x11,0x88,0xE5,0xA0,0x81,
0x19,0x91,0x90,0x28,0xB3,0x14,0xD0,0xB5,0x91,0x9A,0x29,0x0B,0x07,0xA2,0xB3,0x01,
0x9D,0x28,0x41,0xD0,0x91,0x90,0x82,0x1A,0xA8,0x44,0x9A,0xA9,0x21,0xE3,0xA9,0x4B,
0x19,0x78,0x89,0x83,0xA3,0xB9,0x5A,0x3D,0x80,0x82,0xA2,0xA0,0x6C,0x10,0x20,0x8B,
0x93,0x8B,0x0E,0x33,0xA9,0xB1,0x68,0x8A,0x31,0xAC,0x94,0xB4,0x8B,0x32,0x0B,0xB4,
0x81,0x91,0x1D,0x33,0xD9,0x31,0xE1,0x8B,0x3B,0x30,0x12,0x49,0xD2,0x8E,0x29,0x18,
0x8A,0x92,0x02,0xAA,0x59,0x1C,0x32,0x88,0x01,0x23,0xFB,0x83,0x29,0xDA,0x59,0x01,
0x81,0x92,0xE1,0x18,0x8A,0x1D,0x30,0x93,0xF1,0x00,0x01,0x0B,0x39,0x92,0x89,0xA0,
0x11,0x5B,0xE0,0x82,0x09,0x13,0xAA,0xB4,0x16,0xD8,0x91,0x2A,0x29,0x84,0x1B,0xC5,
0x98,0x98,0x31,0x98,0x99,0x17,0xA9,0x20,0x92,0xC3,0x18,0x9D,0x20,0x3D,0x89,0x94,
0xA2,0x1C,0x5C,0x29,0x39,0xA0,0xB3,0x00,0x0C,0x4C,0x48,0x92,0x0A,0x91,0x85,0x9A,
0x01,0x82,0x1F,0x10,0x99,0x15,0xC1,0xA0,0x39,0x1A,0x1D,0x85,0xB4,0x90,0x1A,0x2A,
0x4B,0x01,0xB2,0x93,0xBE,0x12,0x83,0xC9,0x18,0x09,0x20,0x78,0xF1,0x08,0x19,0x88,
0x3A,0x83,0xB3,0xA9,0x93,0x7A,0x0A,0x96,0x98,0x00,0xA8,0x3A,0x30,0x92,0xF2,0x9B,
0x3D,0x38,0x92,0x92,0xC3,0xB8,0x6B,0x29,0x01,0x01,0xB2,0x2F,0x09,0x19,0x18,0x01,
0x3B,0x7B,0x10,0xA1,0x90,0x39,0x0F,0x38,0x0A,0xB5,0xA4,0x89,0x8B,0x6A,0x2B,0x12,
0xC8,0x90,0x40,0x2A,0x9E,0x22,0x88,0x18,0x09,0x3A,0xC3,0xE8,0x09,0x59,0x08,0x12,
0x94,0xD0,0x1A,0x2C,0x38,0x00,0xA1,0x83,0xE8,0x08,0x3A,0x08,0x10,0x9E,0x83,0x1D,
0x92,0x19,0x2C,0x39,0x3B,0x59,0x04,0xE1,0x80,0x08,0x8D,0x21,0x81,0xB2,0xB2,0x02,
0x99,0x91,0xA4,0xD6,0x98,0x99,0x03,0x80,0x98,0xA7,0x91,0x09,0xA1,0xB2,0xB3,0xE1,
0x12,0x92,0xB1,0x81,0x06,0x99,0x0A,0x23,0xC4,0xB1,0xF2,0x89,0x19,0x3A,0x94,0x82,
0xE0,0x89,0x38,0x0B,0xA4,0xA5,0x80,0x80,0x8C,0x34,0xB9,0xA9,0x23,0x13,0xB9,0xC1,
0xC7,0x1B,0x89,0x10,0x20,0x11,0xE3,0xA8,0x4B,0x0B,0x40,0x91,0x90,0x1B,0x5F,0x2A,
0x18,0x82,0x91,0x0B,0x4A,0x28,0xCA,0x40,0x80,0x5B,0x2C,0x13,0xB0,0x8A,0xA9,0x5A,
0x58,0x89,0x82,0x88,0x2E,0x3B,0x31,0xA1,0x9B,0x01,0x7A,0x2C,0x01,0x91,0x93,0x3F,
0x88,0x39,0x10,0xF1,0x91,0x8B,0x48,0x0A,0x12,0xE3,0xA8,0x18,0x28,0x92,0x97,0x98,
0x99,0x19,0xA1,0x11,0xB6,0x88,0x3B,0x10,0xD3,0xC3,0xA1,0x2A,0x8A,0x49,0x04,0xF1,
0x91,0x02,0x8A,0x89,0x04,0xF1,0x98,0x80,0x18,0x12,0xE3,0x81,0x98,0x80,0x01,0xB3,
0xF2,0x99,0x12,0x2A,0xB5,0xB3,0x92,0xAA,0x19,0x50,0xB2,0xC3,0x92,0xD0,0x2B,0x68,
0x93,0x99,0xC0,0x2C,0x3E,0x80,0x20,0x08,0x93,0x0D,0x2A,0x31,0x8D,0x02,0x2B,0x91,
0x08,0x0A,0x03,0x2C,0x3C,0x52,0xB9,0xA0,0x12,0xBF,0x3A,0x29,0x01,0x88,0xC0,0x6A,
0x3C,0x0A,0x49,0x18,0x0B,0x39,0x2B,0x69,0x0A,0x84,0x2A,0x2A,0x1C,0x2A,0xC3,0x8C,
0x19,0x50,0x09,0x91,0xA7,0x8D,0x18,0x1A,0x28,0x00,0xA0,0x94,0x10,0x1F,0x20,0x90,
0x8A,0x12,0xD0,0x1A,0x5A,0x81,0x04,0xBC,0x23,0x10,0xE0,0x90,0x90,0x18,0x1A,0xA6,
0x12,0xB1,0xD0,0x4A,0x08,0x82,0x92,0xB6,0x9A,0x0A,0x12,0x88,0xC3,0xC5,0x8A,0x89,
0x20,0xB5,0x93,0x0B,0x18,0x00,0x09,0xF2,0x88,0x2A,0x4A,0x08,0x05,0xB2,0xA9,0x3B,
0x5D,0x28,0xA4,0xB1,0x00,0x19,0x19,0x7A,0xA3,0xB3,0x0A,0x90,0xA1,0xC4,0x80,0xBA,
0x50,0x13,0xC1,0xC2,0x9A,0x2A,0x7B,0x28,0x84,0xC1,0x09,0x3B,0x4E,0x20,0x91,0xA1,
0x18,0xAB,0x79,0x10,0xB4,0x08,0x9A,0x11,0x2B,0xF0,0x93,0xAA,0x01,0x6A,0x01,0x93,
0x80,0xB8,0x2A,0x5B,0x10,0x80,0x89,0x4A,0x5B,0x92,0x15,0xB2,0xA0,0x2F,0x19,0x93,
0xB8,0x95,0x80,0x1C,0x21,0xA9,0x02,0x0B,0xA0,0x5A,0x18,0x98,0x39,0x1B,0x68,0x00,
0x91,0x91,0x9C,0x39,0x3E,0x18,0x84,0xB3,0x9B,0x7A,0x08,0x18,0x0A,0xB5,0x91,0x0B,
0x28,0x39,0x19,0x90,0x0A,0x50,0xAC,0x11,0x01,0xAB,0x88,0x52,0x1B,0x83,0xC4,0xA2,
0x9A,0xAB,0x03,0x90,0x19,0x93,0x81,0x08,0x92,0x9A,0x68,0x98,0x19,0x39,0xC1,0x92,
0x8A,0x38,0x4E,0x02,0xB1,0x90,0xC3,0x18,0x2B,0x04,0xC3,0xD2,0x91,0x90,0x81,0x89,
0x13,0xF1,0x88,0x93,0xA2,0x00,0x91,0xC0,0x5B,0x21,0x99,0x93,0x06,0x9A,0x1B,0x48,
0x99,0xB7,0x90,0x89,0x18,0x1B,0x11,0xA4,0xB2,0x81,0x9A,0x08,0x97,0x98,0x91,0x10,
0xB8,0x06,0xA2,0xA0,0x29,0x2B,0x21,0xC2,0xD1,0x10,0x1A,0x4A,0x29,0xF1,0x98,0x29,
0x1B,0x31,0x10,0xA0,0xA1,0x1D,0x5A,0x29,0xB2,0x82,0xA8,0x0F,0x28,0x21,0x09,0x91,
0x82,0x4D,0x10,0xA3,0xB0,0x89,0x4C,0x39,0xA0,0xA4,0xA1,0x89,0x1E,0x28,0x29,0xA3,
0xC3,0x2D,0x19,0x01,0x49,0x01,0x9B,0x0C,0x21,0xC2,0xA2,0x93,0x7C,0x2A,0x10,0x90,
/* Source: 08HH.ROM */
/* Length: 384 / 0x00000180 */
0x75,0xF2,0xAB,0x7D,0x7E,0x5C,0x3B,0x4B,0x3C,0x4D,0x4A,0x02,0xB3,0xC5,0xE7,0xE3,
0x92,0xB3,0xC4,0xB3,0xC3,0x8A,0x3B,0x5D,0x5C,0x3A,0x84,0xC2,0x91,0xA4,0xE7,0xF7,
0xF7,0xF4,0xA1,0x1B,0x49,0xA5,0xB1,0x1E,0x7F,0x5A,0x00,0x89,0x39,0xB7,0xA8,0x3D,
0x4A,0x84,0xE7,0xF7,0xE2,0x2D,0x4C,0x3A,0x4E,0x7D,0x04,0xB0,0x2D,0x4B,0x10,0x80,
0xA3,0x99,0x10,0x0E,0x59,0x93,0xC4,0xB1,0x81,0xC4,0xA2,0xB2,0x88,0x08,0x3F,0x3B,
0x28,0xA6,0xC3,0xA2,0xA2,0xC5,0xC1,0x3F,0x7E,0x39,0x81,0x93,0xC2,0xA3,0xE5,0xD2,
0x80,0x93,0xB8,0x6D,0x49,0x82,0xD4,0xA1,0x90,0x01,0xA0,0x09,0x04,0xE3,0xB2,0x91,
0xB7,0xB3,0xA8,0x2A,0x03,0xF3,0xA1,0x92,0xC5,0xC3,0xB2,0x0B,0x30,0xB3,0x8E,0x6D,
0x4A,0x01,0xB4,0xB4,0xC4,0xC3,0x99,0x3B,0x12,0xE3,0xA1,0x88,0x82,0xB4,0x9A,0x5C,
0x3A,0x18,0x93,0xC3,0xB3,0xB4,0xA8,0x19,0x04,0xF3,0xA8,0x3B,0x10,0xA2,0x88,0xA5,
0xB2,0x0B,0x6D,0x4B,0x10,0x91,0x89,0x3C,0x18,0x18,0xA6,0xC4,0xC3,0x98,0x19,0x2B,
0x20,0x91,0xA0,0x4E,0x28,0x93,0xB3,0xC2,0x92,0xA9,0x5A,0x96,0xC4,0xC2,0x09,0x01,
0xC4,0xA1,0x92,0xC4,0xA1,0x89,0x10,0xA3,0xA1,0x90,0x1C,0x5A,0x01,0xC5,0xA1,0x92,
0xD4,0xB3,0xC4,0xC4,0xC3,0xA1,0x88,0x1A,0x28,0x89,0x3C,0x3A,0x3D,0x29,0x00,0x93,
0xB0,0x3D,0x28,0x80,0x91,0x82,0xE3,0x99,0x2A,0x11,0xD6,0xC3,0x99,0x29,0x82,0xC4,
0xC3,0xA1,0x0A,0x3B,0x3D,0x3A,0x02,0xC3,0xA2,0x99,0x3B,0x2C,0x7C,0x28,0x81,0xA3,
0xB2,0xA3,0xB1,0x08,0x1A,0x3C,0x18,0x2E,0x4C,0x39,0xA5,0xB3,0xB4,0xC2,0x88,0x08,
0x19,0x0A,0x49,0xB7,0xB3,0xA2,0xA1,0x92,0xA1,0x93,0xB1,0x0C,0x7D,0x39,0x93,0xB3,
0xB1,0x1A,0x19,0x5D,0x28,0xA6,0xC4,0xB2,0x90,0x09,0x2A,0x18,0x1B,0x5B,0x28,0x88,
0x2C,0x29,0x82,0xA0,0x18,0x91,0x2D,0x29,0x2B,0x5C,0x4C,0x3B,0x4C,0x28,0x80,0x92,
0x90,0x09,0x2B,0x28,0x1D,0x6B,0x11,0xC5,0xB2,0x0B,0x39,0x09,0x4D,0x28,0x88,0x00,
0x1B,0x28,0x94,0xE3,0xA0,0x1A,0x28,0xB5,0xB4,0xB3,0xB2,0x93,0xE2,0x91,0x92,0xD4,
0xA0,0x1B,0x4A,0x01,0xA1,0x88,0x2D,0x5C,0x3B,0x28,0x08,0x93,0xD4,0xB2,0x91,0xB4,
0xA0,0x3E,0x3B,0x4B,0x3B,0x29,0x08,0x93,0x9B,0x7B,0x3A,0x19,0x00,0x80,0x80,0xA0,
/* Source: 10TOM.ROM */
/* Length: 640 / 0x00000280 */
0x77,0x27,0x87,0x01,0x2D,0x4F,0xC3,0xC1,0x92,0x91,0x89,0x59,0x83,0x1A,0x32,0xC2,
0x95,0xB1,0x81,0x88,0x81,0x4A,0x3D,0x11,0x9E,0x0B,0x88,0x0C,0x18,0x3B,0x11,0x11,
0x91,0x00,0xA0,0xE2,0x0A,0x48,0x13,0x24,0x81,0x48,0x1B,0x39,0x1C,0x83,0x84,0xA1,
0xD1,0x8E,0x8A,0x0B,0xC0,0x98,0x92,0xB8,0x39,0x90,0x10,0x92,0xF0,0xB5,0x88,0x32,
0x49,0x51,0x21,0x03,0x82,0x10,0x8A,0x7A,0x09,0x00,0xA2,0xCA,0x1B,0xCC,0x1C,0xB9,
0x8E,0x89,0x89,0xA1,0x89,0x92,0x29,0x11,0x60,0x40,0x14,0x22,0x32,0x78,0x40,0x01,
0x02,0x90,0x81,0xAB,0x0B,0x00,0xAF,0x99,0xCC,0xAB,0xDA,0xA9,0x99,0x1B,0x30,0x14,
0x92,0x22,0x19,0x68,0x32,0x14,0x26,0x13,0x23,0x23,0x20,0x12,0x9A,0xA8,0xB9,0xFA,
0xAA,0xCA,0xCC,0x0C,0xA8,0xAE,0x88,0xB9,0x88,0xA0,0x02,0x21,0x50,0x43,0x03,0x81,
0x2A,0x11,0x34,0x63,0x24,0x33,0x22,0x38,0x8B,0xEA,0xAE,0x99,0xA0,0x90,0x82,0x00,
0x89,0xBF,0x8A,0xE8,0xA9,0x90,0x01,0x12,0x13,0x12,0x08,0xA9,0xAA,0xC9,0x22,0x63,
0x63,0x12,0x44,0x00,0x10,0x88,0x9C,0x98,0xA1,0x85,0x03,0x32,0x36,0x80,0x89,0xDB,
0xDB,0xBB,0xB9,0xBA,0x01,0x81,0x28,0x19,0xCB,0xFA,0xBC,0x09,0x13,0x37,0x34,0x34,
0x23,0x31,0x20,0x10,0x00,0x00,0x28,0x38,0x10,0x88,0xEC,0x8D,0xCB,0xBC,0xCC,0xBB,
0xBB,0xC9,0x99,0x00,0x00,0x33,0x11,0x22,0x81,0x07,0x41,0x54,0x34,0x34,0x22,0x31,
0x00,0x88,0x9A,0x9B,0x98,0xAB,0x8E,0x9B,0xBD,0x9C,0xBC,0xBB,0xDA,0xAA,0xA9,0x99,
0x18,0x38,0x60,0x20,0x31,0x13,0x13,0x51,0x14,0x31,0x53,0x33,0x35,0x22,0x01,0x8A,
0x9C,0xA9,0xCA,0xC9,0xA8,0x00,0x10,0x81,0x9C,0x9E,0xAB,0xCC,0xAB,0xBA,0x98,0x30,
0x52,0x03,0x81,0x08,0x9C,0xAC,0xAC,0x18,0x11,0x03,0x51,0x61,0x41,0x31,0x31,0x02,
0x01,0x20,0x24,0x43,0x44,0x40,0x30,0x10,0xBC,0xBE,0xCB,0xDB,0xAB,0xBA,0x99,0x98,
0x99,0xAA,0xBD,0xAA,0xC8,0x90,0x11,0x53,0x37,0x23,0x43,0x34,0x33,0x33,0x33,0x11,
0x28,0x00,0x19,0xA9,0x9A,0xCB,0xCE,0xBB,0xEB,0xBC,0xBB,0xCA,0xBA,0xA8,0x88,0x11,
0x12,0x21,0x20,0x22,0x26,0x26,0x23,0x23,0x43,0x24,0x22,0x32,0x20,0x31,0x81,0x9A,
0xBC,0xBC,0xCB,0xBD,0x9A,0xA9,0x90,0x98,0xBA,0xCC,0xCB,0xBC,0x8B,0x88,0x22,0x35,
0x23,0x12,0x99,0x8B,0xAA,0xAA,0x89,0x82,0x93,0x31,0x42,0x23,0x23,0x21,0x32,0x11,
0x20,0x13,0x13,0x24,0x24,0x24,0x22,0x11,0x8A,0x9E,0xAC,0xAC,0xAA,0xBA,0xAA,0xAB,
0xBD,0xBC,0xCB,0xCB,0xA9,0xA8,0x91,0x12,0x44,0x43,0x44,0x34,0x34,0x42,0x33,0x42,
0x21,0x11,0x11,0x88,0x80,0xAA,0x0B,0xAC,0xCB,0xEC,0xAC,0xBA,0xCA,0xAB,0x9A,0x99,
0x80,0x91,0x09,0x08,0x10,0x22,0x44,0x43,0x44,0x33,0x43,0x22,0x13,0x21,0x22,0x20,
0x09,0x88,0xB9,0xC8,0xBB,0xAB,0xAB,0xA9,0xA9,0x9B,0x9B,0x99,0x90,0x90,0x00,0x81,
0x00,0x08,0x09,0x8A,0x9A,0xAA,0xA9,0xA9,0x99,0x90,0x80,0x01,0x80,0x00,0x09,0x31,
0x32,0x44,0x33,0x43,0x34,0x33,0x24,0x22,0x23,0x12,0x10,0x09,0x9B,0xAB,0xCA,0xCC,
0xBB,0xCB,0xDA,0xCA,0xAB,0xCA,0xAB,0xA9,0xA8,0x92,0x12,0x43,0x53,0x35,0x23,0x33,
0x43,0x43,0x52,0x22,0x22,0x21,0x01,0x09,0x89,0xA9,0xBB,0xBD,0xBC,0xCB,0xDA,0xAB,
0xAB,0xAB,0xAA,0xA9,0x99,0xA8,0x09,0x01,0x11,0x34,0x25,0x23,0x33,0x51,0x22,0x31,
0x12,0x20,0x21,0x12,0x10,0x80,0x99,0x9A,0x99,0x99,0x88,0x08,0x00,0x88,0xA9,0x99,
0x99,0x80,0x80,0x10,0x01,0x00,0x9A,0xAA,0xBB,0xBA,0xBA,0xA9,0x99,0x99,0x89,0x99,
0x99,0x00,0x01,0x33,0x35,0x24,0x23,0x34,0x23,0x33,0x34,0x33,0x43,0x32,0x21,0x88,
0xAB,0xBD,0xBB,0xDB,0xAB,0xBA,0xBB,0xDA,0xBB,0xCB,0xBB,0xBC,0xA8,0x90,0x01,0x12,
0x23,0x43,0x53,0x34,0x34,0x39,0x80,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00,
/* Source: 20RIM.ROM */
/* Length: 128 / 0x00000080 */
0x0F,0xFF,0x73,0x8E,0x71,0xCD,0x00,0x49,0x10,0x90,0x21,0x49,0xA0,0xDB,0x02,0x3A,
0xE3,0x0A,0x50,0x98,0xC0,0x59,0xA2,0x99,0x09,0x22,0xA2,0x80,0x10,0xA8,0x5B,0xD2,
0x88,0x21,0x09,0x96,0xA8,0x10,0x0A,0xE0,0x08,0x48,0x19,0xAB,0x52,0xA8,0x92,0x0C,
0x03,0x19,0xE2,0x0A,0x12,0xC2,0x81,0x1E,0x01,0xD0,0x48,0x88,0x98,0x01,0x49,0x91,
0xAA,0x2C,0x25,0x89,0x88,0xB5,0x81,0xA2,0x9A,0x12,0x9E,0x38,0x3B,0x81,0x9B,0x59,
0x01,0x93,0xCA,0x4A,0x21,0xA0,0x3D,0x0A,0x39,0x3D,0x12,0xA8,0x3F,0x18,0x01,0x92,
0x1C,0x00,0xB2,0x48,0xB9,0x94,0xA3,0x19,0x4F,0x19,0xB2,0x32,0x90,0xBA,0x01,0xE6,
0x91,0x80,0xC1,0xA4,0x2A,0x08,0xA1,0xB1,0x25,0xD2,0x88,0x99,0x21,0x80,0x88,0x80,
};

View file

@ -0,0 +1,612 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Konami SCC emulation core
Konami SCC means "Sound Creative Chip", it's actually MSX MegaROM/RAM Mapper with 5 channel Wavetable sound generator.
It was first appeared at 1987, F-1 Spirit and Nemesis 2/Gradius 2 for MSX. then several MSX cartridges used that until 1990, Metal Gear 2: Solid Snake.
Even after MSX is discontinued, it was still used at some low-end arcade and amusement hardwares.
and some Third-party MSX utilities still support this due to its market shares.
There's 2 SCC types:
K051649 (or simply known as SCC)
This chip is used for MSX MegaROM Mapper, some arcade machines.
Channel 4 and 5 must be share waveform, other channels has its own waveforms.
K052539 (also known as SCC+)
This chip is used for MSX MegaRAM Mapper (Konami Sound Cartridges for Snatcher/SD Snatcher).
All channels can be has its own waveforms, and also has backward compatibility mode with K051649.
Based on:
https://www.msx.org/wiki/MegaROM_Mappers
https://www.msx.org/wiki/Konami_051649
https://www.msx.org/wiki/Konami_052539
http://bifi.msxnet.org/msxnet/tech/scc
http://bifi.msxnet.org/msxnet/tech/soundcartridge
K051649 Register Layout
--------------------------------------------------------------------
4000-bfff MegaROM Mapper
--------------------------------------------------------------------
Address Bit R/W Description
7654 3210
4000-5fff xxxx xxxx R Bank page 0
c000-dfff mirror of 4000-5fff
6000-7fff xxxx xxxx R Bank page 1
e000-ffff mirror of 6000-7fff
8000-9fff xxxx xxxx R Bank page 2
0000-1fff mirror of 8000-9fff
a000-bfff xxxx xxxx R Bank page 3
2000-3fff mirror of a000-bfff
--------------------------------------------------------------------
5000-57ff, 7000-77ff, 9000-97ff, b000-b7ff Bank select
--------------------------------------------------------------------
Address Bit R/W Description
7654 3210
5000 --xx xxxx W Bank select, Page 0
5001-57ff Mirror of 5000
7000 --xx xxxx W Bank select, Page 1
7001-77ff Mirror of 7000
9000 --xx xxxx W Bank select, Page 2
--11 1111 W SCC Enable
9001-97ff Mirror of 9000
b000 --xx xxxx W Bank select, Page 3
b001-b7ff Mirror of b000
--------------------------------------------------------------------
9800-9fff SCC register
--------------------------------------------------------------------
9800-987f Waveform
Address Bit R/W Description
7654 3210
9800-981f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed)
9820-983f xxxx xxxx R/W Channel 1 ""
9840-985f xxxx xxxx R/W Channel 2 ""
9860-987f xxxx xxxx R/W Channel 3/4 ""
9880-9889 Pitch
9880 xxxx xxxx W Channel 0 Pitch LSB
9881 ---- xxxx W Channel 0 Pitch MSB
9882 xxxx xxxx W Channel 1 Pitch LSB
9883 ---- xxxx W Channel 1 Pitch MSB
9884 xxxx xxxx W Channel 2 Pitch LSB
9885 ---- xxxx W Channel 2 Pitch MSB
9886 xxxx xxxx W Channel 3 Pitch LSB
9887 ---- xxxx W Channel 3 Pitch MSB
9888 xxxx xxxx W Channel 4 Pitch LSB
9889 ---- xxxx W Channel 4 Pitch MSB
9888-988e Volume
988a ---- xxxx W Channel 0 Volume
988b ---- xxxx W Channel 1 Volume
988c ---- xxxx W Channel 2 Volume
988d ---- xxxx W Channel 3 Volume
988e ---- xxxx W Channel 4 Volume
988f ---x ---- W Channel 4 Output enable/disable flag
---- x--- W Channel 3 Output enable/disable flag
---- -x-- W Channel 2 Output enable/disable flag
---- --x- W Channel 1 Output enable/disable flag
---- ---x W Channel 0 Output enable/disable flag
9890-989f Mirror of 9880-988f
98a0-98bf xxxx xxxx R Channel 4 Waveform
98e0 x--- ---- W Waveform rotate flag for channel 4
-x-- ---- W Waveform rotate flag for all channels
--x- ---- W Reset waveform position after pitch writes
---- --x- W 8 bit frequency
---- --0x W 4 bit frequency
98e1-98ff Mirror of 98e0
9900-9fff Mirror of 9800-98ff
--------------------------------------------------------------------
K052539 Register Layout
--------------------------------------------------------------------
4000-bfff MegaRAM Mapper
--------------------------------------------------------------------
Address Bit R/W Description
7654 3210
4000-5fff xxxx xxxx R/W Bank page 0
c000-dfff xxxx xxxx R/W ""
6000-7fff xxxx xxxx R/W Bank page 1
e000-ffff xxxx xxxx R/W ""
8000-9fff xxxx xxxx R/W Bank page 2
0000-1fff xxxx xxxx R/W ""
a000-bfff xxxx xxxx R/W Bank page 3
2000-3fff xxxx xxxx R/W ""
--------------------------------------------------------------------
5000-57ff, 7000-77ff, 9000-97ff, b000-b7ff Bank select
--------------------------------------------------------------------
Address Bit R/W Description
7654 3210
5000 xxxx xxxx W Bank select, Page 0
5001-57ff Mirror of 5000
7000 xxxx xxxx W Bank select, Page 1
7001-77ff Mirror of 7000
9000 xxxx xxxx W Bank select, Page 2
--11 1111 W SCC Enable (SCC Compatible mode)
9001-97ff Mirror of 9000
b000 xxxx xxxx W Bank select, Page 3
1--- ---- W SCC+ Enable (SCC+ mode)
b001-b7ff Mirror of b000
--------------------------------------------------------------------
bffe-bfff Mapper configuration
--------------------------------------------------------------------
Address Bit R/W Description
7654 3210
bffe --x- ---- W SCC operation mode
--0- ---- W SCC Compatible mode
--1- ---- W SCC+ mode
---x ---- W RAM write/Bank select toggle for all Bank pages
---0 ---- W Bank select enable
---1 ---- W RAM write enable
---0 -x-- W RAM write/Bank select toggle for Bank page 2
---0 --x- W RAM write/Bank select toggle for Bank page 1
---0 ---x W RAM write/Bank select toggle for Bank page 0
bfff Mirror of bffe
--------------------------------------------------------------------
9800-9fff SCC Compatible mode register
--------------------------------------------------------------------
9800-987f Waveform
Address Bit R/W Description
7654 3210
9800-981f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed)
9820-983f xxxx xxxx R/W Channel 1 ""
9840-985f xxxx xxxx R/W Channel 2 ""
9860-987f xxxx xxxx R/W Channel 3/4 ""
9880-9889 Pitch
9880 xxxx xxxx W Channel 0 Pitch LSB
9881 ---- xxxx W Channel 0 Pitch MSB
9882 xxxx xxxx W Channel 1 Pitch LSB
9883 ---- xxxx W Channel 1 Pitch MSB
9884 xxxx xxxx W Channel 2 Pitch LSB
9885 ---- xxxx W Channel 2 Pitch MSB
9886 xxxx xxxx W Channel 3 Pitch LSB
9887 ---- xxxx W Channel 3 Pitch MSB
9888 xxxx xxxx W Channel 4 Pitch LSB
9889 ---- xxxx W Channel 4 Pitch MSB
9888-988e Volume
988a ---- xxxx W Channel 0 Volume
988b ---- xxxx W Channel 1 Volume
988c ---- xxxx W Channel 2 Volume
988d ---- xxxx W Channel 3 Volume
988e ---- xxxx W Channel 4 Volume
988f ---x ---- W Channel 4 Output enable/disable flag
---- x--- W Channel 3 Output enable/disable flag
---- -x-- W Channel 2 Output enable/disable flag
---- --x- W Channel 1 Output enable/disable flag
---- ---x W Channel 0 Output enable/disable flag
9890-989f Mirror of 9880-988f
98a0-98bf xxxx xxxx R Channel 4 Waveform
98c0 -x-- ---- W Waveform rotate flag for all channels
--x- ---- W Reset waveform position after pitch writes
---- --x- W 8 bit frequency
---- --0x W 4 bit frequency
98c1-98df Mirror of 98c0
9900-9fff Mirror of 9800-98ff
--------------------------------------------------------------------
b800-bfff SCC+ mode register
--------------------------------------------------------------------
b800-b89f Waveform
Address Bit R/W Description
7654 3210
b800-b81f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed)
b820-b83f xxxx xxxx R/W Channel 1 ""
b840-b85f xxxx xxxx R/W Channel 2 ""
b860-b87f xxxx xxxx R/W Channel 3 ""
b880-b89f xxxx xxxx R/W Channel 3 ""
b8a0-b8a9 Pitch
b8a0 xxxx xxxx W Channel 0 Pitch LSB
b8a1 ---- xxxx W Channel 0 Pitch MSB
b8a2 xxxx xxxx W Channel 1 Pitch LSB
b8a3 ---- xxxx W Channel 1 Pitch MSB
b8a4 xxxx xxxx W Channel 2 Pitch LSB
b8a5 ---- xxxx W Channel 2 Pitch MSB
b8a6 xxxx xxxx W Channel 3 Pitch LSB
b8a7 ---- xxxx W Channel 3 Pitch MSB
b8a8 xxxx xxxx W Channel 4 Pitch LSB
b8a9 ---- xxxx W Channel 4 Pitch MSB
b8a8-b8ae Volume
b8aa ---- xxxx W Channel 0 Volume
b8ab ---- xxxx W Channel 1 Volume
b8ac ---- xxxx W Channel 2 Volume
b8ad ---- xxxx W Channel 3 Volume
b8ae ---- xxxx W Channel 4 Volume
b8af ---x ---- W Channel 4 Output enable/disable flag
---- x--- W Channel 3 Output enable/disable flag
---- -x-- W Channel 2 Output enable/disable flag
---- --x- W Channel 1 Output enable/disable flag
---- ---x W Channel 0 Output enable/disable flag
b8b0-b8bf Mirror of b8a0-b8af
b8c0 -x-- ---- W Waveform rotate flag for all channels
--x- ---- W Reset waveform position after pitch writes
---- --x- W 8 bit frequency
---- --0x W 4 bit frequency
b8c1-b8df Mirror of b8c0
b900-bfff Mirror of b800-b8ff
--------------------------------------------------------------------
SCC Frequency calculation:
if 8 bit frequency then
Frequency = Input clock / ((bit 0 to 7 of Pitch input) + 1)
else if 4 bit frequency then
Frequency = Input clock / ((bit 8 to 11 of Pitch input) + 1)
else
Frequency = Input clock / (Pitch input + 1)
*/
#include "scc.hpp"
// shared SCC features
void scc_core::tick()
{
m_out = 0;
for (auto & elem : m_voice)
{
elem.tick();
m_out += elem.out;
}
}
void scc_core::voice_t::tick()
{
if (pitch >= 9) // or voice is halted
{
// update counter - Post decrement
u16 temp = counter;
if (m_host.m_test.freq_4bit) // 4 bit frequency mode
{
counter = (counter & ~0x0ff) | (bitfield(bitfield(counter, 0, 8) - 1, 0, 8) << 0);
counter = (counter & ~0xf00) | (bitfield(bitfield(counter, 8, 4) - 1, 0, 4) << 8);
}
else
counter = bitfield(counter - 1, 0, 12);
// handle counter carry
bool carry = m_host.m_test.freq_8bit ? (bitfield(temp, 0, 8) == 0) :
(m_host.m_test.freq_4bit ? (bitfield(temp, 8, 4) == 0) :
(bitfield(temp, 0, 12) == 0));
if (carry)
{
addr = bitfield(addr + 1, 0, 5);
counter = pitch;
}
}
// get output
if (enable)
out = (wave[addr] * volume) >> 4; // scale to 11 bit digital output
else
out = 0;
}
void scc_core::reset()
{
for (auto & elem : m_voice)
elem.reset();
m_test.reset();
m_out = 0;
std::fill(std::begin(m_reg), std::end(m_reg), 0);
}
void scc_core::voice_t::reset()
{
std::fill(std::begin(wave), std::end(wave), 0);
enable = false;
pitch = 0;
volume = 0;
addr = 0;
counter = 0;
out = 0;
}
// SCC accessors
u8 scc_core::wave_r(bool is_sccplus, u8 address)
{
u8 ret = 0xff;
const u8 voice = bitfield(address, 5, 3);
if (voice > 4)
return ret;
u8 wave_addr = bitfield(address, 0, 5);
if (m_test.rotate) // rotate flag
wave_addr = bitfield(wave_addr + m_voice[voice].addr, 0, 5);
if (!is_sccplus)
{
if (voice == 3) // rotate voice 3~4 flag
{
if (m_test.rotate4 || m_test.rotate) // rotate flag
wave_addr = bitfield(bitfield(address, 0, 5) + m_voice[3 + m_test.rotate].addr, 0, 5);
}
}
ret = m_voice[voice].wave[wave_addr];
return ret;
}
void scc_core::wave_w(bool is_sccplus, u8 address, u8 data)
{
if (m_test.rotate) // write protected
return;
const u8 voice = bitfield(address, 5, 3);
if (voice > 4)
return;
const u8 wave_addr = bitfield(address, 0, 5);
if (!is_sccplus)
{
if (((voice >= 3) && m_test.rotate4) || (voice >= 4)) // Ignore if write protected, or voice 4
return;
if (voice >= 3) // voice 3, 4 shares waveform
{
m_voice[3].wave[wave_addr] = data;
m_voice[4].wave[wave_addr] = data;
}
else
m_voice[voice].wave[wave_addr] = data;
}
else
m_voice[voice].wave[wave_addr] = data;
}
void scc_core::freq_vol_enable_w(u8 address, u8 data)
{
const u8 voice_freq = bitfield(address, 1, 3);
const u8 voice_reg = bitfield(address, 0, 4);
// *0-*f Pitch, Volume, Enable
switch (voice_reg)
{
case 0x0: // 0x*0 Voice 0 Pitch LSB
case 0x2: // 0x*2 Voice 1 Pitch LSB
case 0x4: // 0x*4 Voice 2 Pitch LSB
case 0x6: // 0x*6 Voice 3 Pitch LSB
case 0x8: // 0x*8 Voice 4 Pitch LSB
if (m_test.resetpos) // Reset address
m_voice[voice_freq].addr = 0;
m_voice[voice_freq].pitch = (m_voice[voice_freq].pitch & ~0x0ff) | data;
break;
case 0x1: // 0x*1 Voice 0 Pitch MSB
case 0x3: // 0x*3 Voice 1 Pitch MSB
case 0x5: // 0x*5 Voice 2 Pitch MSB
case 0x7: // 0x*7 Voice 3 Pitch MSB
case 0x9: // 0x*9 Voice 4 Pitch MSB
if (m_test.resetpos) // Reset address
m_voice[voice_freq].addr = 0;
m_voice[voice_freq].pitch = (m_voice[voice_freq].pitch & ~0xf00) | (u16(bitfield(data, 0, 4)) << 8);
break;
case 0xa: // 0x*a Voice 0 Volume
case 0xb: // 0x*b Voice 1 Volume
case 0xc: // 0x*c Voice 2 Volume
case 0xd: // 0x*d Voice 3 Volume
case 0xe: // 0x*e Voice 4 Volume
m_voice[voice_reg - 0xa].volume = bitfield(data, 0, 4);
break;
case 0xf: // 0x*f Enable/Disable flag
m_voice[0].enable = bitfield(data, 0);
m_voice[1].enable = bitfield(data, 1);
m_voice[2].enable = bitfield(data, 2);
m_voice[3].enable = bitfield(data, 3);
m_voice[4].enable = bitfield(data, 4);
break;
}
}
void k051649_scc_core::scc_w(bool is_sccplus, u8 address, u8 data)
{
const u8 voice = bitfield(address, 5, 3);
switch (voice)
{
case 0b000: // 0x00-0x1f Voice 0 Waveform
case 0b001: // 0x20-0x3f Voice 1 Waveform
case 0b010: // 0x40-0x5f Voice 2 Waveform
case 0b011: // 0x60-0x7f Voice 3/4 Waveform
wave_w(false, address, data);
break;
case 0b100: // 0x80-0x9f Pitch, Volume, Enable
freq_vol_enable_w(address, data);
break;
case 0b111: // 0xe0-0xff Test register
m_test.freq_4bit = bitfield(data, 0);
m_test.freq_8bit = bitfield(data, 1);
m_test.resetpos = bitfield(data, 5);
m_test.rotate = bitfield(data, 6);
m_test.rotate4 = bitfield(data, 7);
break;
}
m_reg[address] = data;
}
void k052539_scc_core::scc_w(bool is_sccplus, u8 address, u8 data)
{
const u8 voice = bitfield(address, 5, 3);
if (is_sccplus)
{
switch (voice)
{
case 0b000: // 0x00-0x1f Voice 0 Waveform
case 0b001: // 0x20-0x3f Voice 1 Waveform
case 0b010: // 0x40-0x5f Voice 2 Waveform
case 0b011: // 0x60-0x7f Voice 3 Waveform
case 0b100: // 0x80-0x9f Voice 4 Waveform
wave_w(true, address, data);
break;
case 0b101: // 0xa0-0xbf Pitch, Volume, Enable
freq_vol_enable_w(address, data);
break;
case 0b110: // 0xc0-0xdf Test register
m_test.freq_4bit = bitfield(data, 0);
m_test.freq_8bit = bitfield(data, 1);
m_test.resetpos = bitfield(data, 5);
m_test.rotate = bitfield(data, 6);
break;
default:
break;
}
}
else
{
switch (voice)
{
case 0b000: // 0x00-0x1f Voice 0 Waveform
case 0b001: // 0x20-0x3f Voice 1 Waveform
case 0b010: // 0x40-0x5f Voice 2 Waveform
case 0b011: // 0x60-0x7f Voice 3/4 Waveform
wave_w(false, address, data);
break;
case 0b100: // 0x80-0x9f Pitch, Volume, Enable
freq_vol_enable_w(address, data);
break;
case 0b110: // 0xc0-0xdf Test register
m_test.freq_4bit = bitfield(data, 0);
m_test.freq_8bit = bitfield(data, 1);
m_test.resetpos = bitfield(data, 5);
m_test.rotate = bitfield(data, 6);
break;
default:
break;
}
}
m_reg[address] = data;
}
u8 k051649_scc_core::scc_r(bool is_sccplus, u8 address)
{
const u8 voice = bitfield(address, 5, 3);
const u8 wave = bitfield(address, 0, 5);
u8 ret = 0xff;
switch (voice)
{
case 0b000: // 0x00-0x1f Voice 0 Waveform
case 0b001: // 0x20-0x3f Voice 1 Waveform
case 0b010: // 0x40-0x5f Voice 2 Waveform
case 0b011: // 0x60-0x7f Voice 3 Waveform
case 0b101: // 0xa0-0xbf Voice 4 Waveform
ret = wave_r(false, (std::min<u8>(4, voice) << 5) | wave);
break;
}
return ret;
}
u8 k052539_scc_core::scc_r(bool is_sccplus, u8 address)
{
const u8 voice = bitfield(address, 5, 3);
const u8 wave = bitfield(address, 0, 5);
u8 ret = 0xff;
if (is_sccplus)
{
switch (voice)
{
case 0b000: // 0x00-0x1f Voice 0 Waveform
case 0b001: // 0x20-0x3f Voice 1 Waveform
case 0b010: // 0x40-0x5f Voice 2 Waveform
case 0b011: // 0x60-0x7f Voice 3 Waveform
case 0b100: // 0x80-0x9f Voice 4 Waveform
ret = wave_r(true, address);
break;
}
}
else
{
switch (voice)
{
case 0b000: // 0x00-0x1f Voice 0 Waveform
case 0b001: // 0x20-0x3f Voice 1 Waveform
case 0b010: // 0x40-0x5f Voice 2 Waveform
case 0b011: // 0x60-0x7f Voice 3 Waveform
case 0b101: // 0xa0-0xbf Voice 4 Waveform
ret = wave_r(false, (std::min<u8>(4, voice) << 5) | wave);
break;
}
}
return ret;
}

View file

@ -0,0 +1,137 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Konami SCC emulation core
See scc.cpp for more info.
*/
#include <algorithm>
#include <memory>
#ifndef _VGSOUND_EMU_SCC_HPP
#define _VGSOUND_EMU_SCC_HPP
#pragma once
namespace scc
{
typedef unsigned char u8;
typedef signed char s8;
typedef unsigned short u16;
typedef signed short s16;
typedef unsigned int u32;
typedef signed int s32;
// get bitfield, bitfield(input, position, len)
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
{
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
}
}
using namespace scc;
// shared for SCCs
class scc_core
{
public:
// constructor
scc_core()
: m_voice{*this,*this,*this,*this,*this}
{};
virtual ~scc_core(){};
// accessors
virtual u8 scc_r(bool is_sccplus, u8 address) = 0;
virtual void scc_w(bool is_sccplus, u8 address, u8 data) = 0;
// internal state
virtual void reset();
void tick();
// getters
s32 out() { return m_out; } // output to DA0...DA10 pin
s32 chan_out(u8 ch) { return m_voice[ch].out; }
u8 reg(u8 address) { return m_reg[address]; }
protected:
// voice structs
struct voice_t
{
// constructor
voice_t(scc_core &host) : m_host(host) {};
// internal state
void reset();
void tick();
// registers
scc_core &m_host;
s8 wave[32] = {0}; // internal waveform
bool enable = false; // output enable flag
u16 pitch = 0; // pitch
u8 volume = 0; // volume
u8 addr = 0; // waveform pointer
u16 counter = 0; // frequency counter
s32 out = 0; // current output
};
voice_t m_voice[5]; // 5 voices
// accessor
u8 wave_r(bool is_sccplus, u8 address);
void wave_w(bool is_sccplus, u8 address, u8 data);
void freq_vol_enable_w(u8 address, u8 data);
struct test_t
{
// constructor
test_t()
: freq_4bit(0)
, freq_8bit(0)
, resetpos(0)
, rotate(0)
, rotate4(0)
{ };
void reset()
{
freq_4bit = 0;
freq_8bit = 0;
resetpos = 0;
rotate = 0;
rotate4 = 0;
}
u8 freq_4bit : 1; // 4 bit frequency
u8 freq_8bit : 1; // 8 bit frequency
u8 resetpos : 1; // reset counter after pitch writes
u8 rotate : 1; // rotate and write protect waveform for all channels
u8 rotate4 : 1; // same as above but for channel 4 only
};
test_t m_test; // test register
s32 m_out = 0; // output to DA0...10
u8 m_reg[256] = {0}; // register pool
};
// SCC core
class k051649_scc_core : public scc_core
{
public:
// accessors
virtual u8 scc_r(bool is_sccplus, u8 address) override;
virtual void scc_w(bool is_sccplus, u8 address, u8 data) override;
};
class k052539_scc_core : public k051649_scc_core
{
public:
// accessors
virtual u8 scc_r(bool is_sccplus, u8 address) override;
virtual void scc_w(bool is_sccplus, u8 address, u8 data) override;
};
#endif

View file

@ -465,6 +465,8 @@ public:
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
fm_engine* debug_fm_engine() { return &m_fm; }
protected:
// internal helpers
void update_prescale(uint8_t prescale);
@ -546,6 +548,12 @@ public:
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
// get the engine
fm_engine* debug_fm_engine() { return &m_fm; }
ssg_engine* debug_ssg_engine() { return &m_ssg; }
adpcm_a_engine* debug_adpcm_a_engine() { return &m_adpcm_a; }
adpcm_b_engine* debug_adpcm_b_engine() { return &m_adpcm_b; }
protected:
// internal helpers
void update_prescale(uint8_t prescale);

View file

@ -183,10 +183,11 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].pcm) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
DivSample* sample=parent->getSample(ins->amiga.initSample);
// TODO: sample map?
DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note));
if (sample!=NULL) {
double off=0.25;
if (sample->centerRate<1) {
@ -204,7 +205,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
if (chan[i].keyOn) {
if (chan[i].pcm) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
DivSample* sample=parent->getSample(ins->amiga.initSample);
DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note));
if (sample!=NULL) {
unsigned int sampleEnd=sample->offSU+sample->samples;
if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1;
@ -298,13 +299,13 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value*(1+(chan[c.chan].baseFreq>>9));
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9)));
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value*(1+(chan[c.chan].baseFreq>>9));
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9)));
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;

View file

@ -203,7 +203,7 @@ void DivPlatformSwan::tick(bool sysTick) {
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (i==1 && pcm && furnaceDac) {
double off=1.0;
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
@ -263,7 +263,7 @@ int DivPlatformSwan::dispatch(DivCommand c) {
dacPos=0;
dacPeriod=0;
if (ins->type==DIV_INS_AMIGA) {
dacSample=ins->amiga.initSample;
dacSample=ins->amiga.getSample(c.value);
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);
@ -448,6 +448,7 @@ void DivPlatformSwan::muteChannel(int ch, bool mute) {
}
void DivPlatformSwan::forceIns() {
noise=0;
for (int i=0; i<4; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;

View file

@ -151,16 +151,16 @@ int DivPlatformVERA::calcNoteFreq(int ch, int note) {
if (ch<16) {
return parent->calcBaseFreq(chipClock,2097152,note,false);
} else {
double off=1.0;
double off=65536.0;
if (chan[ch].pcm.sample>=0 && chan[ch].pcm.sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[ch].pcm.sample);
if (s->centerRate<1) {
off=1.0;
off=65536.0;
} else {
off=s->centerRate/8363.0;
off=65536.0*(s->centerRate/8363.0);
}
}
return (int)(off*parent->calcBaseFreq(chipClock,65536,note,false));
return (int)(parent->calcBaseFreq(chipClock,off,note,false));
}
}
@ -208,7 +208,7 @@ void DivPlatformVERA::tick(bool sysTick) {
chan[i].freqChanged=true;
}
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2,chipClock,2097152);
if (chan[i].freq>65535) chan[i].freq=65535;
rWrite(i,0,chan[i].freq&0xff);
rWrite(i,1,(chan[i].freq>>8)&0xff);
@ -237,7 +237,16 @@ void DivPlatformVERA::tick(bool sysTick) {
}
}
if (chan[16].freqChanged) {
chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8,chan[16].pitch2);
double off=65536.0;
if (chan[16].pcm.sample>=0 && chan[16].pcm.sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[16].pcm.sample);
if (s->centerRate<1) {
off=65536.0;
} else {
off=65536.0*(s->centerRate/8363.0);
}
}
chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8,chan[16].pitch2,chipClock,off);
if (chan[16].freq>128) chan[16].freq=128;
rWritePCMRate(chan[16].freq&0xff);
chan[16].freqChanged=false;
@ -249,9 +258,9 @@ int DivPlatformVERA::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON:
if (c.chan<16) {
rWriteLo(c.chan,2,chan[c.chan].vol)
rWriteLo(c.chan,2,chan[c.chan].vol);
} else {
chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.initSample;
chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.getSample(c.value);
if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) {
chan[16].pcm.sample=-1;
}
@ -297,12 +306,16 @@ int DivPlatformVERA::dispatch(DivCommand c) {
if (c.chan<16) {
tmp=c.value&0x3f;
chan[c.chan].vol=tmp;
if (chan[c.chan].active) {
rWriteLo(c.chan,2,tmp);
}
} else {
tmp=c.value&0x0f;
chan[c.chan].vol=tmp;
if (chan[c.chan].active) {
rWritePCMVol(tmp);
}
}
break;
case DIV_CMD_GET_VOLUME:
return chan[c.chan].vol;
@ -401,7 +414,7 @@ void DivPlatformVERA::muteChannel(int ch, bool mute) {
}
float DivPlatformVERA::getPostAmp() {
return 8.0f;
return 4.0f;
}
bool DivPlatformVERA::isStereo() {

View file

@ -132,7 +132,7 @@ void DivPlatformVIC20::tick(bool sysTick) {
chan[i].freqChanged=true;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (i<3) {
chan[i].freq>>=(2-i);
} else {

View file

@ -197,9 +197,9 @@ void DivPlatformVRC6::tick(bool sysTick) {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
if (i==2) { // sawtooth
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,14)-1;
} else { // pulse
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,16)-1;
if (chan[i].furnaceDac) {
double off=1.0;
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
@ -243,7 +243,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) {
if (chan[c.chan].pcm) {
if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].dacSample=ins->amiga.initSample;
chan[c.chan].dacSample=ins->amiga.getSample(c.value);
if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);

View file

@ -262,17 +262,17 @@ 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;
double off=8192.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;
off=8192.0;
} else {
off=s->centerRate/8363.0;
off=8192.0*(s->centerRate/8363.0);
}
}
return off*parent->calcBaseFreq(chipClock,8192,note,false);
return parent->calcBaseFreq(chipClock,off,note,false);
}
// Wavetable note
return NOTE_FREQUENCY(note);
@ -487,7 +487,19 @@ void DivPlatformX1_010::tick(bool sysTick) {
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,2,chan[i].pitch2);
double off=8192.0;
if (chan[i].pcm) {
int sample=chan[i].sample;
if (sample>=0 && sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(sample);
if (s->centerRate<1) {
off=8192.0;
} else {
off=8192.0*(s->centerRate/8363.0);
}
}
}
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,chan[i].pcm?off:CHIP_FREQBASE);
if (chan[i].pcm) {
if (chan[i].freq<1) chan[i].freq=1;
if (chan[i].freq>255) chan[i].freq=255;
@ -547,7 +559,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
if (chan[c.chan].furnacePCM) {
chan[c.chan].pcm=true;
chan[c.chan].macroInit(ins);
chan[c.chan].sample=ins->amiga.initSample;
chan[c.chan].sample=ins->amiga.getSample(c.value);
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);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,132 @@
/**
* 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 _YM2203_H
#define _YM2203_H
#include "../dispatch.h"
#include "../macroInt.h"
#include <queue>
#include "sound/ymfm/ymfm_opn.h"
#include "ay.h"
class DivYM2203Interface: public ymfm::ymfm_interface {
};
class DivPlatformYM2203: public DivDispatch {
protected:
const unsigned short chanOffs[3]={
0x00, 0x01, 0x02
};
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
unsigned char psgMode, autoEnvNum, autoEnvDen;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
int vol, outVol;
int sample;
DivMacroInt std;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
Channel():
freqH(0),
freqL(0),
freq(0),
baseFreq(0),
pitch(0),
pitch2(0),
portaPauseFreq(0),
note(0),
ins(-1),
psgMode(1),
autoEnvNum(0),
autoEnvDen(0),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
portaPause(false),
inPorta(false),
furnacePCM(false),
hardReset(false),
vol(0),
outVol(15),
sample(-1) {}
};
Channel chan[6];
DivDispatchOscBuffer* oscBuf[6];
bool isMuted[6];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
ymfm::ym2203* fm;
ymfm::ym2203::output_data fmout;
DivYM2203Interface iface;
unsigned char regPool[512];
unsigned char lastBusy;
DivPlatformAY8910* ay;
unsigned char sampleBank;
int delay;
bool extMode;
short oldWrites[256];
short pendingWrites[256];
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);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool isStereo();
bool keyOffAffectsArp(int ch);
void notifyInsChange(int ins);
void notifyInsDeletion(void* ins);
void setSkipRegisterWrites(bool val);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
void setFlags(unsigned int flags);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformYM2203();
};
#endif

View file

@ -0,0 +1,574 @@
/**
* 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 "ym2610bext.h"
#include "../engine.h"
#include <math.h>
#include "ym2610shared.h"
#include "fmshared_OPN.h"
int DivPlatformYM2610BExt::dispatch(DivCommand c) {
if (c.chan<2) {
return DivPlatformYM2610B::dispatch(c);
}
if (c.chan>5) {
c.chan-=3;
return DivPlatformYM2610B::dispatch(c);
}
int ch=c.chan-2;
int ordch=orderedOps[ch];
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
// TODO: how does this work?!
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127));
}
}
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+0x60,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+0x70,op.d2r&31);
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+0x90,op.ssgEnv&15);
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
}
opChan[ch].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
opChan[ch].portaPause=false;
opChan[ch].freqChanged=true;
}
opChan[ch].keyOn=true;
opChan[ch].active=true;
break;
}
case DIV_CMD_NOTE_OFF:
opChan[ch].keyOff=true;
opChan[ch].keyOn=false;
opChan[ch].active=false;
break;
case DIV_CMD_VOLUME: {
opChan[ch].vol=c.value;
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127));
}
break;
}
case DIV_CMD_GET_VOLUME: {
return opChan[ch].vol;
break;
}
case DIV_CMD_INSTRUMENT:
if (opChan[ch].ins!=c.value || c.value2==1) {
opChan[ch].insChanged=true;
}
opChan[ch].ins=c.value;
break;
case DIV_CMD_PANNING: {
if (c.value==0 && c.value2==0) {
opChan[ch].pan=3;
} else {
opChan[ch].pan=(c.value2>0)|((c.value>0)<<1);
}
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (parent->song.sharedExtStat) {
for (int i=0; i<4; i++) {
if (ch==i) continue;
opChan[i].pan=opChan[ch].pan;
}
}
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
break;
}
case DIV_CMD_PITCH: {
opChan[ch].pitch=c.value;
opChan[ch].freqChanged=true;
break;
}
case DIV_CMD_NOTE_PORTA: {
if (parent->song.linearPitch==2) {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>opChan[ch].baseFreq) {
opChan[ch].baseFreq+=c.value;
if (opChan[ch].baseFreq>=destFreq) {
opChan[ch].baseFreq=destFreq;
return2=true;
}
} else {
opChan[ch].baseFreq-=c.value;
if (opChan[ch].baseFreq<=destFreq) {
opChan[ch].baseFreq=destFreq;
return2=true;
}
}
opChan[ch].freqChanged=true;
if (return2) {
//opChan[ch].inPorta=false;
return 2;
}
break;
}
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq;
bool return2=false;
if (opChan[ch].portaPause) {
opChan[ch].baseFreq=opChan[ch].portaPauseFreq;
}
if (destFreq>opChan[ch].baseFreq) {
newFreq=opChan[ch].baseFreq+c.value;
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=opChan[ch].baseFreq-c.value;
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;
}
}
// what the heck!
if (!opChan[ch].portaPause) {
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
}
}
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800);
}
}
}
opChan[ch].portaPause=false;
opChan[ch].freqChanged=true;
opChan[ch].baseFreq=newFreq;
if (return2) return 2;
break;
}
case DIV_CMD_LEGATO: {
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
opChan[ch].freqChanged=true;
break;
}
case DIV_CMD_FM_LFO: {
rWrite(0x22,(c.value&7)|((c.value>>4)<<3));
break;
}
case DIV_CMD_FM_MULT: { // TODO
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]];
rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4));
break;
}
case DIV_CMD_FM_TL: { // TODO
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (isOutput[ins->fm.alg][c.value]) {
rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127));
} else {
rWrite(baseAddr+0x40,c.value2);
}
break;
}
case DIV_CMD_FM_AR: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.ar=c.value2&31;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
}
} else {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.ar=c.value2&31;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
}
break;
}
case DIV_CMD_FM_RS: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.rs=c.value2&3;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.rs=c.value2&3;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
break;
}
case DIV_CMD_FM_AM: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.am=c.value2&1;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.am=c.value2&1;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
break;
}
case DIV_CMD_FM_DR: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.dr=c.value2&31;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.dr=c.value2&31;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
break;
}
case DIV_CMD_FM_SL: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.sl=c.value2&15;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.sl=c.value2&15;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
break;
}
case DIV_CMD_FM_RR: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.rr=c.value2&15;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.rr=c.value2&15;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
break;
}
case DIV_CMD_FM_D2R: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.d2r=c.value2&31;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.d2r=c.value2&31;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
}
break;
}
case DIV_CMD_FM_DT: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.dt=c.value&7;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.dt=c.value2&7;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
break;
}
case DIV_CMD_FM_SSG: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.ssgEnv=8^(c.value2&15);
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.ssgEnv=8^(c.value2&15);
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
break;
}
case DIV_CMD_GET_VOLMAX:
return 127;
break;
case DIV_ALWAYS_SET_VOLUME:
return 0;
break;
case DIV_CMD_PRE_PORTA:
break;
default:
//printf("WARNING: unimplemented command %d\n",c.cmd);
break;
}
return 1;
}
static int opChanOffsL[4]={
0xa9, 0xaa, 0xa8, 0xa2
};
static int opChanOffsH[4]={
0xad, 0xae, 0xac, 0xa6
};
void DivPlatformYM2610BExt::tick(bool sysTick) {
if (extMode) {
bool writeSomething=false;
unsigned char writeMask=2;
for (int i=0; i<4; i++) {
writeMask|=opChan[i].active<<(4+i);
if (opChan[i].keyOn || opChan[i].keyOff) {
writeSomething=true;
writeMask&=~(1<<(4+i));
opChan[i].keyOff=false;
}
}
if (writeSomething) {
immWrite(0x28,writeMask);
}
}
DivPlatformYM2610B::tick(sysTick);
bool writeNoteOn=false;
unsigned char writeMask=2;
if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) {
if (parent->song.linearPitch==2) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
} else {
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
int block=(opChan[i].baseFreq&0xf800)>>11;
if (fNum<0) fNum=0;
if (fNum>2047) {
while (block<7) {
fNum>>=1;
block++;
}
if (fNum>2047) fNum=2047;
}
opChan[i].freq=(block<<11)|fNum;
}
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
}
writeMask|=opChan[i].active<<(4+i);
if (opChan[i].keyOn) {
writeNoteOn=true;
writeMask|=1<<(4+i);
opChan[i].keyOn=false;
}
}
if (writeNoteOn) {
immWrite(0x28,writeMask);
}
}
void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) {
if (ch<2) {
DivPlatformYM2610B::muteChannel(ch,mute);
return;
}
if (ch>5) {
DivPlatformYM2610B::muteChannel(ch-3,mute);
return;
}
isOpMuted[ch-2]=mute;
int ordch=orderedOps[ch-2];
DivInstrument* ins=parent->getIns(opChan[ch-2].ins,DIV_INS_FM);
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
if (isOpMuted[ch-2]) {
rWrite(baseAddr+0x40,127);
} else if (isOutput[ins->fm.alg][ordch]) {
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch-2].vol&0x7f))/127));
} else {
rWrite(baseAddr+0x40,op.tl);
}
}
void DivPlatformYM2610BExt::forceIns() {
for (int i=0; i<6; i++) {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (i==2) { // extended channel
if (isOpMuted[j]) {
rWrite(baseAddr+0x40,127);
} else if (isOutput[chan[i].state.alg][j]) {
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[j].vol&0x7f))/127));
} else {
rWrite(baseAddr+0x40,op.tl);
}
} else {
if (isMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (isOutput[chan[i].state.alg][j]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
}
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
if (chan[i].active) {
chan[i].keyOn=true;
chan[i].freqChanged=true;
}
}
for (int i=6; i<16; i++) {
chan[i].insChanged=true;
}
ay->forceIns();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
for (int i=0; i<4; i++) {
opChan[i].insChanged=true;
if (opChan[i].active) {
opChan[i].keyOn=true;
opChan[i].freqChanged=true;
}
}
}
void* DivPlatformYM2610BExt::getChanState(int ch) {
if (ch>=6) return &chan[ch-3];
if (ch>=2) return &opChan[ch-2];
return &chan[ch];
}
DivDispatchOscBuffer* DivPlatformYM2610BExt::getOscBuffer(int ch) {
if (ch>=6) return oscBuf[ch-3];
if (ch<3) return oscBuf[ch];
return NULL;
}
void DivPlatformYM2610BExt::reset() {
DivPlatformYM2610B::reset();
for (int i=0; i<4; i++) {
opChan[i]=DivPlatformYM2610BExt::OpChannel();
opChan[i].vol=127;
}
// channel 2 mode
immWrite(0x27,0x40);
extMode=true;
}
bool DivPlatformYM2610BExt::keyOffAffectsArp(int ch) {
return (ch>8);
}
void DivPlatformYM2610BExt::notifyInsChange(int ins) {
DivPlatformYM2610B::notifyInsChange(ins);
for (int i=0; i<4; i++) {
if (opChan[i].ins==ins) {
opChan[i].insChanged=true;
}
}
}
int DivPlatformYM2610BExt::init(DivEngine* parent, int channels, int sugRate, unsigned int flags) {
DivPlatformYM2610B::init(parent,channels,sugRate,flags);
for (int i=0; i<4; i++) {
isOpMuted[i]=false;
}
reset();
return 19;
}
void DivPlatformYM2610BExt::quit() {
DivPlatformYM2610B::quit();
}
DivPlatformYM2610BExt::~DivPlatformYM2610BExt() {
}

View file

@ -0,0 +1,51 @@
/**
* 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 "../dispatch.h"
#include "ym2610b.h"
class DivPlatformYM2610BExt: public DivPlatformYM2610B {
struct OpChannel {
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
int vol;
unsigned char pan;
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
};
OpChannel opChan[4];
bool isOpMuted[4];
friend void putDispatchChan(void*,int,int);
public:
int dispatch(DivCommand c);
void* getChanState(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan);
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool keyOffAffectsArp(int ch);
void notifyInsChange(int ins);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformYM2610BExt();
};

View file

@ -0,0 +1,45 @@
/**
* 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.
*/
static unsigned short opOffs[4]={
0x00, 0x04, 0x08, 0x0c
};
static bool isOutput[8][4]={
// 1 3 2 4
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,true ,true},
{false,true ,true ,true},
{false,true ,true ,true},
{true ,true ,true ,true},
};
static unsigned char dtTable[8]={
7,6,5,0,1,2,3,4
};
static int orderedOps[4]={
0,2,1,3
};
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define CHIP_FREQBASE 4720270

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,148 @@
/**
* 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 _YM2608_H
#define _YM2608_H
#include "../dispatch.h"
#include "../macroInt.h"
#include <queue>
#include "sound/ymfm/ymfm_opn.h"
#include "ay.h"
class DivYM2608Interface: public ymfm::ymfm_interface {
public:
unsigned char* adpcmBMem;
int sampleBank;
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address);
void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data);
DivYM2608Interface(): adpcmBMem(NULL), sampleBank(0) {}
};
class DivPlatformYM2608: public DivDispatch {
protected:
const unsigned short chanOffs[6]={
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
};
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
unsigned char psgMode, autoEnvNum, autoEnvDen;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
int vol, outVol;
int sample;
unsigned char pan;
DivMacroInt std;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
Channel():
freqH(0),
freqL(0),
freq(0),
baseFreq(0),
pitch(0),
pitch2(0),
portaPauseFreq(0),
note(0),
ins(-1),
psgMode(1),
autoEnvNum(0),
autoEnvDen(0),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
portaPause(false),
inPorta(false),
furnacePCM(false),
hardReset(false),
vol(0),
outVol(15),
sample(-1),
pan(3) {}
};
Channel chan[16];
DivDispatchOscBuffer* oscBuf[16];
bool isMuted[16];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
ymfm::ym2608* fm;
ymfm::ym2608::output_data fmout;
unsigned char regPool[512];
unsigned char lastBusy;
unsigned char* adpcmBMem;
size_t adpcmBMemLen;
DivYM2608Interface iface;
DivPlatformAY8910* ay;
unsigned char sampleBank;
unsigned char writeRSSOff, writeRSSOn;
int delay;
bool extMode;
short oldWrites[512];
short pendingWrites[512];
double NOTE_OPNB(int ch, int note);
double NOTE_ADPCMB(int note);
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);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool isStereo();
bool keyOffAffectsArp(int ch);
void notifyInsChange(int ins);
void notifyInsDeletion(void* ins);
void setSkipRegisterWrites(bool val);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
const void* getSampleMem(int index);
size_t getSampleMemCapacity(int index);
size_t getSampleMemUsage(int index);
void renderSamples();
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformYM2608();
};
#endif

View file

@ -0,0 +1,42 @@
/**
* 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 "sound/ymfm/ymfm.h"
#include "ym2608.h"
#include "../engine.h"
#include "sound/rss.h"
uint8_t DivYM2608Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) {
switch (type) {
case ymfm::ACCESS_ADPCM_A:
return YM2608_ADPCM_ROM[address&0x1fff];
case ymfm::ACCESS_ADPCM_B:
if (adpcmBMem==NULL) {
return 0;
}
return adpcmBMem[address&0xffffff];
default:
return 0;
}
return 0;
}
void DivYM2608Interface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) {
}

View file

@ -456,13 +456,13 @@ double DivPlatformYM2610::NOTE_OPNB(int ch, int note) {
return NOTE_PERIODIC(note);
}
// FM
return NOTE_FREQUENCY(note);
return NOTE_FNUM_BLOCK(note,11);
}
double DivPlatformYM2610::NOTE_ADPCMB(int note) {
if (chan[13].sample>=0 && chan[13].sample<parent->song.sampleLen) {
double off=(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0;
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false);
double off=65535.0*(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0;
return parent->calcBaseFreq((double)chipClock/144,off,note,false);
}
return 0;
}
@ -470,7 +470,7 @@ double DivPlatformYM2610::NOTE_ADPCMB(int note) {
void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t len) {
static int os[2];
ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine();
ymfm::ym2610::fm_engine* fme=fm->debug_fm_engine();
ymfm::ssg_engine* ssge=fm->debug_ssg_engine();
ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine();
ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine();
@ -559,15 +559,15 @@ void DivPlatformYM2610::tick(bool sysTick) {
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
} else {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
chan[i].freqChanged=true;
}
}
@ -726,7 +726,12 @@ void DivPlatformYM2610::tick(bool sysTick) {
}
}
if (chan[13].freqChanged) {
chan[13].freq=parent->calcFreq(chan[13].baseFreq,chan[13].pitch,false,4);
if (chan[13].sample>=0 && chan[13].sample<parent->song.sampleLen) {
double off=65535.0*(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0;
chan[13].freq=parent->calcFreq(chan[13].baseFreq,chan[13].pitch,false,4,chan[13].pitch2,(double)chipClock/144,off);
} else {
chan[13].freq=0;
}
immWrite(0x19,chan[13].freq&0xff);
immWrite(0x1a,(chan[13].freq>>8)&0xff);
chan[13].freqChanged=false;
@ -742,11 +747,24 @@ void DivPlatformYM2610::tick(bool sysTick) {
for (int i=0; i<4; i++) {
if (i==1 && extMode) continue;
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
if (chan[i].freq>262143) chan[i].freq=262143;
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8);
immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff);
if (parent->song.linearPitch==2) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
} else {
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
int block=(chan[i].baseFreq&0xf800)>>11;
if (fNum<0) fNum=0;
if (fNum>2047) {
while (block<7) {
fNum>>=1;
block++;
}
if (fNum>2047) fNum=2047;
}
chan[i].freq=(block<<11)|fNum;
}
if (chan[i].freq>0x3fff) chan[i].freq=0x3fff;
immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8);
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
chan[i].freqChanged=false;
}
if (chan[i].keyOn) {
@ -756,47 +774,6 @@ void DivPlatformYM2610::tick(bool sysTick) {
}
}
int DivPlatformYM2610::octave(int freq) {
if (freq>=622.0f*128) {
return 128;
} else if (freq>=622.0f*64) {
return 64;
} else if (freq>=622.0f*32) {
return 32;
} else if (freq>=622.0f*16) {
return 16;
} else if (freq>=622.0f*8) {
return 8;
} else if (freq>=622.0f*4) {
return 4;
} else if (freq>=622.0f*2) {
return 2;
} else {
return 1;
}
return 1;
}
int DivPlatformYM2610::toFreq(int freq) {
if (freq>=622.0f*128) {
return 0x3800|((freq>>7)&0x7ff);
} else if (freq>=622.0f*64) {
return 0x3000|((freq>>6)&0x7ff);
} else if (freq>=622.0f*32) {
return 0x2800|((freq>>5)&0x7ff);
} else if (freq>=622.0f*16) {
return 0x2000|((freq>>4)&0x7ff);
} else if (freq>=622.0f*8) {
return 0x1800|((freq>>3)&0x7ff);
} else if (freq>=622.0f*4) {
return 0x1000|((freq>>2)&0x7ff);
} else if (freq>=622.0f*2) {
return 0x800|((freq>>1)&0x7ff);
} else {
return freq&0x7ff;
}
}
int DivPlatformYM2610::dispatch(DivCommand c) {
if (c.chan>3 && c.chan<7) {
c.chan-=4;
@ -818,7 +795,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x1b,chan[c.chan].outVol);
}
chan[c.chan].sample=ins->amiga.initSample;
chan[c.chan].sample=ins->amiga.getSample(c.value);
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(s->offB>>8)&0xff);
@ -863,8 +840,9 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144);
chan[c.chan].freqChanged=true;
int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0);
immWrite(0x19,freq&0xff);
immWrite(0x1a,(freq>>8)&0xff);
}
break;
}
@ -928,7 +906,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
chan[c.chan].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
chan[c.chan].portaPause=false;
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
@ -1020,12 +998,13 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
break;
}
case DIV_CMD_PITCH: {
if (c.chan==13 && !chan[c.chan].furnacePCM) break;
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
}
case DIV_CMD_NOTE_PORTA: {
if (c.chan>3) { // PSG, ADPCM-B
if (c.chan>3 || parent->song.linearPitch==2) { // PSG, ADPCM-B
int destFreq=NOTE_OPNB(c.chan,c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
@ -1048,33 +1027,48 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
}
break;
}
int destFreq=NOTE_FREQUENCY(c.value2);
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq;
bool return2=false;
if (chan[c.chan].portaPause) {
chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq;
}
if (destFreq>chan[c.chan].baseFreq) {
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
newFreq=chan[c.chan].baseFreq+c.value;
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
newFreq=chan[c.chan].baseFreq-c.value;
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;
}
}
// check for octave boundary
// what the heck!
if (!chan[c.chan].portaPause) {
if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) {
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
chan[c.chan].portaPause=true;
break;
}
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
chan[c.chan].portaPause=true;
break;
}
}
chan[c.chan].baseFreq=newFreq;
chan[c.chan].portaPause=false;
chan[c.chan].freqChanged=true;
if (return2) return 2;
chan[c.chan].baseFreq=newFreq;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_SAMPLE_BANK:
@ -1085,6 +1079,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
if (c.chan==13 && !chan[c.chan].furnacePCM) break;
chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value);
chan[c.chan].freqChanged=true;
break;

View file

@ -61,7 +61,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, note, ins;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
unsigned char psgMode, autoEnvNum, autoEnvDen;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
@ -80,6 +80,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
baseFreq(0),
pitch(0),
pitch2(0),
portaPauseFreq(0),
note(0),
ins(-1),
psgMode(1),
@ -125,8 +126,6 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
short oldWrites[512];
short pendingWrites[512];
int octave(int freq);
int toFreq(int freq);
double NOTE_OPNB(int ch, int note);
double NOTE_ADPCMB(int note);
friend void putDispatchChan(void*,int,int);

View file

@ -436,13 +436,13 @@ double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) {
return NOTE_PERIODIC(note);
}
// FM
return NOTE_FREQUENCY(note);
return NOTE_FNUM_BLOCK(note,11);
}
double DivPlatformYM2610B::NOTE_ADPCMB(int note) {
if (chan[15].sample>=0 && chan[15].sample<parent->song.sampleLen) {
double off=(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0;
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false);
double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0;
return parent->calcBaseFreq((double)chipClock/144,off,note,false);
}
return 0;
}
@ -450,7 +450,7 @@ double DivPlatformYM2610B::NOTE_ADPCMB(int note) {
void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) {
static int os[2];
ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine();
ymfm::ym2610b::fm_engine* fme=fm->debug_fm_engine();
ymfm::ssg_engine* ssge=fm->debug_ssg_engine();
ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine();
ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine();
@ -538,15 +538,15 @@ void DivPlatformYM2610B::tick(bool sysTick) {
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
} else {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
chan[i].freqChanged=true;
}
}
@ -704,7 +704,12 @@ void DivPlatformYM2610B::tick(bool sysTick) {
}
}
if (chan[15].freqChanged) {
chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4);
if (chan[15].sample>=0 && chan[15].sample<parent->song.sampleLen) {
double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0;
chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4,chan[15].pitch2,(double)chipClock/144,off);
} else {
chan[15].freq=0;
}
immWrite(0x19,chan[15].freq&0xff);
immWrite(0x1a,(chan[15].freq>>8)&0xff);
chan[15].freqChanged=false;
@ -720,11 +725,24 @@ void DivPlatformYM2610B::tick(bool sysTick) {
for (int i=0; i<6; i++) {
if (i==2 && extMode) continue;
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
if (chan[i].freq>262143) chan[i].freq=262143;
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8);
immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff);
if (parent->song.linearPitch==2) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
} else {
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2);
int block=(chan[i].baseFreq&0xf800)>>11;
if (fNum<0) fNum=0;
if (fNum>2047) {
while (block<7) {
fNum>>=1;
block++;
}
if (fNum>2047) fNum=2047;
}
chan[i].freq=(block<<11)|fNum;
}
if (chan[i].freq>0x3fff) chan[i].freq=0x3fff;
immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8);
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
chan[i].freqChanged=false;
}
if (chan[i].keyOn) {
@ -734,47 +752,6 @@ void DivPlatformYM2610B::tick(bool sysTick) {
}
}
int DivPlatformYM2610B::octave(int freq) {
if (freq>=622.0f*128) {
return 128;
} else if (freq>=622.0f*64) {
return 64;
} else if (freq>=622.0f*32) {
return 32;
} else if (freq>=622.0f*16) {
return 16;
} else if (freq>=622.0f*8) {
return 8;
} else if (freq>=622.0f*4) {
return 4;
} else if (freq>=622.0f*2) {
return 2;
} else {
return 1;
}
return 1;
}
int DivPlatformYM2610B::toFreq(int freq) {
if (freq>=622.0f*128) {
return 0x3800|((freq>>7)&0x7ff);
} else if (freq>=622.0f*64) {
return 0x3000|((freq>>6)&0x7ff);
} else if (freq>=622.0f*32) {
return 0x2800|((freq>>5)&0x7ff);
} else if (freq>=622.0f*16) {
return 0x2000|((freq>>4)&0x7ff);
} else if (freq>=622.0f*8) {
return 0x1800|((freq>>3)&0x7ff);
} else if (freq>=622.0f*4) {
return 0x1000|((freq>>2)&0x7ff);
} else if (freq>=622.0f*2) {
return 0x800|((freq>>1)&0x7ff);
} else {
return freq&0x7ff;
}
}
int DivPlatformYM2610B::dispatch(DivCommand c) {
if (c.chan>5 && c.chan<9) {
c.chan-=6;
@ -796,7 +773,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x1b,chan[c.chan].outVol);
}
chan[c.chan].sample=ins->amiga.initSample;
chan[c.chan].sample=ins->amiga.getSample(c.value);
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(s->offB>>8)&0xff);
@ -841,8 +818,9 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144);
chan[c.chan].freqChanged=true;
int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0);
immWrite(0x19,freq&0xff);
immWrite(0x1a,(freq>>8)&0xff);
}
break;
}
@ -906,7 +884,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
chan[c.chan].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
chan[c.chan].portaPause=false;
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
@ -998,12 +976,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
break;
}
case DIV_CMD_PITCH: {
if (c.chan==15 && !chan[c.chan].furnacePCM) break;
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
}
case DIV_CMD_NOTE_PORTA: {
if (c.chan>5) { // PSG, ADPCM-B
if (c.chan>5 || parent->song.linearPitch==2) { // PSG, ADPCM-B
int destFreq=NOTE_OPNB(c.chan,c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
@ -1026,33 +1005,48 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
}
break;
}
int destFreq=NOTE_FREQUENCY(c.value2);
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq;
bool return2=false;
if (chan[c.chan].portaPause) {
chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq;
}
if (destFreq>chan[c.chan].baseFreq) {
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
newFreq=chan[c.chan].baseFreq+c.value;
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
newFreq=chan[c.chan].baseFreq-c.value;
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;
}
}
// check for octave boundary
// what the heck!
if (!chan[c.chan].portaPause) {
if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) {
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
chan[c.chan].portaPause=true;
break;
}
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
chan[c.chan].portaPause=true;
break;
}
}
chan[c.chan].baseFreq=newFreq;
chan[c.chan].portaPause=false;
chan[c.chan].freqChanged=true;
if (return2) return 2;
chan[c.chan].baseFreq=newFreq;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_SAMPLE_BANK:
@ -1063,6 +1057,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
if (c.chan==15 && !chan[c.chan].furnacePCM) break;
chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value);
chan[c.chan].freqChanged=true;
break;

View file

@ -35,7 +35,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, note, ins;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
unsigned char psgMode, autoEnvNum, autoEnvDen;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
@ -54,6 +54,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
baseFreq(0),
pitch(0),
pitch2(0),
portaPauseFreq(0),
note(0),
ins(-1),
psgMode(1),
@ -98,8 +99,6 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
short oldWrites[512];
short pendingWrites[512];
int octave(int freq);
int toFreq(int freq);
double NOTE_OPNB(int ch, int note);
double NOTE_ADPCMB(int note);
friend void putDispatchChan(void*,int,int);

View file

@ -120,31 +120,74 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
if (parent->song.linearPitch==2) {
int destFreq=NOTE_FREQUENCY(c.value2);
int newFreq;
bool return2=false;
if (destFreq>opChan[ch].baseFreq) {
newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq);
opChan[ch].baseFreq+=c.value;
if (opChan[ch].baseFreq>=destFreq) {
opChan[ch].baseFreq=destFreq;
return2=true;
}
} else {
opChan[ch].baseFreq-=c.value;
if (opChan[ch].baseFreq<=destFreq) {
opChan[ch].baseFreq=destFreq;
return2=true;
}
}
opChan[ch].freqChanged=true;
if (return2) {
//opChan[ch].inPorta=false;
return 2;
}
break;
}
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq;
bool return2=false;
if (opChan[ch].portaPause) {
opChan[ch].baseFreq=opChan[ch].portaPauseFreq;
}
if (destFreq>opChan[ch].baseFreq) {
newFreq=opChan[ch].baseFreq+c.value;
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq);
newFreq=opChan[ch].baseFreq-c.value;
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;
}
}
// what the heck!
if (!opChan[ch].portaPause) {
if (octave(opChan[ch].baseFreq)!=octave(newFreq)) {
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
}
}
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800);
}
}
}
opChan[ch].baseFreq=newFreq;
opChan[ch].portaPause=false;
opChan[ch].freqChanged=true;
opChan[ch].baseFreq=newFreq;
if (return2) return 2;
break;
}
@ -364,14 +407,24 @@ void DivPlatformYM2610BExt::tick(bool sysTick) {
unsigned char writeMask=2;
if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,octave(opChan[i].baseFreq),opChan[i].pitch2);
if (opChan[i].freq>262143) opChan[i].freq=262143;
int freqt=toFreq(opChan[i].freq);
opChan[i].freqH=freqt>>8;
opChan[i].freqL=freqt&0xff;
immWrite(opChanOffsH[i],opChan[i].freqH);
immWrite(opChanOffsL[i],opChan[i].freqL);
opChan[i].freqChanged=false;
if (parent->song.linearPitch==2) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
} else {
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
int block=(opChan[i].baseFreq&0xf800)>>11;
if (fNum<0) fNum=0;
if (fNum>2047) {
while (block<7) {
fNum>>=1;
block++;
}
if (fNum>2047) fNum=2047;
}
opChan[i].freq=(block<<11)|fNum;
}
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
}
writeMask|=opChan[i].active<<(4+i);
if (opChan[i].keyOn) {

View file

@ -25,12 +25,12 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B {
struct OpChannel {
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, ins;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
int vol;
unsigned char pan;
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
};
OpChannel opChan[4];
bool isOpMuted[4];

View file

@ -63,7 +63,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
opChan[ch].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
opChan[ch].portaPause=false;
opChan[ch].freqChanged=true;
}
@ -120,36 +120,79 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
if (parent->song.linearPitch==2) {
int destFreq=NOTE_FREQUENCY(c.value2);
int newFreq;
bool return2=false;
if (destFreq>opChan[ch].baseFreq) {
newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq);
opChan[ch].baseFreq+=c.value;
if (opChan[ch].baseFreq>=destFreq) {
opChan[ch].baseFreq=destFreq;
return2=true;
}
} else {
opChan[ch].baseFreq-=c.value;
if (opChan[ch].baseFreq<=destFreq) {
opChan[ch].baseFreq=destFreq;
return2=true;
}
}
opChan[ch].freqChanged=true;
if (return2) {
//opChan[ch].inPorta=false;
return 2;
}
break;
}
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq;
bool return2=false;
if (opChan[ch].portaPause) {
opChan[ch].baseFreq=opChan[ch].portaPauseFreq;
}
if (destFreq>opChan[ch].baseFreq) {
newFreq=opChan[ch].baseFreq+c.value;
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq);
newFreq=opChan[ch].baseFreq-c.value;
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;
}
}
// what the heck!
if (!opChan[ch].portaPause) {
if (octave(opChan[ch].baseFreq)!=octave(newFreq)) {
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
}
}
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800);
}
}
}
opChan[ch].baseFreq=newFreq;
opChan[ch].portaPause=false;
opChan[ch].freqChanged=true;
opChan[ch].baseFreq=newFreq;
if (return2) return 2;
break;
}
case DIV_CMD_LEGATO: {
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
opChan[ch].freqChanged=true;
break;
}
@ -364,14 +407,24 @@ void DivPlatformYM2610Ext::tick(bool sysTick) {
unsigned char writeMask=2;
if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,octave(opChan[i].baseFreq),opChan[i].pitch2);
if (opChan[i].freq>262143) opChan[i].freq=262143;
int freqt=toFreq(opChan[i].freq);
opChan[i].freqH=freqt>>8;
opChan[i].freqL=freqt&0xff;
immWrite(opChanOffsH[i],opChan[i].freqH);
immWrite(opChanOffsL[i],opChan[i].freqL);
opChan[i].freqChanged=false;
if (parent->song.linearPitch==2) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
} else {
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
int block=(opChan[i].baseFreq&0xf800)>>11;
if (fNum<0) fNum=0;
if (fNum>2047) {
while (block<7) {
fNum>>=1;
block++;
}
if (fNum>2047) fNum=2047;
}
opChan[i].freq=(block<<11)|fNum;
}
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
}
writeMask|=opChan[i].active<<(4+i);
if (opChan[i].keyOn) {

View file

@ -25,12 +25,12 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 {
struct OpChannel {
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, ins;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
int vol;
unsigned char pan;
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
};
OpChannel opChan[4];
bool isOpMuted[4];

View file

@ -0,0 +1,334 @@
/**
* 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 "zxbeeper.h"
#include "../engine.h"
#include <math.h>
#define CHIP_FREQBASE 8192*6
const char** DivPlatformZXBeeper::getRegisterSheet() {
return NULL;
}
const char* DivPlatformZXBeeper::getEffectName(unsigned char effect) {
switch (effect) {
case 0x10:
return "10xx: Change waveform";
break;
case 0x11:
return "11xx: Toggle noise mode";
break;
case 0x12:
return "12xx: Setup LFO (0: disabled; 1: 1x depth; 2: 16x depth; 3: 256x depth)";
break;
case 0x13:
return "13xx: Set LFO speed";
break;
case 0x17:
return "17xx: Toggle PCM mode";
break;
}
return NULL;
}
void DivPlatformZXBeeper::acquire(short* bufL, short* bufR, size_t start, size_t len) {
for (size_t h=start; h<start+len; h++) {
// clock here
bool o=false;
unsigned short oldPos=chan[curChan].sPosition;
if (sOffTimer) {
sOffTimer--;
o=true;
}
chan[curChan].sPosition+=chan[curChan].freq;
if (oldPos>chan[curChan].sPosition) {
if (!isMuted[curChan] && chan[curChan].outVol) sOffTimer+=chan[curChan].duty;
}
if (++curChan>=6) curChan=0;
bufL[h]=o?16384:0;
}
}
void DivPlatformZXBeeper::tick(bool sysTick) {
for (int i=0; i<6; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=((chan[i].vol&1)*MIN(1,chan[i].std.vol.val));
}
if (chan[i].std.duty.had) {
chan[i].duty=chan[i].std.duty.val;
chan[i].freqChanged=true;
}
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
} else {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
chan[i].freqChanged=true;
}
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-2048,2048);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
if (chan[i].active) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].freq>65535) chan[i].freq=65535;
}
if (chan[i].keyOn) {
//rWrite(16+i*5,0x80);
//chWrite(i,0x04,0x80|chan[i].vol);
}
if (chan[i].keyOff) {
chan[i].freq=0;
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
}
}
int DivPlatformZXBeeper::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroInit(ins);
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
chan[c.chan].pcm=false;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(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;
chan[c.chan].insChanged=true;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.vol.has) {
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_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(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_STD_NOISE_MODE:
chan[c.chan].duty=c.value;
break;
case DIV_CMD_SAMPLE_MODE:
chan[c.chan].pcm=c.value;
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_LEGATO:
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER));
}
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 1;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformZXBeeper::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
}
void DivPlatformZXBeeper::forceIns() {
for (int i=0; i<6; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
}
}
void* DivPlatformZXBeeper::getChanState(int ch) {
return &chan[ch];
}
DivDispatchOscBuffer* DivPlatformZXBeeper::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformZXBeeper::getRegisterPool() {
ulaOut=sOffTimer?0x10:0x08;
return &ulaOut;
}
int DivPlatformZXBeeper::getRegisterPoolSize() {
return 1;
}
void DivPlatformZXBeeper::reset() {
while (!writes.empty()) writes.pop();
memset(regPool,0,128);
for (int i=0; i<6; i++) {
chan[i]=DivPlatformZXBeeper::Channel();
chan[i].std.setEngine(parent);
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
lastPan=0xff;
memset(tempL,0,32*sizeof(int));
memset(tempR,0,32*sizeof(int));
cycles=0;
curChan=0;
sOffTimer=0;
sampleBank=0;
ulaOut=0;
}
bool DivPlatformZXBeeper::keyOffAffectsArp(int ch) {
return true;
}
void DivPlatformZXBeeper::notifyWaveChange(int wave) {
}
void DivPlatformZXBeeper::notifyInsDeletion(void* ins) {
for (int i=0; i<6; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformZXBeeper::setFlags(unsigned int flags) {
if (flags&1) { // technically there is no PAL PC Engine but oh well...
chipClock=COLOR_PAL*4.0/5.0;
} else {
chipClock=COLOR_NTSC;
}
rate=chipClock/4;
for (int i=0; i<6; i++) {
oscBuf[i]->rate=rate;
}
}
void DivPlatformZXBeeper::poke(unsigned int addr, unsigned short val) {
}
void DivPlatformZXBeeper::poke(std::vector<DivRegWrite>& wlist) {
}
int DivPlatformZXBeeper::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<6; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
reset();
return 6;
}
void DivPlatformZXBeeper::quit() {
for (int i=0; i<6; i++) {
delete oscBuf[i];
}
}
DivPlatformZXBeeper::~DivPlatformZXBeeper() {
}

View file

@ -0,0 +1,108 @@
/**
* 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 _ZXBEEPER_H
#define _ZXBEEPER_H
#include "../dispatch.h"
#include <queue>
#include "../macroInt.h"
class DivPlatformZXBeeper: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2, note;
int dacPeriod, dacRate;
unsigned int dacPos;
int dacSample, ins;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac;
signed char vol, outVol;
unsigned short sPosition;
unsigned char duty;
DivMacroInt std;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
Channel():
freq(0),
baseFreq(0),
pitch(0),
pitch2(0),
note(0),
dacPeriod(0),
dacRate(0),
dacPos(0),
dacSample(-1),
ins(-1),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
inPorta(false),
noise(false),
pcm(false),
furnaceDac(false),
vol(1),
outVol(1),
sPosition(0),
duty(64) {}
};
Channel chan[6];
DivDispatchOscBuffer* oscBuf[6];
bool isMuted[6];
struct QueuedWrite {
unsigned char addr;
unsigned char val;
QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {}
};
std::queue<QueuedWrite> writes;
unsigned char lastPan, ulaOut;
int cycles, curChan, sOffTimer, delay;
int tempL[32];
int tempR[32];
unsigned char sampleBank, lfoMode, lfoSpeed;
unsigned char regPool[128];
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);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
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();
~DivPlatformZXBeeper();
};
#endif

View file

@ -268,12 +268,35 @@ void DivEngine::processRow(int i, bool afterDelay) {
int whatRow=afterDelay?chan[i].delayRow:curRow;
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][whatOrder],false);
// pre effects
if (!afterDelay) for (int j=0; j<song.pat[i].effectCols; j++) {
if (!afterDelay) {
bool returnAfterPre=false;
for (int j=0; j<song.pat[i].effectCols; j++) {
short effect=pat->data[whatRow][4+(j<<1)];
short effectVal=pat->data[whatRow][5+(j<<1)];
if (effectVal==-1) effectVal=0;
if (effect==0xed && effectVal!=0) {
switch (effect) {
case 0x09: // speed 1
if (effectVal>0) speed1=effectVal;
break;
case 0x0f: // speed 2
if (effectVal>0) speed2=effectVal;
break;
case 0x0b: // change order
if (changeOrd==-1) {
changeOrd=effectVal;
changePos=0;
}
break;
case 0x0d: // next order
if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) {
changeOrd=-2;
changePos=effectVal;
}
break;
case 0xed: // delay
if (effectVal!=0) {
if (effectVal<=nextSpeed) {
chan[i].rowDelay=effectVal+1;
chan[i].delayOrder=whatOrder;
@ -283,11 +306,15 @@ void DivEngine::processRow(int i, bool afterDelay) {
} else {
chan[i].delayLocked=false;
}
return;
returnAfterPre=true;
} else {
chan[i].delayLocked=false;
}
}
break;
}
}
if (returnAfterPre) return;
}
if (chan[i].delayLocked) return;
@ -386,24 +413,6 @@ void DivEngine::processRow(int i, bool afterDelay) {
// per-system effect
if (!perSystemEffect(i,effect,effectVal)) switch (effect) {
case 0x09: // speed 1
if (effectVal>0) speed1=effectVal;
break;
case 0x0f: // speed 2
if (effectVal>0) speed2=effectVal;
break;
case 0x0b: // change order
if (changeOrd==-1) {
changeOrd=effectVal;
changePos=0;
}
break;
case 0x0d: // next order
if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) {
changeOrd=-2;
changePos=effectVal;
}
break;
case 0x08: // panning (split 4-bit)
chan[i].panL=(effectVal>>4)|(effectVal&0xf0);
chan[i].panR=(effectVal&15)|((effectVal&15)<<4);
@ -631,7 +640,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
chan[i].scheduledSlideReset=false;
chan[i].inPorta=false;
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0));
dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote));
dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote));
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
chan[i].inPorta=false;
@ -954,7 +963,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
}
if (!song.noSlidesOnFirstTick || !firstTick) {
if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) {
if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) {
if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) {
chan[i].portaSpeed=0;
chan[i].oldNote=chan[i].note;
chan[i].note=chan[i].portaNote;

View file

@ -65,7 +65,9 @@ enum DivSystem {
DIV_SYSTEM_MMC5,
DIV_SYSTEM_N163,
DIV_SYSTEM_OPN,
DIV_SYSTEM_OPN_EXT,
DIV_SYSTEM_PC98,
DIV_SYSTEM_PC98_EXT,
DIV_SYSTEM_OPL,
DIV_SYSTEM_OPL2,
DIV_SYSTEM_OPL3,
@ -307,7 +309,12 @@ struct DivSong {
// compatibility flags
bool limitSlides;
bool linearPitch;
// linear pitch
// 0: not linear
// 1: only pitch changes (04xy/E5xx) linear
// 2: full linear
unsigned char linearPitch;
unsigned char pitchSlideSpeed;
// loop behavior
// 0: reset on loop
// 1: fake reset on loop
@ -377,7 +384,6 @@ struct DivSong {
*/
void clearSamples();
/**
* unloads the song, freeing all memory associated with it.
* use before destroying the object.
@ -418,7 +424,8 @@ struct DivSong {
masterVol(1.0f),
tuning(440.0f),
limitSlides(false),
linearPitch(true),
linearPitch(2),
pitchSlideSpeed(4),
loopModality(0),
properNoiseLayout(false),
waveDutyIsVol(false),

View file

@ -734,6 +734,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YMU759]=new DivSysDef(
"Yamaha YMU759 (MA-2)", NULL, 0x01, 0x01, 17, true, false, 0, false,
"a chip which found its way inside mobile phones in the 2000's.\nas proprietary as it is, it passed away after losing to MP3 in the mobile hardware battle.",
{"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" }, // name
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM" }, // short
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM }, // type
@ -742,16 +743,19 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_GENESIS]=new DivSysDef(
"Sega Genesis/Mega Drive", "セガメガドライブ", 0x02, 0x02, 10, true, true, 0, true,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_GENESIS_EXT]=new DivSysDef(
"Sega Genesis Extended Channel 3", NULL, 0x42, 0x42, 13, true, true, 0, true,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_SMS]=new DivSysDef(
"TI SN76489", NULL, 0x03, 0x03, 4, false, true, 0x150, false,
"a square/noise sound chip found on the Sega Master System, ColecoVision, Tandy, TI's own 99/4A and a few other places.",
{"Square 1", "Square 2", "Square 3", "Noise"},
{"S1", "S2", "S3", "NO"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE},
@ -771,11 +775,13 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SMS_OPLL]=new DivSysDef(
"Sega Master System + FM Expansion", NULL, 0x43, 0x43, 13, true, true, 0, true,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_GB]=new DivSysDef(
"Game Boy", NULL, 0x04, 0x04, 4, false, true, 0x161, false,
"the most popular portable game console of the era.",
{"Pulse 1", "Pulse 2", "Wavetable", "Noise"},
{"S1", "S2", "WA", "NO"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE, DIV_CH_NOISE},
@ -804,6 +810,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_PCE]=new DivSysDef(
"PC Engine/TurboGrafx-16", NULL, 0x05, 0x05, 6, false, true, 0x161, false,
"an '80's game console with a wavetable sound chip, popular in Japan.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
@ -835,6 +842,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_NES]=new DivSysDef(
"NES (Ricoh 2A03)", NULL, 0x06, 0x06, 5, false, true, 0x161, false,
"also known as Famicom in Japan, it's the most well-known game console of the '80's.",
{"Pulse 1", "Pulse 2", "Triangle", "Noise", "PCM"},
{"S1", "S2", "TR", "NO", "PCM"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE, DIV_CH_NOISE, DIV_CH_PCM},
@ -866,16 +874,19 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_NES_VRC7]=new DivSysDef(
"NES + Konami VRC7", NULL, 0x46, 0x46, 11, true, true, 0, true,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_NES_FDS]=new DivSysDef(
"Famicom Disk System", NULL, 0, 0x86, 6, false, true, 0, true,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_C64_6581]=new DivSysDef(
"Commodore 64 (6581)", NULL, 0x47, 0x47, 3, false, true, 0, false,
"this computer is powered by the SID chip, which had synthesizer features like a filter and ADSR.",
{"Channel 1", "Channel 2", "Channel 3"},
{"CH1", "CH2", "CH3"},
{DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -887,6 +898,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_C64_8580]=new DivSysDef(
"Commodore 64 (8580)", NULL, 0x07, 0x07, 3, false, true, 0, false,
"this computer is powered by the SID chip, which had synthesizer features like a filter and ADSR.\nthis is the newer revision of the chip.",
{"Channel 1", "Channel 2", "Channel 3"},
{"CH1", "CH2", "CH3"},
{DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -898,6 +910,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_ARCADE]=new DivSysDef(
"DefleCade", NULL, 0x08, 0x08, 13, true, false, 0, true,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
@ -914,6 +927,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2610]=new DivSysDef(
"Neo Geo CD", NULL, 0x09, 0x09, 13, true, true, 0x151, false,
"like Neo Geo, but lacking the ADPCM-B channel since they couldn't connect the pins.",
{"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6"},
{"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -925,6 +939,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2610_EXT]=new DivSysDef(
"Neo Geo CD Extended Channel 2", NULL, 0x49, 0x49, 16, true, true, 0x151, false,
"like Neo Geo, but lacking the ADPCM-B channel since they couldn't connect the pins.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6"},
{"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"},
{DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -936,6 +951,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_AY8910]=new DivSysDef(
"AY-3-8910", NULL, 0x80, 0, 3, false, true, 0x151, false,
"this chip is everywhere! ZX Spectrum, MSX, Amstrad CPC, Intellivision, Vectrex...\nthe discovery of envelope bass helped it beat the SN76489 with ease.",
{"PSG 1", "PSG 2", "PSG 3"},
{"S1", "S2", "S3"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
@ -947,6 +963,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_AMIGA]=new DivSysDef(
"Amiga", NULL, 0x81, 0, 4, false, true, 0, false,
"a computer from the '80's with full sampling capabilities, giving it a sound ahead of its time.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -973,6 +990,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2151]=new DivSysDef(
"Yamaha YM2151 (OPM)", NULL, 0x82, 0, 8, true, false, 0x150, false,
"this was Yamaha's first integrated FM chip.\nit was used in several synthesizers, computers and arcade boards.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -998,6 +1016,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2612]=new DivSysDef(
"Yamaha YM2612 (OPN2)", NULL, 0x83, 0, 6, true, false, 0x150, false,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"},
{"F1", "F2", "F3", "F4", "F5", "F6"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1009,6 +1028,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_TIA]=new DivSysDef(
"Atari 2600", NULL, 0x84, 0, 2, false, true, 0, false,
"it's a challenge to make music on this chip which barely has musical capabilities...",
{"Channel 1", "Channel 2"},
{"CH1", "CH2"},
{DIV_CH_WAVE, DIV_CH_WAVE},
@ -1029,6 +1049,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SAA1099]=new DivSysDef(
"Philips SAA1099", NULL, 0x97, 0, 6, false, true, 0x171, false,
"supposedly an upgrade from the AY-3-8910, this was present on the Creative Music System (Game Blaster) and SAM Coupé.",
{"PSG 1", "PSG 2", "PSG 3", "PSG 4", "PSG 5", "PSG 6"},
{"S1", "S2", "S3", "S4", "S5", "S6"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
@ -1055,6 +1076,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_AY8930]=new DivSysDef(
"Microchip AY8930", NULL, 0x9a, 0, 3, false, true, 0x151, false,
"an improved version of the AY-3-8910 with a bigger frequency range, duty cycles, configurable noise and per-channel envelopes!",
{"PSG 1", "PSG 2", "PSG 3"},
{"S1", "S2", "S3"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
@ -1077,6 +1099,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_VIC20]=new DivSysDef(
"Commodore VIC-20", NULL, 0x85, 0, 4, false, true, 0, false,
"Commodore's successor to the PET.\nits square wave channels are more than just square...",
{"Low", "Mid", "High", "Noise"},
{"LO", "MID", "HI", "NO"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE},
@ -1087,6 +1110,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_PET]=new DivSysDef(
"Commodore PET", NULL, 0x86, 0, 1, false, true, 0, false,
"one channel of 1-bit wavetable which is better (and worse) than the PC Speaker.",
{"Wave"},
{"PET"},
{DIV_CH_PULSE},
@ -1097,6 +1121,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SNES]=new DivSysDef(
"SNES", NULL, 0x87, 0, 8, false, true, 0, false,
"FM? nah... samples! Nintendo's answer to Sega.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1105,6 +1130,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_VRC6]=new DivSysDef(
"Konami VRC6", NULL, 0x88, 0, 3, false, true, 0, false,
"an expansion chip for the Famicom, featuring a quirky sawtooth channel.",
{"VRC6 1", "VRC6 2", "VRC6 Saw"},
{"V1", "V2", "VS"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE},
@ -1138,6 +1164,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPLL]=new DivSysDef(
"Yamaha YM2413 (OPLL)", NULL, 0x89, 0, 9, true, false, 0x150, false,
"cost-reduced version of the OPL with 16 patches and only one of them is user-configurable.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1149,6 +1176,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_FDS]=new DivSysDef(
"Famicom Disk System (chip)", NULL, 0x8a, 0, 1, false, true, 0x161, false,
"a disk drive for the Famicom which also contains one wavetable channel.",
{"FDS"},
{"FDS"},
{DIV_CH_WAVE},
@ -1183,6 +1211,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_MMC5]=new DivSysDef(
"MMC5", NULL, 0x8b, 0, 3, false, true, 0, false,
"an expansion chip for the Famicom, featuring a little-known PCM channel.",
{"Pulse 1", "Pulse 2", "PCM"},
{"S1", "S2", "PCM"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM},
@ -1205,6 +1234,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_N163]=new DivSysDef(
"Namco 163", NULL, 0x8c, 0, 8, false, true, 0, false,
"an expansion chip for the Famicom, with full wavetable.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
@ -1259,23 +1289,56 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_OPN]=new DivSysDef(
"Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, false, 0, false,
"Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, true, 0x151, false,
"cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)",
{"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"},
{"F1", "F2", "F3", "S1", "S2", "S3"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY},
{},
fmHardResetEffectHandler,
fmPostEffectHandler
);
sysDefs[DIV_SYSTEM_OPN_EXT]=new DivSysDef(
"Yamaha YM2203 (OPN) Extended Channel 3", NULL, 0xb6, 0, 9, true, true, 0x151, false,
"cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "PSG 1", "PSG 2", "PSG 3"},
{"F1", "F2", "O1", "O2", "O3", "O4", "S1", "S2", "S3"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
{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},
{},
fmHardResetEffectHandler,
fmPostEffectHandler
);
sysDefs[DIV_SYSTEM_PC98]=new DivSysDef(
"Yamaha YM2608 (OPNA)", NULL, 0x8e, 0, 16, true, false, 0, false,
"Yamaha YM2608 (OPNA)", NULL, 0x8e, 0, 16, true, true, 0, false,
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.",
{"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"},
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM},
{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}
{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_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA},
{},
fmHardResetEffectHandler,
fmPostEffectHandler
);
sysDefs[DIV_SYSTEM_PC98_EXT]=new DivSysDef(
"Yamaha YM2608 (OPNA) Extended Channel 3", NULL, 0xb7, 0, 19, true, true, 0, false,
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies",
{"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", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM},
{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_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA},
{},
fmHardResetEffectHandler,
fmPostEffectHandler
);
sysDefs[DIV_SYSTEM_OPL]=new DivSysDef(
"Yamaha YM3526 (OPL)", NULL, 0x8f, 0, 9, true, false, 0x151, false,
"OPN, but what if you only had two operators, no stereo, no detune and a lower ADSR parameter range?",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1287,6 +1350,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPL2]=new DivSysDef(
"Yamaha YM3812 (OPL2)", NULL, 0x90, 0, 9, true, false, 0x151, false,
"OPL, but what if you had more waveforms to choose than the normal sine?",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1298,6 +1362,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPL3]=new DivSysDef(
"Yamaha YMF262 (OPL3)", NULL, 0x91, 0, 18, true, false, 0x151, false,
"OPL2, but what if you had twice the channels, 4-op mode, stereo and even more waveforms?",
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"},
{DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1309,6 +1374,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_MULTIPCM]=new DivSysDef(
"MultiPCM", NULL, 0x92, 0, 28, false, true, 0, false,
"how many channels of PCM do you want?\nMultiPCM: yes",
{"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"},
{"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"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1317,14 +1383,16 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_PCSPKR]=new DivSysDef(
"PC Speaker", NULL, 0x93, 0, 1, false, true, 0, false,
"good luck!",
{"Square"},
{"SQ"},
{DIV_CH_PULSE},
{DIV_INS_STD}
{DIV_INS_BEEPER}
);
sysDefs[DIV_SYSTEM_POKEY]=new DivSysDef(
"POKEY", NULL, 0x94, 0, 4, false, true, 0, false,
"TIA, but better and more flexible.\nused in the Atari 8-bit family of computers (400/800/XL/XE).",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
@ -1333,6 +1401,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_RF5C68]=new DivSysDef(
"Ricoh RF5C68", NULL, 0x95, 0, 8, false, true, 0, false,
"this is like SNES' sound chip but without interpolation and the rest of nice bits.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1341,6 +1410,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SWAN]=new DivSysDef(
"WonderSwan", NULL, 0x96, 0, 4, false, true, 0x171, false,
"developed by the same team under the Game Boy and the Virtual Boy...",
{"Wave", "Wave/PCM", "Wave", "Wave/Noise"},
{"CH1", "CH2", "CH3", "CH4"},
{DIV_CH_WAVE, DIV_CH_PCM, DIV_CH_WAVE, DIV_CH_NOISE},
@ -1372,6 +1442,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPZ]=new DivSysDef(
"Yamaha YM2414 (OPZ)", NULL, 0x98, 0, 8, true, false, 0, false,
"like OPM, but with more waveforms, fixed frequency mode and totally... undocumented.\nused in the Yamaha TX81Z and some other synthesizers.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1392,6 +1463,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_POKEMINI]=new DivSysDef(
"Pokémon Mini", NULL, 0x99, 0, 1, false, true, 0, false,
"this one is like PC Speaker but has duty cycles.",
{"Square"},
{"SQ"},
{DIV_CH_PULSE},
@ -1400,6 +1472,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SEGAPCM]=new DivSysDef(
"SegaPCM", NULL, 0x9b, 0, 16, false, true, 0x151, false,
"used in some Sega arcade boards (like OutRun), and usually paired with a YM2151.",
{"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"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1411,6 +1484,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_VBOY]=new DivSysDef(
"Virtual Boy", NULL, 0x9c, 0, 6, false, true, 0, false,
"a console which failed to sell well due to its headache-inducing features.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "NO"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_NOISE},
@ -1419,6 +1493,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_VRC7]=new DivSysDef(
"Konami VRC7", NULL, 0x9d, 0, 6, true, false, 0x151, false,
"like OPLL, but even more cost reductions applied. three less FM channels, and no drums mode...",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"},
{"F1", "F2", "F3", "F4", "F5", "F6"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1430,6 +1505,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2610B]=new DivSysDef(
"Yamaha YM2610B (OPNB-B)", NULL, 0x9e, 0, 16, true, false, 0x151, false,
"so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.",
{"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"},
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1441,6 +1517,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SFX_BEEPER]=new DivSysDef(
"ZX Spectrum Beeper", NULL, 0x9f, 0, 6, false, true, 0, false,
"the ZX Spectrum only had a basic beeper capable of...\n...a bunch of thin pulses and tons of other interesting stuff!\nFurnace provides a thin pulse system.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
@ -1449,6 +1526,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2612_EXT]=new DivSysDef(
"Yamaha YM2612 (OPN2) Extended Channel 3", NULL, 0xa0, 0, 9, true, false, 0x150, false,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1459,11 +1537,14 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SCC]=new DivSysDef(
"Konami SCC", NULL, 0xa1, 0, 5, false, true, 0, false,
"Konami SCC", NULL, 0xa1, 0, 5, false, true, 0x161, false,
"a wavetable chip made by Konami for use with the MSX.\nthe last channel shares its wavetable with the previous one though.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"},
{"CH1", "CH2", "CH3", "CH4", "CH5"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
{DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}
{DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC},
{},
waveOnlyEffectHandler
);
auto oplDrumsEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool {
@ -1482,6 +1563,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPL_DRUMS]=new DivSysDef(
"Yamaha YM3526 (OPL) with drums", NULL, 0xa2, 0, 11, true, false, 0x151, false,
"the OPL chip but with drums mode enabled.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"},
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -1493,6 +1575,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPL2_DRUMS]=new DivSysDef(
"Yamaha YM3812 (OPL2) with drums", NULL, 0xa3, 0, 11, true, false, 0x151, false,
"the OPL2 chip but with drums mode enabled.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"},
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -1504,6 +1587,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPL3_DRUMS]=new DivSysDef(
"Yamaha YMF262 (OPL3) with drums", NULL, 0xa4, 0, 20, true, false, 0x151, false,
"the OPL3 chip but with drums mode enabled.",
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "BD", "SD", "TM", "TP", "HH"},
{DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -1515,6 +1599,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2610_FULL]=new DivSysDef(
"Yamaha YM2610 (OPNB)", NULL, 0xa5, 0, 14, true, false, 0x151, false,
"this chip was used in SNK's Neo Geo arcade board and video game console.\nit's like OPNA but the rhythm channels are ADPCM channels and two FM channels went missing.",
{"FM 1", "FM 2", "FM 3", "FM 4", "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"},
{"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1526,6 +1611,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2610_FULL_EXT]=new DivSysDef(
"Yamaha YM2610 (OPNB) Extended Channel 2", NULL, 0xa6, 0, 17, true, false, 0x151, false,
"this chip was used in SNK's Neo Geo arcade board and video game console.\nit's like OPNA but the rhythm channels are ADPCM channels and two FM channels went missing.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "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"},
{"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
{DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1537,6 +1623,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPLL_DRUMS]=new DivSysDef(
"Yamaha YM2413 (OPLL) with drums", NULL, 0xa7, 0, 11, true, false, 0x150, false,
"the OPLL chips but with drums mode turned on.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"},
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -1548,6 +1635,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_LYNX]=new DivSysDef(
"Atari Lynx", NULL, 0xa8, 0, 4, false, true, 0, false,
"a portable console made by Atari. it has all of Atari's trademark waveforms.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
@ -1566,6 +1654,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_QSOUND]=new DivSysDef(
"Capcom QSound", NULL, 0xe0, 0, 19, false, true, 0x161, false,
"used in some of Capcom's arcade boards. surround-like sampled sound with echo.",
{"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -1596,6 +1685,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_VERA]=new DivSysDef(
"VERA", NULL, 0xac, 0, 17, false, true, 0, false,
"the chip used in a computer design created by The 8-Bit Guy.",
{"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"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM},
@ -1618,6 +1708,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2610B_EXT]=new DivSysDef(
"Yamaha YM2610B (OPNB-B) Extended Channel 3", NULL, 0xde, 0, 19, true, false, 0x151, false,
"so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.",
{"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"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1629,6 +1720,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SEGAPCM_COMPAT]=new DivSysDef(
"SegaPCM (compatible 5-channel mode)", NULL, 0xa9, 0, 5, false, true, 0x151, false,
"this is the same thing as SegaPCM, but only exposes 5 of the channels for compatibility with DefleMask.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"},
{"P1", "P2", "P3", "P4", "P5"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1640,6 +1732,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_X1_010]=new DivSysDef(
"Seta/Allumer X1-010", NULL, 0xb0, 0, 16, false, true, 0x171, false,
"a sound chip used in several Seta/Allumer-manufactured arcade boards with too many channels of wavetable sound, which also are capable of sampled sound.",
{"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"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
@ -1690,6 +1783,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_BUBSYS_WSG]=new DivSysDef(
"Konami Bubble System WSG", NULL, 0xad, 0, 2, false, true, 0, false,
"this is the wavetable part of the Bubble System, which also had two AY-3-8910s.",
{"Channel 1", "Channel 2"},
{"CH1", "CH2"},
{DIV_CH_WAVE, DIV_CH_WAVE},
@ -1701,6 +1795,7 @@ void DivEngine::registerSystems() {
// to Grauw: feel free to change this to 24 during development of OPL4's PCM part.
sysDefs[DIV_SYSTEM_OPL4]=new DivSysDef(
"Yamaha OPL4", NULL, 0xae, 0, 42, true, true, 0, false,
"like OPL3, but this time it also has a 24-channel version of MultiPCM.",
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"},
{DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1709,6 +1804,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPL4_DRUMS]=new DivSysDef(
"Yamaha OPL4 with drums", NULL, 0xaf, 0, 44, true, true, 0, false,
"the OPL4 but with drums mode turned on.",
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "BD", "SD", "TM", "TP", "HH", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"},
{DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1717,6 +1813,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_ES5506]=new DivSysDef(
"Ensoniq ES5506", NULL, 0xb1, 0, 32, false, true, 0, false,
"a sample chip used in the Gravis Ultrasound, popular in the PC (DOS) demoscene.",
{"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", "Channel 29", "Channel 30", "Channel 31", "Channel 32"},
{"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", "29", "30", "31", "32"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1725,30 +1822,42 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_Y8950]=new DivSysDef(
"Yamaha Y8950", NULL, 0xb2, 0, 10, true, false, 0, false,
"like OPL but with an ADPCM channel.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "PCM"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "PCM"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM},
{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_AMIGA}
{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_AMIGA},
{},
oplEffectHandler,
fmOPLPostEffectHandler
);
sysDefs[DIV_SYSTEM_Y8950_DRUMS]=new DivSysDef(
"Yamaha Y8950 with drums", NULL, 0xb3, 0, 12, true, false, 0, false,
"the Y8950 chip, in drums mode.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat", "PCM"},
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "PCM"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM},
{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_AMIGA}
{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_AMIGA},
{},
oplEffectHandler,
fmOPLPostEffectHandler
);
sysDefs[DIV_SYSTEM_SCC_PLUS]=new DivSysDef(
"Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0, false,
"Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0x161, false,
"this is a variant of Konami's SCC chip with the last channel's wavetable being independent.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"},
{"CH1", "CH2", "CH3", "CH4", "CH5"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
{DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}
{DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC},
{},
waveOnlyEffectHandler
);
sysDefs[DIV_SYSTEM_SOUND_UNIT]=new DivSysDef(
"tildearrow Sound Unit", NULL, 0xb5, 0, 8, false, true, 0, false,
"tildearrow's fantasy sound chip. put SID, AY and VERA in a blender, and you get this!",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
{DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -1758,6 +1867,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_MSM6295]=new DivSysDef(
"OKI MSM6295", NULL, 0xaa, 0, 4, false, true, 0, false,
"an ADPCM sound chip manufactured by OKI and used in many arcade boards.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1766,6 +1876,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_MSM6258]=new DivSysDef(
"OKI MSM6258", NULL, 0xab, 0, 1, false, true, 0, false,
"an ADPCM sound chip manufactured by OKI and used in the Sharp X68000.",
{"Sample"},
{"PCM"},
{DIV_CH_PCM},
@ -1774,6 +1885,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef(
"Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false,
"this is a system designed for testing purposes..",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
{DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},

View file

@ -530,6 +530,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
break;
}
break;
case DIV_SYSTEM_OPN:
w->writeC(5|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7:
@ -598,6 +603,71 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
break;
}
break;
case DIV_SYSTEM_SCC:
if (write.addr<0x80) {
w->writeC(0xd2);
w->writeC(baseAddr2|0);
w->writeC(write.addr&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr<0x8a) {
w->writeC(0xd2);
w->writeC(baseAddr2|1);
w->writeC((write.addr-0x80)&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr<0x8f) {
w->writeC(0xd2);
w->writeC(baseAddr2|2);
w->writeC((write.addr-0x8a)&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr<0x90) {
w->writeC(0xd2);
w->writeC(baseAddr2|3);
w->writeC((write.addr-0x8f)&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr>=0xe0) {
w->writeC(0xd2);
w->writeC(baseAddr2|5);
w->writeC((write.addr-0xe0)&0x7f);
w->writeC(write.val&0xff);
} else {
logW("SCC: writing to unmapped address %.2x!",write.addr);
}
break;
case DIV_SYSTEM_SCC_PLUS:
if (write.addr<0x80) {
w->writeC(0xd2);
w->writeC(baseAddr2|0);
w->writeC(write.addr&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr<0xa0) {
w->writeC(0xd2);
w->writeC(baseAddr2|4);
w->writeC(write.addr);
w->writeC(write.val&0xff);
} else if (write.addr<0xaa) {
w->writeC(0xd2);
w->writeC(baseAddr2|1);
w->writeC((write.addr-0xa0)&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr<0xaf) {
w->writeC(0xd2);
w->writeC(baseAddr2|2);
w->writeC((write.addr-0xaa)&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr<0xb0) {
w->writeC(0xd2);
w->writeC(baseAddr2|3);
w->writeC((write.addr-0xaf)&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr>=0xe0) {
w->writeC(0xd2);
w->writeC(baseAddr2|5);
w->writeC((write.addr-0xe0)&0x7f);
w->writeC(write.val&0xff);
} else {
logW("SCC+: writing to unmapped address %.2x!",write.addr);
}
break;
default:
logW("write not handled!");
break;
@ -884,6 +954,18 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
howManyChips++;
}
break;
case DIV_SYSTEM_OPN:
if (!hasOPN) {
hasOPN=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeDACSamples=true;
} else if (!(hasOPN&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasOPN|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7:
@ -988,6 +1070,24 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
howManyChips++;
}
break;
case DIV_SYSTEM_SCC:
case DIV_SYSTEM_SCC_PLUS:
if (!hasK051649) {
hasK051649=disCont[i].dispatch->chipClock;
if (song.system[i]==DIV_SYSTEM_SCC_PLUS) {
hasK051649|=0x80000000;
}
willExport[i]=true;
} else if (!(hasK051649&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasK051649|=0x40000000;
if (song.system[i]==DIV_SYSTEM_SCC_PLUS) {
hasK051649|=0x80000000;
}
howManyChips++;
}
break;
default:
break;
}

View file

@ -18,6 +18,8 @@
*/
#include "gui.h"
#include "imgui.h"
#include "intConst.h"
void FurnaceGUI::drawCompatFlags() {
if (nextWindow==GUI_WINDOW_COMPAT_FLAGS) {
@ -32,10 +34,6 @@ void FurnaceGUI::drawCompatFlags() {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves.");
}
ImGui::Checkbox("Linear pitch control",&e->song.linearPitch);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work in tonality space\nnon-linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work on frequency/period space");
}
ImGui::Checkbox("Proper noise layout on NES and PC Engine",&e->song.properNoiseLayout);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("use a proper noise channel note mapping (0-15) instead of a rather unusual compatible one.\nunlocks all noise frequencies on PC Engine.");
@ -126,6 +124,35 @@ void FurnaceGUI::drawCompatFlags() {
ImGui::SetTooltip("when enabled, the pitch macro of an instrument is in linear space.");
}
ImGui::Text("Pitch linearity:");
if (ImGui::RadioButton("None",e->song.linearPitch==0)) {
e->song.linearPitch=0;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("like ProTracker/FamiTracker");
}
if (ImGui::RadioButton("Partial (only 04xy/E5xx)",e->song.linearPitch==1)) {
e->song.linearPitch=1;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("like DefleMask");
}
if (ImGui::RadioButton("Full",e->song.linearPitch==2)) {
e->song.linearPitch=2;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("like Impulse Tracker");
}
if (e->song.linearPitch==2) {
ImGui::SameLine();
ImGui::SetNextItemWidth(120.0f*dpiScale);
if (ImGui::InputScalar("Pitch slide speed multiplier",ImGuiDataType_U8,&e->song.pitchSlideSpeed,&_ONE,&_ONE)) {
if (e->song.pitchSlideSpeed<1) e->song.pitchSlideSpeed=1;
if (e->song.pitchSlideSpeed>64) e->song.pitchSlideSpeed=64;
}
}
ImGui::Text("Loop modality:");
if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) {
e->song.loopModality=0;

View file

@ -21,10 +21,21 @@
#include "actionUtil.h"
void FurnaceGUI::startSelection(int xCoarse, int xFine, int y) {
void FurnaceGUI::startSelection(int xCoarse, int xFine, int y, bool fullRow) {
DETERMINE_FIRST_LAST;
if (xCoarse!=selStart.xCoarse || xFine!=selStart.xFine || y!=selStart.y) {
curNibble=false;
}
if (fullRow) {
selStart.xCoarse=firstChannel;
selStart.xFine=0;
selEnd.xCoarse=lastChannel-1;
selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2;
selStart.y=y;
selEnd.y=y;
} else {
cursor.xCoarse=xCoarse;
cursor.xFine=xFine;
cursor.y=y;
@ -34,16 +45,25 @@ void FurnaceGUI::startSelection(int xCoarse, int xFine, int y) {
selEnd.xCoarse=xCoarse;
selEnd.xFine=xFine;
selEnd.y=y;
}
selecting=true;
selectingFull=fullRow;
e->setMidiBaseChan(cursor.xCoarse);
}
void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y) {
void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y, bool fullRow) {
if (!selecting) return;
if (selectingFull) {
DETERMINE_LAST;
selEnd.xCoarse=lastChannel-1;
selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2;
selEnd.y=y;
} else {
selEnd.xCoarse=xCoarse;
selEnd.xFine=xFine;
selEnd.y=y;
}
}
void FurnaceGUI::finishSelection() {
// swap points if needed
@ -66,6 +86,7 @@ void FurnaceGUI::finishSelection() {
selEnd.xFine^=selStart.xFine;
}
selecting=false;
selectingFull=false;
// boundary check
int chanCount=e->getTotalChannelCount();

View file

@ -59,7 +59,7 @@ extern "C" {
#define BACKUP_FUR "/backup.fur"
#endif
#ifdef ANDROID
#ifdef IS_MOBILE
#define MOBILE_UI_DEFAULT true
#else
#define MOBILE_UI_DEFAULT false
@ -194,21 +194,6 @@ void FurnaceGUI::decodeKeyMap(std::map<int,int>& map, String source) {
}
}
void FurnaceGUI::encodeMMLStr(String& target, unsigned char* macro, unsigned char macroLen, signed char macroLoop, signed char macroRel) {
target="";
char buf[32];
for (int i=0; i<macroLen; i++) {
if (i==macroLoop) target+="| ";
if (i==macroRel) target+="/ ";
if (i==macroLen-1) {
snprintf(buf,31,"%d",macro[i]);
} else {
snprintf(buf,31,"%d ",macro[i]);
}
target+=buf;
}
}
void FurnaceGUI::encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex) {
target="";
char buf[32];
@ -290,53 +275,6 @@ void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int ma
}
}
void FurnaceGUI::decodeMMLStr(String& source, unsigned char* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel) {
int buf=0;
bool hasVal=false;
macroLen=0;
macroLoop=-1;
macroRel=-1;
for (char& i: source) {
switch (i) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
hasVal=true;
buf*=10;
buf+=i-'0';
break;
case ' ':
if (hasVal) {
hasVal=false;
macro[macroLen]=buf;
if (macro[macroLen]<macroMin) macro[macroLen]=macroMin;
if (macro[macroLen]>macroMax) macro[macroLen]=macroMax;
macroLen++;
buf=0;
}
break;
case '|':
if (macroLoop==-1) {
macroLoop=macroLen;
}
break;
case '/':
if (macroRel==-1) {
macroRel=macroLen;
}
break;
}
if (macroLen>=128) break;
}
if (hasVal && macroLen<128) {
hasVal=false;
macro[macroLen]=buf;
if (macro[macroLen]<macroMin) macro[macroLen]=macroMin;
if (macro[macroLen]>macroMax) macro[macroLen]=macroMax;
macroLen++;
buf=0;
}
}
void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel) {
int buf=0;
bool negaBuf=false;
@ -370,11 +308,29 @@ void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLe
}
break;
case '|':
if (hasVal) {
hasVal=false;
macro[macroLen]=negaBuf?-buf:buf;
negaBuf=false;
if (macro[macroLen]<macroMin) macro[macroLen]=macroMin;
if (macro[macroLen]>macroMax) macro[macroLen]=macroMax;
macroLen++;
buf=0;
}
if (macroLoop==-1) {
macroLoop=macroLen;
}
break;
case '/':
if (hasVal) {
hasVal=false;
macro[macroLen]=negaBuf?-buf:buf;
negaBuf=false;
if (macro[macroLen]<macroMin) macro[macroLen]=macroMin;
if (macro[macroLen]>macroMax) macro[macroLen]=macroMax;
macroLen++;
buf=0;
}
if (macroRel==-1) {
macroRel=macroLen;
}
@ -1507,6 +1463,19 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
dpiScale
);
break;
case GUI_FILE_YRW801_ROM_OPEN:
case GUI_FILE_TG100_ROM_OPEN:
case GUI_FILE_MU5_ROM_OPEN:
if (!dirExists(workingDirSample)) workingDirSample=getHomeDir();
hasOpened=fileDialog->openLoad(
"Load ROM",
{"compatible files", "*.rom *.bin",
"all files", ".*"},
"compatible files{.rom,.bin},.*",
workingDirROM,
dpiScale
);
break;
}
if (hasOpened) curFileDialog=type;
//ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard;
@ -2381,7 +2350,7 @@ bool FurnaceGUI::loop() {
}
sampleDragActive=false;
if (selecting) {
cursor=selEnd;
if (!selectingFull) cursor=selEnd;
finishSelection();
demandScrollX=true;
if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y &&
@ -2453,6 +2422,15 @@ bool FurnaceGUI::loop() {
wantCaptureKeyboard=ImGui::GetIO().WantTextInput;
if (wantCaptureKeyboard!=oldWantCaptureKeyboard) {
oldWantCaptureKeyboard=wantCaptureKeyboard;
if (wantCaptureKeyboard) {
SDL_StartTextInput();
} else {
SDL_StopTextInput();
}
}
if (wantCaptureKeyboard) {
WAKE_UP;
}
@ -2775,10 +2753,12 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("settings")) {
#ifndef IS_MOBILE
if (ImGui::MenuItem("full screen",BIND_FOR(GUI_ACTION_FULLSCREEN),fullScreen)) {
doAction(GUI_ACTION_FULLSCREEN);
}
if (ImGui::MenuItem("lock layout",NULL,lockLayout)) {
#endif
if (ImGui::MenuItem("lock layout (not working!)",NULL,lockLayout)) {
lockLayout=!lockLayout;
}
if (ImGui::MenuItem("visualizer",NULL,fancyPattern)) {
@ -3013,6 +2993,11 @@ bool FurnaceGUI::loop() {
case GUI_FILE_EXPORT_LAYOUT:
workingDirLayout=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_YRW801_ROM_OPEN:
case GUI_FILE_TG100_ROM_OPEN:
case GUI_FILE_MU5_ROM_OPEN:
workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
}
if (fileDialog->accepted()) {
fileName=fileDialog->getFileName();
@ -3226,6 +3211,15 @@ bool FurnaceGUI::loop() {
case GUI_FILE_EXPORT_LAYOUT:
exportLayout(copyOfName);
break;
case GUI_FILE_YRW801_ROM_OPEN:
settings.yrw801Path=copyOfName;
break;
case GUI_FILE_TG100_ROM_OPEN:
settings.tg100Path=copyOfName;
break;
case GUI_FILE_MU5_ROM_OPEN:
settings.mu5Path=copyOfName;
break;
}
curFileDialog=GUI_FILE_OPEN;
}
@ -3278,7 +3272,7 @@ bool FurnaceGUI::loop() {
}
ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
if (ImGui::BeginPopupModal("New Song",NULL,ImGuiWindowFlags_NoMove)) {
if (ImGui::BeginPopupModal("New Song",NULL,ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) {
ImGui::SetWindowPos(ImVec2(((scrW*dpiScale)-ImGui::GetWindowSize().x)*0.5,((scrH*dpiScale)-ImGui::GetWindowSize().y)*0.5));
drawNewSong();
ImGui::EndPopup();
@ -3657,8 +3651,18 @@ bool FurnaceGUI::init() {
tempoView=e->getConfBool("tempoView",true);
waveHex=e->getConfBool("waveHex",false);
lockLayout=e->getConfBool("lockLayout",false);
#ifdef IS_MOBILE
fullScreen=true;
#else
fullScreen=e->getConfBool("fullScreen",false);
#endif
mobileUI=e->getConfBool("mobileUI",MOBILE_UI_DEFAULT);
edit=e->getConfBool("edit",false);
followOrders=e->getConfBool("followOrders",true);
followPattern=e->getConfBool("followPattern",true);
orderEditMode=e->getConfInt("orderEditMode",0);
if (orderEditMode<0) orderEditMode=0;
if (orderEditMode>3) orderEditMode=3;
syncSettings();
@ -3666,6 +3670,8 @@ bool FurnaceGUI::init() {
dpiScale=settings.dpiScale;
}
initSystemPresets();
#if !(defined(__APPLE__) || defined(_WIN32))
unsigned char* furIcon=getFurnaceIcon();
SDL_Surface* icon=SDL_CreateRGBSurfaceFrom(furIcon,256,256,32,256*4,0xff,0xff00,0xff0000,0xff000000);
@ -3679,6 +3685,7 @@ bool FurnaceGUI::init() {
#endif
SDL_SetHint("SDL_HINT_VIDEO_ALLOW_SCREENSAVER","1");
SDL_SetHint("SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH","1");
SDL_Init(SDL_INIT_VIDEO);
@ -3841,6 +3848,10 @@ bool FurnaceGUI::finish() {
e->setConf("lockLayout",lockLayout);
e->setConf("fullScreen",fullScreen);
e->setConf("mobileUI",mobileUI);
e->setConf("edit",edit);
e->setConf("followOrders",followOrders);
e->setConf("followPattern",followPattern);
e->setConf("orderEditMode",orderEditMode);
for (int i=0; i<DIV_MAX_CHANS; i++) {
delete oldPat[i];
@ -3870,6 +3881,7 @@ FurnaceGUI::FurnaceGUI():
displayExporting(false),
vgmExportLoop(true),
wantCaptureKeyboard(false),
oldWantCaptureKeyboard(false),
displayMacroMenu(false),
displayNew(false),
fullScreen(false),
@ -3975,6 +3987,7 @@ FurnaceGUI::FurnaceGUI():
chanOscDocked(false),
*/
selecting(false),
selectingFull(false),
curNibble(false),
orderNibble(false),
followOrders(true),
@ -4134,8 +4147,6 @@ FurnaceGUI::FurnaceGUI():
valueKeys[SDLK_KP_8]=8;
valueKeys[SDLK_KP_9]=9;
initSystemPresets();
memset(willExport,1,32*sizeof(bool));
peak[0]=0;

View file

@ -257,7 +257,10 @@ enum FurnaceGUIFileDialogs {
GUI_FILE_IMPORT_LAYOUT,
GUI_FILE_EXPORT_COLORS,
GUI_FILE_EXPORT_KEYBINDS,
GUI_FILE_EXPORT_LAYOUT
GUI_FILE_EXPORT_LAYOUT,
GUI_FILE_YRW801_ROM_OPEN,
GUI_FILE_TG100_ROM_OPEN,
GUI_FILE_MU5_ROM_OPEN
};
enum FurnaceGUIWarnings {
@ -718,11 +721,14 @@ struct FurnaceGUISysDef {
struct FurnaceGUISysCategory {
const char* name;
const char* description;
std::vector<FurnaceGUISysDef> systems;
FurnaceGUISysCategory(const char* n):
name(n) {}
FurnaceGUISysCategory(const char* n, const char* d):
name(n),
description(d) {}
FurnaceGUISysCategory():
name(NULL) {}
name(NULL),
description(NULL) {}
};
struct FurnaceGUIMacroDesc {
@ -765,11 +771,11 @@ class FurnaceGUI {
bool updateSampleTex;
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile;
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout;
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirROM;
String mmlString[17];
String mmlStringW;
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, displayMacroMenu;
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
bool displayNew, fullScreen, preserveChanPos;
bool willExport[32];
int vgmExportVersion;
@ -823,6 +829,9 @@ class FurnaceGUI {
int saaCore;
int nesCore;
int fdsCore;
String yrw801Path;
String tg100Path;
String mu5Path;
int mainFont;
int patFont;
int audioRate;
@ -861,6 +870,7 @@ class FurnaceGUI {
int roundedButtons;
int roundedMenus;
int loadJapanese;
int loadChinese;
int fmLayout;
int sampleLayout;
int waveLayout;
@ -890,6 +900,7 @@ class FurnaceGUI {
int hiddenSystems;
int horizontalDataView;
int noMultiSystem;
int oldMacroVSlider;
unsigned int maxUndoSteps;
String mainFontPath;
String patFontPath;
@ -909,6 +920,9 @@ class FurnaceGUI {
saaCore(1),
nesCore(0),
fdsCore(0),
yrw801Path(""),
tg100Path(""),
mu5Path(""),
mainFont(0),
patFont(0),
audioRate(44100),
@ -945,6 +959,7 @@ class FurnaceGUI {
roundedButtons(1),
roundedMenus(0),
loadJapanese(0),
loadChinese(0),
fmLayout(0),
sampleLayout(0),
waveLayout(0),
@ -970,10 +985,11 @@ class FurnaceGUI {
powerSave(1),
absorbInsInput(0),
eventDelay(0),
moveWindowTitle(0),
moveWindowTitle(1),
hiddenSystems(0),
horizontalDataView(0),
noMultiSystem(0),
oldMacroVSlider(0),
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),
@ -1003,7 +1019,7 @@ class FurnaceGUI {
*/
SelectionPoint selStart, selEnd, cursor;
bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI;
bool selecting, selectingFull, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI;
bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, lockLayout, editOptsVisible, latchNibble, nonLatchNibble;
FurnaceGUIWindows curWindow, nextWindow, curWindowLast;
float peak[2];
@ -1108,7 +1124,7 @@ class FurnaceGUI {
ImVec2 patWindowPos, patWindowSize;
// pattern view specific
ImVec2 threeChars, twoChars;
ImVec2 fourChars, threeChars, twoChars;
SelectionPoint sel1, sel2;
int dummyRows, demandX;
int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax;
@ -1249,8 +1265,8 @@ class FurnaceGUI {
void commitSettings();
void processDrags(int dragX, int dragY);
void startSelection(int xCoarse, int xFine, int y);
void updateSelection(int xCoarse, int xFine, int y);
void startSelection(int xCoarse, int xFine, int y, bool fullRow=false);
void updateSelection(int xCoarse, int xFine, int y, bool fullRow=false);
void finishSelection();
void moveCursor(int x, int y, bool select);
@ -1307,8 +1323,6 @@ class FurnaceGUI {
void initSystemPresets();
void encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex=false);
void encodeMMLStr(String& target, unsigned char* macro, unsigned char macroLen, signed char macroLoop, signed char macroRel);
void decodeMMLStr(String& source, unsigned char* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel);
void decodeMMLStr(String& source, int* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel);
void decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMax, bool hex=false);

View file

@ -101,7 +101,7 @@ const char* insTypes[DIV_INS_MAX]={
"Konami SCC/Bubble System WSG",
"FM (OPZ)",
"POKEY",
"PC Beeper",
"Beeper",
"WonderSwan",
"Atari Lynx",
"VERA",
@ -831,14 +831,21 @@ const int availableSystems[]={
DIV_SYSTEM_AY8910,
DIV_SYSTEM_AMIGA,
DIV_SYSTEM_PCSPKR,
DIV_SYSTEM_SFX_BEEPER,
DIV_SYSTEM_YMU759,
DIV_SYSTEM_DUMMY,
DIV_SYSTEM_SOUND_UNIT,
DIV_SYSTEM_OPN,
DIV_SYSTEM_OPN_EXT,
DIV_SYSTEM_PC98,
DIV_SYSTEM_PC98_EXT,
DIV_SYSTEM_OPLL,
DIV_SYSTEM_OPLL_DRUMS,
DIV_SYSTEM_VRC7,
DIV_SYSTEM_OPL,
DIV_SYSTEM_OPL_DRUMS,
DIV_SYSTEM_Y8950,
DIV_SYSTEM_Y8950_DRUMS,
DIV_SYSTEM_OPL2,
DIV_SYSTEM_OPL2_DRUMS,
DIV_SYSTEM_OPL3,
@ -859,6 +866,8 @@ const int availableSystems[]={
DIV_SYSTEM_VRC6,
DIV_SYSTEM_FDS,
DIV_SYSTEM_MMC5,
DIV_SYSTEM_SCC,
DIV_SYSTEM_SCC_PLUS,
0 // don't remove this last one!
};

View file

@ -1077,7 +1077,7 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
float loopIndicator[256];
int index=0;
float reservedSpace=ImGui::GetStyle().ScrollbarSize;
float reservedSpace=(settings.oldMacroVSlider)?(20.0f*dpiScale+ImGui::GetStyle().ItemSpacing.x):ImGui::GetStyle().ScrollbarSize;
if (ImGui::BeginTable("MacroSpace",2)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0);
@ -1152,8 +1152,8 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
i.macro->vZoom=24;
i.macro->vScroll=120-12;
} else if (i.macro->name=="pitch") {
i.macro->vZoom=64;
i.macro->vScroll=1024-32;
i.macro->vZoom=128;
i.macro->vScroll=2048-64;
} else {
i.macro->vZoom=i.max-i.min;
i.macro->vScroll=0;
@ -1192,9 +1192,10 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
}
if (i.macro->open) {
if (ImGui::IsItemHovered() && ctrlWheeling) {
if (ImGui::IsItemHovered()) {
if (ctrlWheeling) {
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
i.macro->vZoom+=wheelY;
i.macro->vZoom+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vZoom<1) i.macro->vZoom=1;
if (i.macro->vZoom>(i.max-i.min)) i.macro->vZoom=i.max-i.min;
if ((i.macro->vScroll+i.macro->vZoom)>(i.max-i.min)) {
@ -1205,26 +1206,48 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
if (macroPointSize<1) macroPointSize=1;
if (macroPointSize>256) macroPointSize=256;
}
} else if ((ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) && wheelY!=0) {
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
}
// slider
if (!i.isBitfield) {
if (settings.oldMacroVSlider) {
ImGui::SameLine(0.0f);
if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) {
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
if (ImGui::IsItemHovered() && ctrlWheeling) {
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
} else {
ImS64 scrollV=(i.max-i.min-i.macro->vZoom)-i.macro->vScroll;
ImS64 availV=i.macro->vZoom;
ImS64 contentsV=(i.max-i.min);
ImGui::SameLine(0.0f);
ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x);
ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos());
scrollbarPos.Min.x-=ImGui::GetStyle().ItemSpacing.x;
scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize;
scrollbarPos.Max.x-=ImGui::GetStyle().ItemSpacing.x;
scrollbarPos.Max.y+=i.height*dpiScale;
ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale));
if (ImGui::IsItemHovered() && ctrlWheeling) {
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
if (ImGui::ScrollbarEx(scrollbarPos,ImGui::GetID("IMacroVScroll"),ImGuiAxis_Y,&scrollV,availV,contentsV,0)) {
i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV;
}
}
}
// loop area
PlotCustom("##IMacroLoop",loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,&macroHoverLoop);
@ -2851,7 +2874,7 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_GB) {
volMax=0;
}
if (ins->type==DIV_INS_PET) {
if (ins->type==DIV_INS_PET || ins->type==DIV_INS_BEEPER) {
volMax=1;
}
if (ins->type==DIV_INS_FDS) {
@ -2883,6 +2906,10 @@ void FurnaceGUI::drawInsEdit() {
dutyLabel="Duty/Int";
dutyMax=10;
}
if (ins->type==DIV_INS_BEEPER) {
dutyLabel="Pulse Width";
dutyMax=255;
}
if (ins->type==DIV_INS_AY8930) {
dutyMax=255;
}

View file

@ -18,6 +18,7 @@
*/
#include "gui.h"
#include <imgui.h>
void FurnaceGUI::drawNewSong() {
bool accepted=false;
@ -27,7 +28,11 @@ void FurnaceGUI::drawNewSong() {
ImGui::Text("Choose a System!");
ImGui::PopFont();
if (ImGui::BeginTable("sysPicker",2)) {
ImVec2 avail=ImGui::GetContentRegionAvail();
avail.y-=ImGui::GetFrameHeightWithSpacing();
if (ImGui::BeginChild("sysPickerC",avail,false,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) {
if (ImGui::BeginTable("sysPicker",2,ImGuiTableFlags_BordersInnerV)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0f);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0f);
@ -46,6 +51,9 @@ void FurnaceGUI::drawNewSong() {
if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { \
newSongCategory=index;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s",i.description);
}
index++;
}
@ -65,6 +73,8 @@ void FurnaceGUI::drawNewSong() {
ImGui::EndTable();
}
}
ImGui::EndChild();
if (ImGui::Button("I'm feeling lucky")) {
if (sysCategories.size()==0) {

View file

@ -88,11 +88,21 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
}
}
// row number
ImGui::PushStyleColor(ImGuiCol_Text,rowIndexColor);
if (settings.patRowsBase==1) {
ImGui::TextColored(rowIndexColor," %.2X ",i);
snprintf(id,31," %.2X ##PR_%d",i,i);
} else {
ImGui::TextColored(rowIndexColor,"%3d ",i);
snprintf(id,31,"%3d ##PR_%d",i,i);
}
ImGui::Selectable(id,false,ImGuiSelectableFlags_NoPadWithHalfSpacing,fourChars);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) {
updateSelection(0,0,i,true);
}
if (ImGui::IsItemClicked()) {
startSelection(0,0,i,true);
}
ImGui::PopStyleColor();
// for each column
for (int j=0; j<chans; j++) {
// check if channel is not hidden
@ -535,6 +545,7 @@ void FurnaceGUI::drawPattern() {
ImGui::TextColored(uiColors[GUI_COLOR_EE_VALUE]," %.2X",e->getExtValue());
}
float oneCharSize=ImGui::CalcTextSize("A").x;
fourChars=ImVec2(oneCharSize*4.0f,lineHeight);
threeChars=ImVec2(oneCharSize*3.0f,lineHeight);
twoChars=ImVec2(oneCharSize*2.0f,lineHeight);
//ImVec2 oneChar=ImVec2(oneCharSize,lineHeight);

View file

@ -31,27 +31,41 @@
// ));
void FurnaceGUI::initSystemPresets() {
sysCategories.clear();
FurnaceGUISysCategory cat;
cat=FurnaceGUISysCategory("FM");
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2612", {
DIV_SYSTEM_YM2612, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2612 (extended channel 3)", {
DIV_SYSTEM_YM2612_EXT, 64, 0, 0,
0
}
));
cat=FurnaceGUISysCategory("FM","chips which use frequency modulation (FM) to generate sound.\nsome of these also pack more (like square and sample channels).");
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2151", {
DIV_SYSTEM_YM2151, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2203", {
DIV_SYSTEM_OPN, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2203 (extended channel 3)", {
DIV_SYSTEM_OPN_EXT, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2608", {
DIV_SYSTEM_PC98, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2608 (extended channel 3)", {
DIV_SYSTEM_PC98_EXT, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2610", {
DIV_SYSTEM_YM2610_FULL, 64, 0, 0,
@ -76,6 +90,18 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2612", {
DIV_SYSTEM_YM2612, 64, 0, (int)0x80000000,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2612 (extended channel 3)", {
DIV_SYSTEM_YM2612_EXT, 64, 0, (int)0x80000000,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2413", {
DIV_SYSTEM_OPLL, 64, 0, 0,
@ -94,6 +120,18 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM3438", {
DIV_SYSTEM_YM2612, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM3438 (extended channel 3)", {
DIV_SYSTEM_YM2612_EXT, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM3526", {
DIV_SYSTEM_OPL, 64, 0, 0,
@ -106,6 +144,18 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha Y8950", {
DIV_SYSTEM_Y8950, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha Y8950 (drums mode)", {
DIV_SYSTEM_Y8950_DRUMS, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM3812", {
DIV_SYSTEM_OPL2, 64, 0, 0,
@ -130,11 +180,25 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
if (settings.hiddenSystems) {
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YMU759", {
DIV_SYSTEM_YMU759, 64, 0, 0,
0
}
));
}
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Square");
cat=FurnaceGUISysCategory("Square","these chips generate square/pulse tones only (but may include noise).");
cat.systems.push_back(FurnaceGUISysDef(
"TI SN76489", {
DIV_SYSTEM_SMS, 64, 0, 4,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Sega PSG (SN76489-like)", {
DIV_SYSTEM_SMS, 64, 0, 0,
0
}
@ -145,15 +209,33 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2149(F)", {
DIV_SYSTEM_AY8910, 64, 0, 16,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Philips SAA1099", {
DIV_SYSTEM_SAA1099, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"PC Speaker", {
DIV_SYSTEM_PCSPKR, 32, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Commodore VIC", {
DIV_SYSTEM_VIC20, 64, 0, 1,
0
}
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Sample");
cat=FurnaceGUISysCategory("Sample","chips/systems which use PCM or ADPCM samples for sound synthesis.");
cat.systems.push_back(FurnaceGUISysDef(
"Amiga", {
DIV_SYSTEM_AMIGA, 64, 0, 0,
@ -180,7 +262,129 @@ void FurnaceGUI::initSystemPresets() {
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Game consoles");
cat=FurnaceGUISysCategory("Wavetable","chips which use user-specified waveforms to generate sound.");
cat.systems.push_back(FurnaceGUISysDef(
"PC Engine", {
DIV_SYSTEM_PCE, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Commodore PET (pseudo-wavetable)", {
DIV_SYSTEM_PET, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Konami Bubble System WSG", {
DIV_SYSTEM_BUBSYS_WSG, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Konami SCC", {
DIV_SYSTEM_SCC, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Konami SCC+", {
DIV_SYSTEM_SCC_PLUS, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Namco 163", {
DIV_SYSTEM_N163, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Famicom Disk System (chip)", {
DIV_SYSTEM_FDS, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"WonderSwan", {
DIV_SYSTEM_SWAN, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Seta/Allumer X1-010", {
DIV_SYSTEM_X1_010, 64, 0, 0,
0
}
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Specialized","chips/systems with unique sound synthesis methods.");
cat.systems.push_back(FurnaceGUISysDef(
"MOS Technology SID (6581)", {
DIV_SYSTEM_C64_6581, 64, 0, 1,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"MOS Technology SID (8580)", {
DIV_SYSTEM_C64_8580, 64, 0, 1,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Microchip AY8930", {
DIV_SYSTEM_AY8930, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Game Boy", {
DIV_SYSTEM_GB, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Atari Lynx", {
DIV_SYSTEM_LYNX, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Atari TIA", {
DIV_SYSTEM_TIA, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Commander X16 (VERA only)", {
DIV_SYSTEM_VERA, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"ZX Spectrum (beeper only)", {
DIV_SYSTEM_SFX_BEEPER, 64, 0, 0,
0
}
));
if (settings.hiddenSystems) {
cat.systems.push_back(FurnaceGUISysDef(
"Dummy System", {
DIV_SYSTEM_DUMMY, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"tildearrow Sound Unit", {
DIV_SYSTEM_SOUND_UNIT, 64, 0, 0,
0
}
));
}
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Game consoles","let's play some chiptune making games!");
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis", {
DIV_SYSTEM_YM2612, 64, 0, 0,
@ -339,7 +543,7 @@ void FurnaceGUI::initSystemPresets() {
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Computers");
cat=FurnaceGUISysCategory("Computers","let's get to work on chiptune today.");
cat.systems.push_back(FurnaceGUISysDef(
"Commodore PET", {
DIV_SYSTEM_PET, 64, 0, 0,
@ -411,6 +615,20 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"MSX + MSX-AUDIO", {
DIV_SYSTEM_AY8910, 64, 0, 16,
DIV_SYSTEM_Y8950, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"MSX + MSX-AUDIO (drums mode)", {
DIV_SYSTEM_AY8910, 64, 0, 16,
DIV_SYSTEM_Y8950_DRUMS, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"MSX + MSX-MUSIC", {
DIV_SYSTEM_AY8910, 64, 0, 16,
@ -433,6 +651,43 @@ void FurnaceGUI::initSystemPresets() {
// per-channel mixer (soft panning, post processing) isn't emulated at all
0
}
));
"MSX + SCC", {
DIV_SYSTEM_AY8910, 64, 0, 16,
DIV_SYSTEM_SCC, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"MSX + SCC+", {
DIV_SYSTEM_AY8910, 64, 0, 16,
DIV_SYSTEM_SCC_PLUS, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"NEC PC-98 (with PC-9801-26K)", {
DIV_SYSTEM_OPN, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"NEC PC-98 (with PC-9801-26K; extended channel 3)", {
DIV_SYSTEM_OPN_EXT, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"NEC PC-98 (with PC-9801-86)", {
DIV_SYSTEM_PC98, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"NEC PC-98 (with PC-9801-86; extended channel 3)", {
DIV_SYSTEM_PC98_EXT, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"ZX Spectrum (48K)", {
@ -594,13 +849,13 @@ void FurnaceGUI::initSystemPresets() {
cat.systems.push_back(FurnaceGUISysDef(
"Commander X16", {
DIV_SYSTEM_VERA, 64, 0, 0,
DIV_SYSTEM_YM2151, 64, 0, 0,
DIV_SYSTEM_YM2151, 32, 0, 0,
0
}
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Arcade systems");
cat=FurnaceGUISysCategory("Arcade systems","INSERT COIN");
cat.systems.push_back(FurnaceGUISysDef(
"Bally Midway MCR", {
DIV_SYSTEM_AY8910, 64, 0, 0,
@ -682,7 +937,7 @@ void FurnaceGUI::initSystemPresets() {
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("DefleMask-compatible");
cat=FurnaceGUISysCategory("DefleMask-compatible","these configurations are compatible with DefleMask.\nselect this if you need to save as .dmf or work with that program.");
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis", {
DIV_SYSTEM_YM2612, 64, 0, 0,

View file

@ -883,6 +883,33 @@ void FurnaceGUI::drawSettings() {
ImGui::Text("FDS core");
ImGui::SameLine();
ImGui::Combo("##FDSCore",&settings.fdsCore,nesCores,2);
ImGui::Separator();
ImGui::Text("Sample ROMs:");
ImGui::Text("OPL4 YRW801 path");
ImGui::SameLine();
ImGui::InputText("##YRW801Path",&settings.yrw801Path);
ImGui::SameLine();
if (ImGui::Button(ICON_FA_FOLDER "##YRW801Load")) {
openFileDialog(GUI_FILE_YRW801_ROM_OPEN);
}
ImGui::Text("MultiPCM TG100 path");
ImGui::SameLine();
ImGui::InputText("##TG100Path",&settings.tg100Path);
ImGui::SameLine();
if (ImGui::Button(ICON_FA_FOLDER "##TG100Load")) {
openFileDialog(GUI_FILE_TG100_ROM_OPEN);
}
ImGui::Text("MultiPCM MU5 path");
ImGui::SameLine();
ImGui::InputText("##MU5Path",&settings.mu5Path);
ImGui::SameLine();
if (ImGui::Button(ICON_FA_FOLDER "##MU5Load")) {
openFileDialog(GUI_FILE_MU5_ROM_OPEN);
}
}
ImGui::EndChild();
ImGui::EndTabItem();
@ -951,6 +978,19 @@ void FurnaceGUI::drawSettings() {
);
}
bool loadChineseB=settings.loadChinese;
if (ImGui::Checkbox("Display Chinese (Simplified) characters",&loadChineseB)) {
settings.loadChinese=loadChineseB;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Only toggle this option if you have enough graphics memory.\n"
"This is a temporary solution until dynamic font atlas is implemented in Dear ImGui.\n\n"
"请在确保你有足够的显存后再启动此设定\n"
"这是一个在ImGui实现动态字体加载之前的临时解决方案"
);
}
ImGui::Separator();
ImGui::Text("Orders row number format:");
@ -1147,6 +1187,11 @@ void FurnaceGUI::drawSettings() {
settings.sampleLayout=sampleLayoutB;
}
bool oldMacroVSliderB=settings.oldMacroVSlider;
if (ImGui::Checkbox("Use classic macro editor vertical slider",&oldMacroVSliderB)) {
settings.oldMacroVSlider=oldMacroVSliderB;
}
bool roundedWindowsB=settings.roundedWindows;
if (ImGui::Checkbox("Rounded window corners",&roundedWindowsB)) {
settings.roundedWindows=roundedWindowsB;
@ -1784,6 +1829,9 @@ void FurnaceGUI::syncSettings() {
settings.saaCore=e->getConfInt("saaCore",1);
settings.nesCore=e->getConfInt("nesCore",0);
settings.fdsCore=e->getConfInt("fdsCore",0);
settings.yrw801Path=e->getConfString("yrw801Path","");
settings.tg100Path=e->getConfString("tg100Path","");
settings.mu5Path=e->getConfString("mu5Path","");
settings.mainFont=e->getConfInt("mainFont",0);
settings.patFont=e->getConfInt("patFont",0);
settings.mainFontPath=e->getConfString("mainFontPath","");
@ -1820,6 +1868,7 @@ void FurnaceGUI::syncSettings() {
settings.roundedButtons=e->getConfInt("roundedButtons",1);
settings.roundedMenus=e->getConfInt("roundedMenus",0);
settings.loadJapanese=e->getConfInt("loadJapanese",0);
settings.loadChinese=e->getConfInt("loadChinese",0);
settings.fmLayout=e->getConfInt("fmLayout",0);
settings.sampleLayout=e->getConfInt("sampleLayout",0);
settings.waveLayout=e->getConfInt("waveLayout",0);
@ -1845,10 +1894,11 @@ void FurnaceGUI::syncSettings() {
settings.powerSave=e->getConfInt("powerSave",POWER_SAVE_DEFAULT);
settings.absorbInsInput=e->getConfInt("absorbInsInput",0);
settings.eventDelay=e->getConfInt("eventDelay",0);
settings.moveWindowTitle=e->getConfInt("moveWindowTitle",0);
settings.moveWindowTitle=e->getConfInt("moveWindowTitle",1);
settings.hiddenSystems=e->getConfInt("hiddenSystems",0);
settings.horizontalDataView=e->getConfInt("horizontalDataView",0);
settings.noMultiSystem=e->getConfInt("noMultiSystem",0);
settings.oldMacroVSlider=e->getConfInt("oldMacroVSlider",0);
clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.patFontSize,2,96);
@ -1895,6 +1945,7 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.roundedButtons,0,1);
clampSetting(settings.roundedMenus,0,1);
clampSetting(settings.loadJapanese,0,1);
clampSetting(settings.loadChinese,0,1);
clampSetting(settings.fmLayout,0,3);
clampSetting(settings.susPosition,0,1);
clampSetting(settings.effectCursorDir,0,2);
@ -1920,7 +1971,8 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.moveWindowTitle,0,1);
clampSetting(settings.hiddenSystems,0,1);
clampSetting(settings.horizontalDataView,0,1);
clampSetting(settings.noMultiSystem,0,1)
clampSetting(settings.noMultiSystem,0,1);
clampSetting(settings.oldMacroVSlider,0,1);
settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys",""));
if (settings.initialSys.size()<4) {
@ -1953,6 +2005,10 @@ void FurnaceGUI::syncSettings() {
}
void FurnaceGUI::commitSettings() {
bool sampleROMsChanged = settings.yrw801Path!=e->getConfString("yrw801Path","") ||
settings.tg100Path!=e->getConfString("tg100Path","") ||
settings.mu5Path!=e->getConfString("mu5Path","");
e->setConf("mainFontSize",settings.mainFontSize);
e->setConf("patFontSize",settings.patFontSize);
e->setConf("iconSize",settings.iconSize);
@ -1968,6 +2024,9 @@ void FurnaceGUI::commitSettings() {
e->setConf("saaCore",settings.saaCore);
e->setConf("nesCore",settings.nesCore);
e->setConf("fdsCore",settings.fdsCore);
e->setConf("yrw801Path",settings.yrw801Path);
e->setConf("tg100Path",settings.tg100Path);
e->setConf("mu5Path",settings.mu5Path);
e->setConf("mainFont",settings.mainFont);
e->setConf("patFont",settings.patFont);
e->setConf("mainFontPath",settings.mainFontPath);
@ -2004,6 +2063,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("roundedButtons",settings.roundedButtons);
e->setConf("roundedMenus",settings.roundedMenus);
e->setConf("loadJapanese",settings.loadJapanese);
e->setConf("loadChinese",settings.loadChinese);
e->setConf("fmLayout",settings.fmLayout);
e->setConf("sampleLayout",settings.sampleLayout);
e->setConf("waveLayout",settings.waveLayout);
@ -2034,6 +2094,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("initialSys",e->encodeSysDesc(settings.initialSys));
e->setConf("horizontalDataView",settings.horizontalDataView);
e->setConf("noMultiSystem",settings.noMultiSystem);
e->setConf("oldMacroVSlider",settings.oldMacroVSlider);
// colors
for (int i=0; i<GUI_COLOR_MAX; i++) {
@ -2055,6 +2116,12 @@ void FurnaceGUI::commitSettings() {
e->saveConf();
if (sampleROMsChanged) {
if (e->loadSampleROMs()) {
showError(e->getLastError());
}
}
if (!e->switchMaster()) {
showError("could not initialize audio!");
}
@ -2587,6 +2654,9 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
if (settings.loadJapanese) {
range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesJapanese());
}
if (settings.loadChinese) {
range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon());
}
// I'm terribly sorry
range.UsedChars[0x80>>5]=0;

View file

@ -370,6 +370,21 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
}
break;
}
case DIV_SYSTEM_OPN: {
if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) {
copyOfFlags=(flags&0x80000000)|0;
}
if (ImGui::RadioButton("PAL (3.54MHz)",(flags&3)==1)) {
copyOfFlags=(flags&0x80000000)|1;
}
if (ImGui::RadioButton("Arcade (4MHz)",(flags&3)==2)) {
copyOfFlags=(flags&0x80000000)|2;
}
if (ImGui::RadioButton("PC-9801-26K? TODO: CONFIRM (3MHz)",(flags&3)==3)) {
copyOfFlags=(flags&0x80000000)|3;
}
break;
}
case DIV_SYSTEM_GB:
case DIV_SYSTEM_SWAN:
case DIV_SYSTEM_VERA:
@ -382,6 +397,8 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
case DIV_SYSTEM_YM2610B_EXT:
case DIV_SYSTEM_YMU759:
case DIV_SYSTEM_PET:
case DIV_SYSTEM_SCC:
case DIV_SYSTEM_SCC_PLUS:
ImGui::Text("nothing to configure");
break;
default: