Merge branch 'master' into feature/Moar-patch-bank-support-part3

This commit is contained in:
James Alan Nguyen 2022-05-12 09:57:11 +10:00
commit 4354e4064b
68 changed files with 4941 additions and 403 deletions

View file

@ -187,8 +187,6 @@ jobs:
export USE_WAE=ON export USE_WAE=ON
export CMAKE_EXTRA_ARGS=() export CMAKE_EXTRA_ARGS=()
if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then 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 }}') CMAKE_EXTRA_ARGS+=('-DCMAKE_GENERATOR_PLATFORM=${{ steps.windows-identify.outputs.msvc-target }}')
# Force static linking # 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) option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF)
set(DEPENDENCIES_INCLUDE_DIRS "") set(DEPENDENCIES_INCLUDE_DIRS "")
if (ANDROID)
set(DEPENDENCIES_DEFINES "IS_MOBILE")
else()
set(DEPENDENCIES_DEFINES "") set(DEPENDENCIES_DEFINES "")
endif()
set(DEPENDENCIES_COMPILE_OPTIONS "") set(DEPENDENCIES_COMPILE_OPTIONS "")
set(DEPENDENCIES_LIBRARIES "") set(DEPENDENCIES_LIBRARIES "")
set(DEPENDENCIES_LIBRARY_DIRS "") set(DEPENDENCIES_LIBRARY_DIRS "")
@ -310,6 +316,8 @@ src/engine/platform/sound/vic20sound.c
src/engine/platform/sound/vrcvi/vrcvi.cpp src/engine/platform/sound/vrcvi/vrcvi.cpp
src/engine/platform/sound/scc/scc.cpp
src/engine/platform/ym2610Interface.cpp src/engine/platform/ym2610Interface.cpp
src/engine/blip_buf.c src/engine/blip_buf.c
@ -343,6 +351,7 @@ src/engine/platform/nes.cpp
src/engine/platform/c64.cpp src/engine/platform/c64.cpp
src/engine/platform/arcade.cpp src/engine/platform/arcade.cpp
src/engine/platform/tx81z.cpp src/engine/platform/tx81z.cpp
src/engine/platform/ym2203.cpp
src/engine/platform/ym2610.cpp src/engine/platform/ym2610.cpp
src/engine/platform/ym2610ext.cpp src/engine/platform/ym2610ext.cpp
src/engine/platform/ym2610b.cpp src/engine/platform/ym2610b.cpp
@ -367,6 +376,7 @@ src/engine/platform/n163.cpp
src/engine/platform/pet.cpp src/engine/platform/pet.cpp
src/engine/platform/vic20.cpp src/engine/platform/vic20.cpp
src/engine/platform/vrc6.cpp src/engine/platform/vrc6.cpp
src/engine/platform/scc.cpp
src/engine/platform/dummy.cpp src/engine/platform/dummy.cpp
) )

View file

@ -126,7 +126,7 @@ Available options:
| Name | Default | Description | | 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 | | `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_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 | | `SYSTEM_LIBSNDFILE` | `OFF` | Use a system-installed version of libsndfile instead of the vendored one |

View file

@ -9,7 +9,6 @@
- OPNA system - OPNA system
- ZX beeper system - ZX beeper system
- Y8950 system - Y8950 system
- SCC/SCC+ system
- maybe YMU759 ADPCM channel - maybe YMU759 ADPCM channel
- ADPCM chips - ADPCM chips
- Game Boy envelope macro/sequence - Game Boy envelope macro/sequence

View file

@ -232,6 +232,9 @@ size | description
4f | A-4 tuning 4f | A-4 tuning
1 | limit slides (>=36) or reserved 1 | limit slides (>=36) or reserved
1 | linear pitch (>=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 | loop modality (>=36) or reserved
1 | proper noise layout (>=42) or reserved 1 | proper noise layout (>=42) or reserved
1 | wave duty is volume (>=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 | weird f-num/block-based chip pitch slides (>=85) or reserved
1 | SN duty macro always resets phase (>=86) or reserved 1 | SN duty macro always resets phase (>=86) or reserved
1 | pitch macro is linear (>=90) 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 # instrument

View file

@ -29,6 +29,7 @@
#include "platform/c64.h" #include "platform/c64.h"
#include "platform/arcade.h" #include "platform/arcade.h"
#include "platform/tx81z.h" #include "platform/tx81z.h"
#include "platform/ym2203.h"
#include "platform/ym2610.h" #include "platform/ym2610.h"
#include "platform/ym2610ext.h" #include "platform/ym2610ext.h"
#include "platform/ym2610b.h" #include "platform/ym2610b.h"
@ -54,6 +55,7 @@
#include "platform/vrc6.h" #include "platform/vrc6.h"
#include "platform/fds.h" #include "platform/fds.h"
#include "platform/mmc5.h" #include "platform/mmc5.h"
#include "platform/scc.h"
#include "platform/dummy.h" #include "platform/dummy.h"
#include "../ta-log.h" #include "../ta-log.h"
#include "song.h" #include "song.h"
@ -233,6 +235,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_TIA: case DIV_SYSTEM_TIA:
dispatch=new DivPlatformTIA; dispatch=new DivPlatformTIA;
break; break;
case DIV_SYSTEM_OPN:
dispatch=new DivPlatformYM2203;
break;
case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7: case DIV_SYSTEM_VRC7:
@ -314,6 +319,14 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_MMC5: case DIV_SYSTEM_MMC5:
dispatch=new DivPlatformMMC5; dispatch=new DivPlatformMMC5;
break; 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: case DIV_SYSTEM_SOUND_UNIT:
dispatch=new DivPlatformSoundUnit; dispatch=new DivPlatformSoundUnit;
break; break;

View file

@ -986,38 +986,66 @@ int DivEngine::calcBaseFreq(double clock, double divider, int note, bool period)
}*/ }*/
double 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); double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0);
return period? return period?
(clock/base)/divider: (clock/base)/divider:
base*(divider/clock); base*(divider/clock);
} }
unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) { #define CONVERT_FNUM_BLOCK(bf,bits,note) \
int bf=calcBaseFreq(clock,divider,note,false); double tuning=song.tuning; \
int block=note/12; if (tuning<400.0) tuning=400.0; \
if (block<0) block=0; if (tuning>500.0) tuning=500.0; \
if (block>7) block=7; int boundaryBottom=tuning*pow(2.0,0.25)*(divider/clock); \
bf>>=block; int boundaryTop=2.0*tuning*pow(2.0,0.25)*(divider/clock); \
if (bf<0) bf=0; int block=(note)/12; \
// octave boundaries if (block<0) block=0; \
while (bf>0 && bf<644 && block>0) { if (block>7) block=7; \
bf<<=1; bf>>=block; \
block--; if (bf<0) bf=0; \
} /* octave boundaries */ \
if (bf>1288) { while (bf>0 && bf<boundaryBottom && block>0) { \
while (block<7) { bf<<=1; \
bf>>=1; block--; \
block++; } \
} if (bf>boundaryTop) { \
if (bf>((1<<bits)-1)) { while (block<7 && bf>boundaryTop) { \
bf=(1<<bits)-1; bf>>=1; \
} block++; \
} } \
if (bf>((1<<bits)-1)) { \
bf=(1<<bits)-1; \
} \
} \
/* logV("f-num: %d block: %d",bf,block); */ \
return bf|(block<<bits); 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) { int DivEngine::calcFreq(int base, int pitch, bool period, int octave, int pitch2, double clock, double divider, int blockBits) {
if (song.linearPitch) { 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 // global pitch multiplier
int whatTheFuck=(1024+(globalPitch<<6)-(globalPitch<0?globalPitch-6:0)); int whatTheFuck=(1024+(globalPitch<<6)-(globalPitch<0?globalPitch-6:0));
if (whatTheFuck<1) whatTheFuck=1; // avoids division by zero but please kill me if (whatTheFuck<1) whatTheFuck=1; // avoids division by zero but please kill me
@ -1209,7 +1237,7 @@ void DivEngine::reset() {
chan[i]=DivChannelState(); chan[i]=DivChannelState();
if (i<chans) chan[i].volMax=(disCont[dispatchOfChan[i]].dispatch->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff; 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; chan[i].volume=chan[i].volMax;
if (!song.linearPitch) chan[i].vibratoFine=4; if (song.linearPitch==0) chan[i].vibratoFine=4;
} }
extValue=0; extValue=0;
extValuePresent=0; extValuePresent=0;

View file

@ -45,8 +45,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false; #define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev93" #define DIV_VERSION "dev94"
#define DIV_ENGINE_VERSION 93 #define DIV_ENGINE_VERSION 94
// for imports // for imports
#define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_MOD 0xff01
@ -484,10 +484,10 @@ class DivEngine {
double calcBaseFreq(double clock, double divider, int note, bool period); double calcBaseFreq(double clock, double divider, int note, bool period);
// calculate base frequency in f-num/block format // 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 // 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 // convert panning formats
int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range); int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range);

View file

@ -139,7 +139,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
// compatibility flags // compatibility flags
ds.limitSlides=true; ds.limitSlides=true;
ds.linearPitch=true; ds.linearPitch=1;
ds.loopModality=0; ds.loopModality=0;
ds.properNoiseLayout=false; ds.properNoiseLayout=false;
ds.waveDutyIsVol=false; ds.waveDutyIsVol=false;
@ -173,12 +173,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.legacyVolumeSlides=false; 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 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_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT
|| ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) {
ds.tuning=443.23; ds.tuning=443.23;
} }
*/
logI("reading module data..."); logI("reading module data...");
if (ds.version>0x0c) { if (ds.version>0x0c) {
@ -948,7 +950,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<37) { // compat flags not stored back then if (ds.version<37) { // compat flags not stored back then
ds.limitSlides=true; ds.limitSlides=true;
ds.linearPitch=true; ds.linearPitch=1;
ds.loopModality=0; ds.loopModality=0;
} }
if (ds.version<43) { if (ds.version<43) {
@ -1390,7 +1392,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
} else { } else {
reader.readC(); 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(); reader.readC();
} }
} }
@ -1647,7 +1654,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
DivSong ds; DivSong ds;
ds.tuning=436.0; ds.tuning=436.0;
ds.version=DIV_VERSION_MOD; ds.version=DIV_VERSION_MOD;
ds.linearPitch=false; ds.linearPitch=0;
ds.noSlidesOnFirstTick=true; ds.noSlidesOnFirstTick=true;
ds.rowResetsArpPos=true; ds.rowResetsArpPos=true;
ds.ignoreJumpAtEnd=false; ds.ignoreJumpAtEnd=false;
@ -2700,7 +2707,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeC(song.fbPortaPause); w->writeC(song.fbPortaPause);
w->writeC(song.snDutyReset); w->writeC(song.snDutyReset);
w->writeC(song.pitchMacroIsLinear); 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); w->writeC(0);
} }

View file

@ -220,7 +220,7 @@ void DivPlatformAmiga::tick(bool sysTick) {
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); //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=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>4095) chan[i].freq=4095;
if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].keyOn) { if (chan[i].keyOn) {

View file

@ -251,7 +251,7 @@ void DivPlatformAY8910::tick(bool sysTick) {
if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1; if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1;
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,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].freq>4095) chan[i].freq=4095;
if (chan[i].keyOn) { if (chan[i].keyOn) {
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); //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); immWrite(0x1a,ayNoiseOr);
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,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>65535) chan[i].freq=65535;
if (chan[i].keyOn) { if (chan[i].keyOn) {
if (chan[i].insChanged) { 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) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SCC); //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<0) chan[i].freq=0;
if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].freq>4095) chan[i].freq=4095;
k005289->load(i,chan[i].freq); 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) { 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].freq>0xffff) chan[i].freq=0xffff;
if (chan[i].keyOn) { if (chan[i].keyOn) {
rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay)); 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) { if (chan[i].freqChanged) {
chan[i].freqChanged=false; 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) { 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>4095) chan[i].freq=4095;
if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].keyOn) { if (chan[i].keyOn) {

View file

@ -237,7 +237,7 @@ void DivPlatformGB::tick(bool sysTick) {
if (ntPos>255) ntPos=255; if (ntPos>255) ntPos=255;
chan[i].freq=noiseTable[ntPos]; chan[i].freq=noiseTable[ntPos];
} else { } 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>2047) chan[i].freq=2047;
if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq<0) chan[i].freq=0;
} }

View file

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

View file

@ -127,6 +127,31 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
break; break;
} }
case DIV_CMD_NOTE_PORTA: { 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 destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq; int newFreq;
bool return2=false; bool return2=false;
@ -148,18 +173,18 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
} }
// what the heck! // what the heck!
if (!opChan[ch].portaPause) { if (!opChan[ch].portaPause) {
if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) { if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
if (parent->song.fbPortaPause) { if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=(644)|((newFreq+0x800)&0xf800); opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
opChan[ch].portaPause=true; opChan[ch].portaPause=true;
break; break;
} else { } else {
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800); newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
} }
} }
if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) { if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
if (parent->song.fbPortaPause) { 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; opChan[ch].portaPause=true;
break; break;
} else { } else {
@ -435,17 +460,21 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
unsigned char writeMask=2; unsigned char writeMask=2;
if (extMode) for (int i=0; i<4; i++) { if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) { if (opChan[i].freqChanged) {
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2); if (parent->song.linearPitch==2) {
int block=(opChan[i].baseFreq&0xf800)>>11; opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
if (fNum<0) fNum=0; } else {
if (fNum>2047) { int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
while (block<7) { int block=(opChan[i].baseFreq&0xf800)>>11;
fNum>>=1; if (fNum<0) fNum=0;
block++; if (fNum>2047) {
while (block<7) {
fNum>>=1;
block++;
}
if (fNum>2047) fNum=2047;
} }
if (fNum>2047) fNum=2047; opChan[i].freq=(block<<11)|fNum;
} }
opChan[i].freq=(block<<11)|fNum;
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff; if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff); 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)); WRITE_OTHER(i, ((chan[i].lfsr&0xf00)>>4));
chan[i].lfsr=-1; 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) { if (chan[i].std.duty.had) {
chan[i].duty=chan[i].std.duty.val; chan[i].duty=chan[i].std.duty.val;
WRITE_FEEDBACK(i, chan[i].duty.feedback); 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) { 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>2047) chan[i].freq=2047;
if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].keyOn) { if (chan[i].keyOn) {

View file

@ -357,7 +357,7 @@ void DivPlatformN163::tick(bool sysTick) {
chan[i].waveUpdated=false; chan[i].waveUpdated=false;
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].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<0) chan[i].freq=0;
if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff; if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff;
if (chan[i].keyOn) { if (chan[i].keyOn) {

View file

@ -308,7 +308,7 @@ void DivPlatformNES::tick(bool sysTick) {
if (ntPos>252) ntPos=252; if (ntPos>252) ntPos=252;
chan[i].freq=(parent->song.properNoiseLayout)?(15-(chan[i].baseFreq&15)):(noiseTable[ntPos]); chan[i].freq=(parent->song.properNoiseLayout)?(15-(chan[i].baseFreq&15)):(noiseTable[ntPos]);
} else { } 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>2047) chan[i].freq=2047;
if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq<0) chan[i].freq=0;
} }

View file

@ -511,7 +511,7 @@ void DivPlatformOPL::tick(bool sysTick) {
bool updateDrums=false; bool updateDrums=false;
for (int i=0; i<totalChans; i++) { for (int i=0; i<totalChans; i++) {
if (chan[i].freqChanged) { 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; if (chan[i].freq>131071) chan[i].freq=131071;
int freqt=toFreq(chan[i].freq)+chan[i].pitch2; int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
chan[i].freqH=freqt>>8; chan[i].freqH=freqt>>8;
@ -800,19 +800,19 @@ int DivPlatformOPL::dispatch(DivCommand c) {
int newFreq; int newFreq;
bool return2=false; bool return2=false;
if (destFreq>chan[c.chan].baseFreq) { 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) { if (newFreq>=destFreq) {
newFreq=destFreq; newFreq=destFreq;
return2=true; return2=true;
} }
} else { } 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) { if (newFreq<=destFreq) {
newFreq=destFreq; newFreq=destFreq;
return2=true; return2=true;
} }
} }
if (!chan[c.chan].portaPause) { if (!chan[c.chan].portaPause && parent->song.linearPitch!=2) {
if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) { if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) {
chan[c.chan].portaPause=true; chan[c.chan].portaPause=true;
break; break;

View file

@ -300,7 +300,7 @@ void DivPlatformOPLL::tick(bool sysTick) {
for (int i=0; i<11; i++) { for (int i=0; i<11; i++) {
if (chan[i].freqChanged) { 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; if (chan[i].freq>262143) chan[i].freq=262143;
int freqt=toFreq(chan[i].freq)+chan[i].pitch2; int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
chan[i].freqL=freqt&0xff; chan[i].freqL=freqt&0xff;
@ -578,13 +578,13 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
int newFreq; int newFreq;
bool return2=false; bool return2=false;
if (destFreq>chan[c.chan].baseFreq) { 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) { if (newFreq>=destFreq) {
newFreq=destFreq; newFreq=destFreq;
return2=true; return2=true;
} }
} else { } 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) { if (newFreq<=destFreq) {
newFreq=destFreq; newFreq=destFreq;
return2=true; return2=true;

View file

@ -227,7 +227,7 @@ void DivPlatformPCE::tick(bool sysTick) {
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE); //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) { if (chan[i].furnaceDac) {
double off=1.0; double off=1.0;
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) { if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {

View file

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

View file

@ -118,7 +118,7 @@ void DivPlatformPET::tick(bool sysTick) {
chan.freqChanged=true; chan.freqChanged=true;
} }
if (chan.freqChanged || chan.keyOn || chan.keyOff) { 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>257) chan.freq=257;
if (chan.freq<2) chan.freq=2; if (chan.freq<2) chan.freq=2;
rWrite(8,chan.freq-2); rWrite(8,chan.freq-2);

View file

@ -24,7 +24,7 @@
#include <map> #include <map>
#define CHIP_DIVIDER (1248*2) #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 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);} #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_addr = 0;
uint16_t qsound_loop = 0; uint16_t qsound_loop = 0;
uint16_t qsound_end = 0; uint16_t qsound_end = 0;
double off=1.0;
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) { if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].sample); 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_bank = 0x8000 | (s->offQSound >> 16);
qsound_addr = s->offQSound & 0xffff; qsound_addr = s->offQSound & 0xffff;
@ -322,15 +316,15 @@ void DivPlatformQSound::tick(bool sysTick) {
if (chan[i].std.arp.had) { if (chan[i].std.arp.had) {
if (!chan[i].inPorta) { if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) { 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 { } 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; chan[i].freqChanged=true;
} else { } else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) { 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; chan[i].freqChanged=true;
} }
} }
@ -354,7 +348,16 @@ void DivPlatformQSound::tick(bool sysTick) {
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); //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].freq>0xffff) chan[i].freq=0xffff;
if (chan[i].keyOn) { if (chan[i].keyOn) {
rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank); rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank);
@ -388,17 +391,8 @@ int DivPlatformQSound::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].sample=ins->amiga.initSample; 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;
}
}
if (c.value!=DIV_NOTE_NULL) { 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) { if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1; chan[c.chan].sample=-1;
@ -467,16 +461,7 @@ int DivPlatformQSound::dispatch(DivCommand c) {
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
break; break;
case DIV_CMD_NOTE_PORTA: { case DIV_CMD_NOTE_PORTA: {
double off=1.0; int destFreq=QS_NOTE_FREQUENCY(c.value2);
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);
bool return2=false; bool return2=false;
if (destFreq>chan[c.chan].baseFreq) { if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value; chan[c.chan].baseFreq+=c.value;
@ -499,16 +484,7 @@ int DivPlatformQSound::dispatch(DivCommand c) {
break; break;
} }
case DIV_CMD_LEGATO: { case DIV_CMD_LEGATO: {
double off=1.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)));
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].freqChanged=true; chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
break; break;

View file

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

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

@ -0,0 +1,391 @@
/**
* 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;
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 (chan[i].active) {
updateWave(i);
}
}
}
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);
}
}
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() {
}

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

@ -0,0 +1,91 @@
/**
* 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;
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

@ -121,7 +121,7 @@ void DivPlatformSMS::tick(bool sysTick) {
} }
for (int i=0; i<3; i++) { for (int i=0; i<3; i++) {
if (chan[i].freqChanged) { 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>1023) chan[i].freq=1023;
if (chan[i].freq<8) chan[i].freq=1; if (chan[i].freq<8) chan[i].freq=1;
//if (chan[i].actualNote>0x5d) chan[i].freq=0x01; //if (chan[i].actualNote>0x5d) chan[i].freq=0x01;
@ -136,7 +136,7 @@ void DivPlatformSMS::tick(bool sysTick) {
} }
} }
if (chan[3].freqChanged || updateSNMode) { 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].freq>1023) chan[3].freq=1023;
if (chan[3].actualNote>0x5d) chan[3].freq=0x01; if (chan[3].actualNote>0x5d) chan[3].freq=0x01;
if (snNoiseMode&2) { // take period from channel 3 if (snNoiseMode&2) { // take period from channel 3

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

@ -183,7 +183,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); //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) { if (chan[i].pcm) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
DivSample* sample=parent->getSample(ins->amiga.initSample); DivSample* sample=parent->getSample(ins->amiga.initSample);
@ -298,13 +298,13 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
int destFreq=NOTE_FREQUENCY(c.value2); int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false; bool return2=false;
if (destFreq>chan[c.chan].baseFreq) { 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) { if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq; chan[c.chan].baseFreq=destFreq;
return2=true; return2=true;
} }
} else { } 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) { if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq; chan[c.chan].baseFreq=destFreq;
return2=true; return2=true;

View file

@ -203,7 +203,7 @@ void DivPlatformSwan::tick(bool sysTick) {
} }
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,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) { if (i==1 && pcm && furnaceDac) {
double off=1.0; double off=1.0;
if (dacSample>=0 && dacSample<parent->song.sampleLen) { if (dacSample>=0 && dacSample<parent->song.sampleLen) {

View file

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

View file

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

View file

@ -197,9 +197,9 @@ void DivPlatformVRC6::tick(bool sysTick) {
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
if (i==2) { // sawtooth 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 } 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) { if (chan[i].furnaceDac) {
double off=1.0; double off=1.0;
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) { if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {

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) { double DivPlatformX1_010::NoteX1_010(int ch, int note) {
if (chan[ch].pcm) { // PCM note if (chan[ch].pcm) { // PCM note
double off=1.0; double off=8192.0;
int sample=chan[ch].sample; int sample=chan[ch].sample;
if (sample>=0 && sample<parent->song.sampleLen) { if (sample>=0 && sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(sample); DivSample* s=parent->getSample(sample);
if (s->centerRate<1) { if (s->centerRate<1) {
off=1.0; off=8192.0;
} else { } 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 // Wavetable note
return NOTE_FREQUENCY(note); return NOTE_FREQUENCY(note);
@ -487,7 +487,19 @@ void DivPlatformX1_010::tick(bool sysTick) {
chan[i].envChanged=false; chan[i].envChanged=false;
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,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].pcm) {
if (chan[i].freq<1) chan[i].freq=1; if (chan[i].freq<1) chan[i].freq=1;
if (chan[i].freq>255) chan[i].freq=255; if (chan[i].freq>255) chan[i].freq=255;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,134 @@
/**
* 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;
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[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,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,130 @@
/**
* 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 "ym2610.h"
class DivPlatformYM2608: public DivPlatformYM2610Base {
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;
DivPlatformAY8910* ay;
unsigned char sampleBank;
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);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformYM2608();
};
#endif

View file

@ -456,13 +456,13 @@ double DivPlatformYM2610::NOTE_OPNB(int ch, int note) {
return NOTE_PERIODIC(note); return NOTE_PERIODIC(note);
} }
// FM // FM
return NOTE_FREQUENCY(note); return NOTE_FNUM_BLOCK(note,11);
} }
double DivPlatformYM2610::NOTE_ADPCMB(int note) { double DivPlatformYM2610::NOTE_ADPCMB(int note) {
if (chan[13].sample>=0 && chan[13].sample<parent->song.sampleLen) { if (chan[13].sample>=0 && chan[13].sample<parent->song.sampleLen) {
double off=(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0; double off=65535.0*(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0;
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); return parent->calcBaseFreq((double)chipClock/144,off,note,false);
} }
return 0; return 0;
} }
@ -559,15 +559,15 @@ void DivPlatformYM2610::tick(bool sysTick) {
if (chan[i].std.arp.had) { if (chan[i].std.arp.had) {
if (!chan[i].inPorta) { if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) { 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 { } 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; chan[i].freqChanged=true;
} else { } else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) { 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; chan[i].freqChanged=true;
} }
} }
@ -726,7 +726,12 @@ void DivPlatformYM2610::tick(bool sysTick) {
} }
} }
if (chan[13].freqChanged) { 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(0x19,chan[13].freq&0xff);
immWrite(0x1a,(chan[13].freq>>8)&0xff); immWrite(0x1a,(chan[13].freq>>8)&0xff);
chan[13].freqChanged=false; chan[13].freqChanged=false;
@ -742,11 +747,24 @@ void DivPlatformYM2610::tick(bool sysTick) {
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
if (i==1 && extMode) continue; if (i==1 && extMode) continue;
if (chan[i].freqChanged) { if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); if (parent->song.linearPitch==2) {
if (chan[i].freq>262143) chan[i].freq=262143; chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
int freqt=toFreq(chan[i].freq)+chan[i].pitch2; } else {
immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); 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; chan[i].freqChanged=false;
} }
if (chan[i].keyOn) { 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) { int DivPlatformYM2610::dispatch(DivCommand c) {
if (c.chan>3 && c.chan<7) { if (c.chan>3 && c.chan<7) {
c.chan-=4; c.chan-=4;
@ -928,7 +905,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
chan[c.chan].insChanged=false; chan[c.chan].insChanged=false;
if (c.value!=DIV_NOTE_NULL) { 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].portaPause=false;
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
@ -1025,7 +1002,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
break; break;
} }
case DIV_CMD_NOTE_PORTA: { 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); int destFreq=NOTE_OPNB(c.chan,c.value2);
bool return2=false; bool return2=false;
if (destFreq>chan[c.chan].baseFreq) { if (destFreq>chan[c.chan].baseFreq) {
@ -1048,33 +1025,48 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
} }
break; break;
} }
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
int destFreq=NOTE_FREQUENCY(c.value2); int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq; int newFreq;
bool return2=false; bool return2=false;
if (chan[c.chan].portaPause) {
chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq;
}
if (destFreq>chan[c.chan].baseFreq) { 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) { if (newFreq>=destFreq) {
newFreq=destFreq; newFreq=destFreq;
return2=true; return2=true;
} }
} else { } else {
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq); newFreq=chan[c.chan].baseFreq-c.value;
if (newFreq<=destFreq) { if (newFreq<=destFreq) {
newFreq=destFreq; newFreq=destFreq;
return2=true; return2=true;
} }
} }
// check for octave boundary
// what the heck!
if (!chan[c.chan].portaPause) { 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; chan[c.chan].portaPause=true;
break; break;
} }
} }
chan[c.chan].baseFreq=newFreq;
chan[c.chan].portaPause=false; chan[c.chan].portaPause=false;
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
if (return2) return 2; chan[c.chan].baseFreq=newFreq;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break; break;
} }
case DIV_CMD_SAMPLE_BANK: case DIV_CMD_SAMPLE_BANK:

View file

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

View file

@ -436,13 +436,13 @@ double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) {
return NOTE_PERIODIC(note); return NOTE_PERIODIC(note);
} }
// FM // FM
return NOTE_FREQUENCY(note); return NOTE_FNUM_BLOCK(note,11);
} }
double DivPlatformYM2610B::NOTE_ADPCMB(int note) { double DivPlatformYM2610B::NOTE_ADPCMB(int note) {
if (chan[15].sample>=0 && chan[15].sample<parent->song.sampleLen) { if (chan[15].sample>=0 && chan[15].sample<parent->song.sampleLen) {
double off=(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0;
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); return parent->calcBaseFreq((double)chipClock/144,off,note,false);
} }
return 0; return 0;
} }
@ -538,15 +538,15 @@ void DivPlatformYM2610B::tick(bool sysTick) {
if (chan[i].std.arp.had) { if (chan[i].std.arp.had) {
if (!chan[i].inPorta) { if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) { 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 { } 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; chan[i].freqChanged=true;
} else { } else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) { 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; chan[i].freqChanged=true;
} }
} }
@ -704,7 +704,12 @@ void DivPlatformYM2610B::tick(bool sysTick) {
} }
} }
if (chan[15].freqChanged) { 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(0x19,chan[15].freq&0xff);
immWrite(0x1a,(chan[15].freq>>8)&0xff); immWrite(0x1a,(chan[15].freq>>8)&0xff);
chan[15].freqChanged=false; chan[15].freqChanged=false;
@ -720,11 +725,24 @@ void DivPlatformYM2610B::tick(bool sysTick) {
for (int i=0; i<6; i++) { for (int i=0; i<6; i++) {
if (i==2 && extMode) continue; if (i==2 && extMode) continue;
if (chan[i].freqChanged) { if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); if (parent->song.linearPitch==2) {
if (chan[i].freq>262143) chan[i].freq=262143; chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
int freqt=toFreq(chan[i].freq)+chan[i].pitch2; } else {
immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2);
immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); 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; chan[i].freqChanged=false;
} }
if (chan[i].keyOn) { 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) { int DivPlatformYM2610B::dispatch(DivCommand c) {
if (c.chan>5 && c.chan<9) { if (c.chan>5 && c.chan<9) {
c.chan-=6; c.chan-=6;
@ -906,7 +883,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
chan[c.chan].insChanged=false; chan[c.chan].insChanged=false;
if (c.value!=DIV_NOTE_NULL) { 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].portaPause=false;
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
@ -1003,7 +980,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
break; break;
} }
case DIV_CMD_NOTE_PORTA: { 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); int destFreq=NOTE_OPNB(c.chan,c.value2);
bool return2=false; bool return2=false;
if (destFreq>chan[c.chan].baseFreq) { if (destFreq>chan[c.chan].baseFreq) {
@ -1026,33 +1003,48 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
} }
break; break;
} }
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
int destFreq=NOTE_FREQUENCY(c.value2); int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq; int newFreq;
bool return2=false; bool return2=false;
if (chan[c.chan].portaPause) {
chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq;
}
if (destFreq>chan[c.chan].baseFreq) { 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) { if (newFreq>=destFreq) {
newFreq=destFreq; newFreq=destFreq;
return2=true; return2=true;
} }
} else { } else {
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq); newFreq=chan[c.chan].baseFreq-c.value;
if (newFreq<=destFreq) { if (newFreq<=destFreq) {
newFreq=destFreq; newFreq=destFreq;
return2=true; return2=true;
} }
} }
// check for octave boundary
// what the heck!
if (!chan[c.chan].portaPause) { 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; chan[c.chan].portaPause=true;
break; break;
} }
} }
chan[c.chan].baseFreq=newFreq;
chan[c.chan].portaPause=false; chan[c.chan].portaPause=false;
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
if (return2) return 2; chan[c.chan].baseFreq=newFreq;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break; break;
} }
case DIV_CMD_SAMPLE_BANK: case DIV_CMD_SAMPLE_BANK:

View file

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

View file

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

View file

@ -25,12 +25,12 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B {
struct OpChannel { struct OpChannel {
DivMacroInt std; DivMacroInt std;
unsigned char freqH, freqL; unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, ins; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
signed char konCycles; signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
int vol; int vol;
unsigned char pan; 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]; OpChannel opChan[4];
bool isOpMuted[4]; bool isOpMuted[4];

View file

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

View file

@ -25,12 +25,12 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 {
struct OpChannel { struct OpChannel {
DivMacroInt std; DivMacroInt std;
unsigned char freqH, freqL; unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, ins; int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
signed char konCycles; signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
int vol; int vol;
unsigned char pan; 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]; OpChannel opChan[4];
bool isOpMuted[4]; bool isOpMuted[4];

View file

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

View file

@ -301,7 +301,12 @@ struct DivSong {
// compatibility flags // compatibility flags
bool limitSlides; 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 // loop behavior
// 0: reset on loop // 0: reset on loop
// 1: fake reset on loop // 1: fake reset on loop
@ -371,7 +376,6 @@ struct DivSong {
*/ */
void clearSamples(); void clearSamples();
/** /**
* unloads the song, freeing all memory associated with it. * unloads the song, freeing all memory associated with it.
* use before destroying the object. * use before destroying the object.
@ -412,7 +416,8 @@ struct DivSong {
masterVol(1.0f), masterVol(1.0f),
tuning(440.0f), tuning(440.0f),
limitSlides(false), limitSlides(false),
linearPitch(true), linearPitch(2),
pitchSlideSpeed(4),
loopModality(0), loopModality(0),
properNoiseLayout(false), properNoiseLayout(false),
waveDutyIsVol(false), waveDutyIsVol(false),

View file

@ -1259,11 +1259,14 @@ void DivEngine::registerSystems() {
); );
sysDefs[DIV_SYSTEM_OPN]=new DivSysDef( 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, false, 0x151, false,
{"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, {"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"},
{"F1", "F2", "F3", "S1", "S2", "S3"}, {"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_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_PC98]=new DivSysDef( sysDefs[DIV_SYSTEM_PC98]=new DivSysDef(
@ -1459,11 +1462,13 @@ void DivEngine::registerSystems() {
); );
sysDefs[DIV_SYSTEM_SCC]=new DivSysDef( 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,
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"}, {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"},
{"CH1", "CH2", "CH3", "CH4", "CH5"}, {"CH1", "CH2", "CH3", "CH4", "CH5"},
{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_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 { auto oplDrumsEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool {
@ -1740,11 +1745,13 @@ void DivEngine::registerSystems() {
); );
sysDefs[DIV_SYSTEM_SCC_PLUS]=new DivSysDef( 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,
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"}, {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"},
{"CH1", "CH2", "CH3", "CH4", "CH5"}, {"CH1", "CH2", "CH3", "CH4", "CH5"},
{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_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( sysDefs[DIV_SYSTEM_SOUND_UNIT]=new DivSysDef(

View file

@ -530,6 +530,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
break; break;
} }
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:
case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7: case DIV_SYSTEM_VRC7:
@ -598,6 +603,71 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
break; break;
} }
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: default:
logW("write not handled!"); logW("write not handled!");
break; break;
@ -884,6 +954,18 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
howManyChips++; howManyChips++;
} }
break; 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:
case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7: case DIV_SYSTEM_VRC7:
@ -988,6 +1070,24 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
howManyChips++; howManyChips++;
} }
break; 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: default:
break; break;
} }

View file

@ -18,6 +18,8 @@
*/ */
#include "gui.h" #include "gui.h"
#include "imgui.h"
#include "intConst.h"
void FurnaceGUI::drawCompatFlags() { void FurnaceGUI::drawCompatFlags() {
if (nextWindow==GUI_WINDOW_COMPAT_FLAGS) { if (nextWindow==GUI_WINDOW_COMPAT_FLAGS) {
@ -32,10 +34,6 @@ void FurnaceGUI::drawCompatFlags() {
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves."); 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); ImGui::Checkbox("Proper noise layout on NES and PC Engine",&e->song.properNoiseLayout);
if (ImGui::IsItemHovered()) { 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."); 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::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:"); ImGui::Text("Loop modality:");
if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) {
e->song.loopModality=0; e->song.loopModality=0;

View file

@ -47,7 +47,20 @@ void FurnaceGUI::drawInsList() {
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) { if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) {
doAction((settings.insLoadAlwaysReplace && curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN); doAction(GUI_ACTION_INS_LIST_OPEN);
}
if (ImGui::BeginPopupContextItem("InsOpenOpt")) {
if (ImGui::MenuItem("replace...")) {
doAction((curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN);
}
ImGui::Separator();
if (ImGui::MenuItem("load from TX81Z")) {
doAction(GUI_ACTION_TX81Z_REQUEST);
}
ImGui::EndPopup();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Open (insert; right-click to replace)");
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) { if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) {

View file

@ -25,6 +25,11 @@
#include "actionUtil.h" #include "actionUtil.h"
#include "sampleUtil.h" #include "sampleUtil.h"
const unsigned char avRequest[15]={
0xf0, 0x43, 0x20, 0x7e, 0x4c, 0x4d, 0x20, 0x20, 0x38, 0x39, 0x37, 0x36, 0x41, 0x45, 0xf7
};
void FurnaceGUI::doAction(int what) { void FurnaceGUI::doAction(int what) {
switch (what) { switch (what) {
case GUI_ACTION_OPEN: case GUI_ACTION_OPEN:
@ -141,6 +146,17 @@ void FurnaceGUI::doAction(int what) {
fullScreen=!fullScreen; fullScreen=!fullScreen;
SDL_SetWindowFullscreen(sdlWin,fullScreen?(SDL_WINDOW_FULLSCREEN|SDL_WINDOW_FULLSCREEN_DESKTOP):0); SDL_SetWindowFullscreen(sdlWin,fullScreen?(SDL_WINDOW_FULLSCREEN|SDL_WINDOW_FULLSCREEN_DESKTOP):0);
break; break;
case GUI_ACTION_TX81Z_REQUEST: {
TAMidiMessage msg;
msg.type=TA_MIDI_SYSEX;
msg.sysExData.reset(new unsigned char[15]);
msg.sysExLen=15;
memcpy(msg.sysExData.get(),avRequest,15);
if (!e->sendMidiMessage(msg)) {
showError("Error while sending request (MIDI output not configured?)");
}
break;
}
case GUI_ACTION_PANIC: case GUI_ACTION_PANIC:
e->syncReset(); e->syncReset();
break; break;

View file

@ -59,7 +59,7 @@ extern "C" {
#define BACKUP_FUR "/backup.fur" #define BACKUP_FUR "/backup.fur"
#endif #endif
#ifdef ANDROID #ifdef IS_MOBILE
#define MOBILE_UI_DEFAULT true #define MOBILE_UI_DEFAULT true
#else #else
#define MOBILE_UI_DEFAULT false #define MOBILE_UI_DEFAULT false
@ -2469,6 +2469,15 @@ bool FurnaceGUI::loop() {
wantCaptureKeyboard=ImGui::GetIO().WantTextInput; wantCaptureKeyboard=ImGui::GetIO().WantTextInput;
if (wantCaptureKeyboard!=oldWantCaptureKeyboard) {
oldWantCaptureKeyboard=wantCaptureKeyboard;
if (wantCaptureKeyboard) {
SDL_StartTextInput();
} else {
SDL_StopTextInput();
}
}
if (wantCaptureKeyboard) { if (wantCaptureKeyboard) {
WAKE_UP; WAKE_UP;
} }
@ -2791,9 +2800,11 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("settings")) { if (ImGui::BeginMenu("settings")) {
#ifndef IS_MOBILE
if (ImGui::MenuItem("full screen",BIND_FOR(GUI_ACTION_FULLSCREEN),fullScreen)) { if (ImGui::MenuItem("full screen",BIND_FOR(GUI_ACTION_FULLSCREEN),fullScreen)) {
doAction(GUI_ACTION_FULLSCREEN); doAction(GUI_ACTION_FULLSCREEN);
} }
#endif
if (ImGui::MenuItem("lock layout",NULL,lockLayout)) { if (ImGui::MenuItem("lock layout",NULL,lockLayout)) {
lockLayout=!lockLayout; lockLayout=!lockLayout;
} }
@ -3673,7 +3684,11 @@ bool FurnaceGUI::init() {
tempoView=e->getConfBool("tempoView",true); tempoView=e->getConfBool("tempoView",true);
waveHex=e->getConfBool("waveHex",false); waveHex=e->getConfBool("waveHex",false);
lockLayout=e->getConfBool("lockLayout",false); lockLayout=e->getConfBool("lockLayout",false);
#ifdef IS_MOBILE
fullScreen=true;
#else
fullScreen=e->getConfBool("fullScreen",false); fullScreen=e->getConfBool("fullScreen",false);
#endif
mobileUI=e->getConfBool("mobileUI",MOBILE_UI_DEFAULT); mobileUI=e->getConfBool("mobileUI",MOBILE_UI_DEFAULT);
syncSettings(); syncSettings();
@ -3695,6 +3710,7 @@ bool FurnaceGUI::init() {
#endif #endif
SDL_SetHint("SDL_HINT_VIDEO_ALLOW_SCREENSAVER","1"); SDL_SetHint("SDL_HINT_VIDEO_ALLOW_SCREENSAVER","1");
SDL_SetHint("SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH","1");
SDL_Init(SDL_INIT_VIDEO); SDL_Init(SDL_INIT_VIDEO);
@ -3886,6 +3902,7 @@ FurnaceGUI::FurnaceGUI():
displayExporting(false), displayExporting(false),
vgmExportLoop(true), vgmExportLoop(true),
wantCaptureKeyboard(false), wantCaptureKeyboard(false),
oldWantCaptureKeyboard(false),
displayMacroMenu(false), displayMacroMenu(false),
displayNew(false), displayNew(false),
fullScreen(false), fullScreen(false),

View file

@ -306,6 +306,7 @@ enum FurnaceGUIActions {
GUI_ACTION_FOLLOW_ORDERS, GUI_ACTION_FOLLOW_ORDERS,
GUI_ACTION_FOLLOW_PATTERN, GUI_ACTION_FOLLOW_PATTERN,
GUI_ACTION_FULLSCREEN, GUI_ACTION_FULLSCREEN,
GUI_ACTION_TX81Z_REQUEST,
GUI_ACTION_PANIC, GUI_ACTION_PANIC,
GUI_ACTION_WINDOW_EDIT_CONTROLS, GUI_ACTION_WINDOW_EDIT_CONTROLS,
@ -768,7 +769,7 @@ class FurnaceGUI {
String mmlString[17]; String mmlString[17];
String mmlStringW; 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 displayNew, fullScreen, preserveChanPos;
bool willExport[32]; bool willExport[32];
int vgmExportVersion; int vgmExportVersion;
@ -887,7 +888,6 @@ class FurnaceGUI {
int eventDelay; int eventDelay;
int moveWindowTitle; int moveWindowTitle;
int hiddenSystems; int hiddenSystems;
int insLoadAlwaysReplace;
int horizontalDataView; int horizontalDataView;
int noMultiSystem; int noMultiSystem;
unsigned int maxUndoSteps; unsigned int maxUndoSteps;
@ -970,9 +970,8 @@ class FurnaceGUI {
powerSave(1), powerSave(1),
absorbInsInput(0), absorbInsInput(0),
eventDelay(0), eventDelay(0),
moveWindowTitle(0), moveWindowTitle(1),
hiddenSystems(0), hiddenSystems(0),
insLoadAlwaysReplace(1),
horizontalDataView(0), horizontalDataView(0),
noMultiSystem(0), noMultiSystem(0),
maxUndoSteps(100), maxUndoSteps(100),

View file

@ -459,6 +459,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("FOLLOW_ORDERS", "Follow orders", 0), D("FOLLOW_ORDERS", "Follow orders", 0),
D("FOLLOW_PATTERN", "Follow pattern", 0), D("FOLLOW_PATTERN", "Follow pattern", 0),
D("FULLSCREEN", "Toggle full-screen", SDLK_F11), D("FULLSCREEN", "Toggle full-screen", SDLK_F11),
D("TX81Z_REQUEST", "Request voice from TX81Z", 0),
D("PANIC", "Panic", SDLK_F12), D("PANIC", "Panic", SDLK_F12),
D("WINDOW_EDIT_CONTROLS", "Edit Controls", 0), D("WINDOW_EDIT_CONTROLS", "Edit Controls", 0),
@ -833,6 +834,8 @@ const int availableSystems[]={
DIV_SYSTEM_YMU759, DIV_SYSTEM_YMU759,
DIV_SYSTEM_DUMMY, DIV_SYSTEM_DUMMY,
DIV_SYSTEM_SOUND_UNIT, DIV_SYSTEM_SOUND_UNIT,
DIV_SYSTEM_OPN,
DIV_SYSTEM_PC98,
DIV_SYSTEM_OPLL, DIV_SYSTEM_OPLL,
DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_OPLL_DRUMS,
DIV_SYSTEM_VRC7, DIV_SYSTEM_VRC7,
@ -858,6 +861,8 @@ const int availableSystems[]={
DIV_SYSTEM_VRC6, DIV_SYSTEM_VRC6,
DIV_SYSTEM_FDS, DIV_SYSTEM_FDS,
DIV_SYSTEM_MMC5, DIV_SYSTEM_MMC5,
DIV_SYSTEM_SCC,
DIV_SYSTEM_SCC_PLUS,
0 // don't remove this last one! 0 // don't remove this last one!
}; };

View file

@ -27,10 +27,6 @@
#include <imgui.h> #include <imgui.h>
#include "plot_nolerp.h" #include "plot_nolerp.h"
const unsigned char avRequest[15]={
0xf0, 0x43, 0x20, 0x7e, 0x4c, 0x4d, 0x20, 0x20, 0x38, 0x39, 0x37, 0x36, 0x41, 0x45, 0xf7
};
const char* ssgEnvTypes[8]={ const char* ssgEnvTypes[8]={
"Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN" "Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN"
}; };
@ -1364,7 +1360,6 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
// TODO: load replace
if (ImGui::Button(ICON_FA_FOLDER_OPEN "##IELoad")) { if (ImGui::Button(ICON_FA_FOLDER_OPEN "##IELoad")) {
doAction(GUI_ACTION_INS_LIST_OPEN_REPLACE); doAction(GUI_ACTION_INS_LIST_OPEN_REPLACE);
} }
@ -1477,14 +1472,7 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
if (ImGui::Button("Request from TX81Z")) { if (ImGui::Button("Request from TX81Z")) {
TAMidiMessage msg; doAction(GUI_ACTION_TX81Z_REQUEST);
msg.type=TA_MIDI_SYSEX;
msg.sysExData.reset(new unsigned char[15]);
msg.sysExLen=15;
memcpy(msg.sysExData.get(),avRequest,15);
if (!e->sendMidiMessage(msg)) {
showError("Error while sending request (MIDI output not configured?)");
}
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Send to TX81Z")) { if (ImGui::Button("Send to TX81Z")) {

View file

@ -425,6 +425,26 @@ void FurnaceGUI::initSystemPresets() {
0 0
} }
)); ));
cat.systems.push_back(FurnaceGUISysDef(
"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( cat.systems.push_back(FurnaceGUISysDef(
"ZX Spectrum (48K)", { "ZX Spectrum (48K)", {
DIV_SYSTEM_AY8910, 64, 0, 2, DIV_SYSTEM_AY8910, 64, 0, 2,
@ -585,7 +605,7 @@ void FurnaceGUI::initSystemPresets() {
cat.systems.push_back(FurnaceGUISysDef( cat.systems.push_back(FurnaceGUISysDef(
"Commander X16", { "Commander X16", {
DIV_SYSTEM_VERA, 64, 0, 0, DIV_SYSTEM_VERA, 64, 0, 0,
DIV_SYSTEM_YM2151, 64, 0, 0, DIV_SYSTEM_YM2151, 32, 0, 0,
0 0
} }
)); ));

View file

@ -419,11 +419,6 @@ void FurnaceGUI::drawSettings() {
settings.restartOnFlagChange=restartOnFlagChangeB; settings.restartOnFlagChange=restartOnFlagChangeB;
} }
bool insLoadAlwaysReplaceB=settings.insLoadAlwaysReplace;
if (ImGui::Checkbox("Always replace currently selected instrument when loading from instrument list",&insLoadAlwaysReplaceB)) {
settings.insLoadAlwaysReplace=insLoadAlwaysReplaceB;
}
bool sysFileDialogB=settings.sysFileDialog; bool sysFileDialogB=settings.sysFileDialog;
if (ImGui::Checkbox("Use system file picker",&sysFileDialogB)) { if (ImGui::Checkbox("Use system file picker",&sysFileDialogB)) {
settings.sysFileDialog=sysFileDialogB; settings.sysFileDialog=sysFileDialogB;
@ -1850,9 +1845,8 @@ void FurnaceGUI::syncSettings() {
settings.powerSave=e->getConfInt("powerSave",POWER_SAVE_DEFAULT); settings.powerSave=e->getConfInt("powerSave",POWER_SAVE_DEFAULT);
settings.absorbInsInput=e->getConfInt("absorbInsInput",0); settings.absorbInsInput=e->getConfInt("absorbInsInput",0);
settings.eventDelay=e->getConfInt("eventDelay",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.hiddenSystems=e->getConfInt("hiddenSystems",0);
settings.insLoadAlwaysReplace=e->getConfInt("insLoadAlwaysReplace",1);
settings.horizontalDataView=e->getConfInt("horizontalDataView",0); settings.horizontalDataView=e->getConfInt("horizontalDataView",0);
settings.noMultiSystem=e->getConfInt("noMultiSystem",0); settings.noMultiSystem=e->getConfInt("noMultiSystem",0);
@ -1925,7 +1919,6 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.eventDelay,0,1); clampSetting(settings.eventDelay,0,1);
clampSetting(settings.moveWindowTitle,0,1); clampSetting(settings.moveWindowTitle,0,1);
clampSetting(settings.hiddenSystems,0,1); clampSetting(settings.hiddenSystems,0,1);
clampSetting(settings.insLoadAlwaysReplace,0,1);
clampSetting(settings.horizontalDataView,0,1); clampSetting(settings.horizontalDataView,0,1);
clampSetting(settings.noMultiSystem,0,1) clampSetting(settings.noMultiSystem,0,1)
@ -2039,7 +2032,6 @@ void FurnaceGUI::commitSettings() {
e->setConf("moveWindowTitle",settings.moveWindowTitle); e->setConf("moveWindowTitle",settings.moveWindowTitle);
e->setConf("hiddenSystems",settings.hiddenSystems); e->setConf("hiddenSystems",settings.hiddenSystems);
e->setConf("initialSys",e->encodeSysDesc(settings.initialSys)); e->setConf("initialSys",e->encodeSysDesc(settings.initialSys));
e->setConf("insLoadAlwaysReplace",settings.insLoadAlwaysReplace);
e->setConf("horizontalDataView",settings.horizontalDataView); e->setConf("horizontalDataView",settings.horizontalDataView);
e->setConf("noMultiSystem",settings.noMultiSystem); e->setConf("noMultiSystem",settings.noMultiSystem);

View file

@ -355,6 +355,21 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
} }
break; 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_GB:
case DIV_SYSTEM_SWAN: case DIV_SYSTEM_SWAN:
case DIV_SYSTEM_VERA: case DIV_SYSTEM_VERA:
@ -367,6 +382,8 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YM2610B_EXT:
case DIV_SYSTEM_YMU759: case DIV_SYSTEM_YMU759:
case DIV_SYSTEM_PET: case DIV_SYSTEM_PET:
case DIV_SYSTEM_SCC:
case DIV_SYSTEM_SCC_PLUS:
ImGui::Text("nothing to configure"); ImGui::Text("nothing to configure");
break; break;
default: default: