mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-24 05:25:12 +00:00
Merge branch 'master' into feature/Moar-patch-bank-support-part3
This commit is contained in:
commit
4354e4064b
68 changed files with 4941 additions and 403 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -187,8 +187,6 @@ jobs:
|
|||
export USE_WAE=ON
|
||||
export CMAKE_EXTRA_ARGS=()
|
||||
if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then
|
||||
# 1. Go to hell
|
||||
export USE_WAE=OFF
|
||||
CMAKE_EXTRA_ARGS+=('-DCMAKE_GENERATOR_PLATFORM=${{ steps.windows-identify.outputs.msvc-target }}')
|
||||
|
||||
# Force static linking
|
||||
|
|
|
@ -45,7 +45,13 @@ option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendor
|
|||
option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF)
|
||||
|
||||
set(DEPENDENCIES_INCLUDE_DIRS "")
|
||||
|
||||
if (ANDROID)
|
||||
set(DEPENDENCIES_DEFINES "IS_MOBILE")
|
||||
else()
|
||||
set(DEPENDENCIES_DEFINES "")
|
||||
endif()
|
||||
|
||||
set(DEPENDENCIES_COMPILE_OPTIONS "")
|
||||
set(DEPENDENCIES_LIBRARIES "")
|
||||
set(DEPENDENCIES_LIBRARY_DIRS "")
|
||||
|
@ -310,6 +316,8 @@ src/engine/platform/sound/vic20sound.c
|
|||
|
||||
src/engine/platform/sound/vrcvi/vrcvi.cpp
|
||||
|
||||
src/engine/platform/sound/scc/scc.cpp
|
||||
|
||||
src/engine/platform/ym2610Interface.cpp
|
||||
|
||||
src/engine/blip_buf.c
|
||||
|
@ -343,6 +351,7 @@ src/engine/platform/nes.cpp
|
|||
src/engine/platform/c64.cpp
|
||||
src/engine/platform/arcade.cpp
|
||||
src/engine/platform/tx81z.cpp
|
||||
src/engine/platform/ym2203.cpp
|
||||
src/engine/platform/ym2610.cpp
|
||||
src/engine/platform/ym2610ext.cpp
|
||||
src/engine/platform/ym2610b.cpp
|
||||
|
@ -367,6 +376,7 @@ src/engine/platform/n163.cpp
|
|||
src/engine/platform/pet.cpp
|
||||
src/engine/platform/vic20.cpp
|
||||
src/engine/platform/vrc6.cpp
|
||||
src/engine/platform/scc.cpp
|
||||
src/engine/platform/dummy.cpp
|
||||
)
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ Available options:
|
|||
|
||||
| Name | Default | Description |
|
||||
| :--: | :-----: | ----------- |
|
||||
| `BUILD_GUI` | `ON` if not building for Android, otherwise `OFF` | Build the tracker (disable to build only a headless player) |
|
||||
| `BUILD_GUI` | `ON` | Build the tracker (disable to build only a headless player) |
|
||||
| `WITH_JACK` | `ON` if system-installed JACK detected, otherwise `OFF` | Whether to build with JACK support. Auto-detects if JACK is available |
|
||||
| `SYSTEM_FMT` | `OFF` | Use a system-installed version of fmt instead of the vendored one |
|
||||
| `SYSTEM_LIBSNDFILE` | `OFF` | Use a system-installed version of libsndfile instead of the vendored one |
|
||||
|
|
1
TODO.md
1
TODO.md
|
@ -9,7 +9,6 @@
|
|||
- OPNA system
|
||||
- ZX beeper system
|
||||
- Y8950 system
|
||||
- SCC/SCC+ system
|
||||
- maybe YMU759 ADPCM channel
|
||||
- ADPCM chips
|
||||
- Game Boy envelope macro/sequence
|
||||
|
|
|
@ -232,6 +232,9 @@ size | description
|
|||
4f | A-4 tuning
|
||||
1 | limit slides (>=36) or reserved
|
||||
1 | linear pitch (>=36) or reserved
|
||||
| - 0: non-linaer
|
||||
| - 1: only pitch change (04xy/E5xx) linear
|
||||
| - 2: full linear (>=94)
|
||||
1 | loop modality (>=36) or reserved
|
||||
1 | proper noise layout (>=42) or reserved
|
||||
1 | wave duty is volume (>=42) or reserved
|
||||
|
@ -286,7 +289,8 @@ size | description
|
|||
1 | weird f-num/block-based chip pitch slides (>=85) or reserved
|
||||
1 | SN duty macro always resets phase (>=86) or reserved
|
||||
1 | pitch macro is linear (>=90) or reserved
|
||||
19 | reserved
|
||||
1 | pitch slide speed in full linear pitch mode (>=94) or reserved
|
||||
18 | reserved
|
||||
```
|
||||
|
||||
# instrument
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "platform/c64.h"
|
||||
#include "platform/arcade.h"
|
||||
#include "platform/tx81z.h"
|
||||
#include "platform/ym2203.h"
|
||||
#include "platform/ym2610.h"
|
||||
#include "platform/ym2610ext.h"
|
||||
#include "platform/ym2610b.h"
|
||||
|
@ -54,6 +55,7 @@
|
|||
#include "platform/vrc6.h"
|
||||
#include "platform/fds.h"
|
||||
#include "platform/mmc5.h"
|
||||
#include "platform/scc.h"
|
||||
#include "platform/dummy.h"
|
||||
#include "../ta-log.h"
|
||||
#include "song.h"
|
||||
|
@ -233,6 +235,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_TIA:
|
||||
dispatch=new DivPlatformTIA;
|
||||
break;
|
||||
case DIV_SYSTEM_OPN:
|
||||
dispatch=new DivPlatformYM2203;
|
||||
break;
|
||||
case DIV_SYSTEM_OPLL:
|
||||
case DIV_SYSTEM_OPLL_DRUMS:
|
||||
case DIV_SYSTEM_VRC7:
|
||||
|
@ -314,6 +319,14 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_MMC5:
|
||||
dispatch=new DivPlatformMMC5;
|
||||
break;
|
||||
case DIV_SYSTEM_SCC:
|
||||
dispatch=new DivPlatformSCC;
|
||||
((DivPlatformSCC*)dispatch)->setChipModel(false);
|
||||
break;
|
||||
case DIV_SYSTEM_SCC_PLUS:
|
||||
dispatch=new DivPlatformSCC;
|
||||
((DivPlatformSCC*)dispatch)->setChipModel(true);
|
||||
break;
|
||||
case DIV_SYSTEM_SOUND_UNIT:
|
||||
dispatch=new DivPlatformSoundUnit;
|
||||
break;
|
||||
|
|
|
@ -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) {
|
||||
if (song.linearPitch==2) { // full linear
|
||||
return (note<<7);
|
||||
}
|
||||
double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0);
|
||||
return period?
|
||||
(clock/base)/divider:
|
||||
base*(divider/clock);
|
||||
}
|
||||
|
||||
unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) {
|
||||
int bf=calcBaseFreq(clock,divider,note,false);
|
||||
int block=note/12;
|
||||
if (block<0) block=0;
|
||||
if (block>7) block=7;
|
||||
bf>>=block;
|
||||
if (bf<0) bf=0;
|
||||
// octave boundaries
|
||||
while (bf>0 && bf<644 && block>0) {
|
||||
bf<<=1;
|
||||
block--;
|
||||
}
|
||||
if (bf>1288) {
|
||||
while (block<7) {
|
||||
bf>>=1;
|
||||
block++;
|
||||
}
|
||||
if (bf>((1<<bits)-1)) {
|
||||
bf=(1<<bits)-1;
|
||||
}
|
||||
}
|
||||
#define CONVERT_FNUM_BLOCK(bf,bits,note) \
|
||||
double tuning=song.tuning; \
|
||||
if (tuning<400.0) tuning=400.0; \
|
||||
if (tuning>500.0) tuning=500.0; \
|
||||
int boundaryBottom=tuning*pow(2.0,0.25)*(divider/clock); \
|
||||
int boundaryTop=2.0*tuning*pow(2.0,0.25)*(divider/clock); \
|
||||
int block=(note)/12; \
|
||||
if (block<0) block=0; \
|
||||
if (block>7) block=7; \
|
||||
bf>>=block; \
|
||||
if (bf<0) bf=0; \
|
||||
/* octave boundaries */ \
|
||||
while (bf>0 && bf<boundaryBottom && block>0) { \
|
||||
bf<<=1; \
|
||||
block--; \
|
||||
} \
|
||||
if (bf>boundaryTop) { \
|
||||
while (block<7 && bf>boundaryTop) { \
|
||||
bf>>=1; \
|
||||
block++; \
|
||||
} \
|
||||
if (bf>((1<<bits)-1)) { \
|
||||
bf=(1<<bits)-1; \
|
||||
} \
|
||||
} \
|
||||
/* logV("f-num: %d block: %d",bf,block); */ \
|
||||
return bf|(block<<bits);
|
||||
|
||||
int DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) {
|
||||
if (song.linearPitch==2) { // full linear
|
||||
return (note<<7);
|
||||
}
|
||||
int bf=calcBaseFreq(clock,divider,note,false);
|
||||
CONVERT_FNUM_BLOCK(bf,bits,note)
|
||||
}
|
||||
|
||||
int DivEngine::calcFreq(int base, int pitch, bool period, int octave, int pitch2) {
|
||||
if (song.linearPitch) {
|
||||
int DivEngine::calcFreq(int base, int pitch, bool period, int octave, int pitch2, double clock, double divider, int blockBits) {
|
||||
if (song.linearPitch==2) {
|
||||
// do frequency calculation here
|
||||
int nbase=base+pitch+pitch2;
|
||||
double fbase=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(nbase+384)/(128.0*12.0));
|
||||
int bf=period?
|
||||
round((clock/fbase)/divider):
|
||||
round(fbase*(divider/clock));
|
||||
if (blockBits>0) {
|
||||
CONVERT_FNUM_BLOCK(bf,blockBits,nbase>>7)
|
||||
} else {
|
||||
return bf;
|
||||
}
|
||||
}
|
||||
if (song.linearPitch==1) {
|
||||
// global pitch multiplier
|
||||
int whatTheFuck=(1024+(globalPitch<<6)-(globalPitch<0?globalPitch-6:0));
|
||||
if (whatTheFuck<1) whatTheFuck=1; // avoids division by zero but please kill me
|
||||
|
@ -1209,7 +1237,7 @@ void DivEngine::reset() {
|
|||
chan[i]=DivChannelState();
|
||||
if (i<chans) chan[i].volMax=(disCont[dispatchOfChan[i]].dispatch->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff;
|
||||
chan[i].volume=chan[i].volMax;
|
||||
if (!song.linearPitch) chan[i].vibratoFine=4;
|
||||
if (song.linearPitch==0) chan[i].vibratoFine=4;
|
||||
}
|
||||
extValue=0;
|
||||
extValuePresent=0;
|
||||
|
|
|
@ -45,8 +45,8 @@
|
|||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||
|
||||
#define DIV_VERSION "dev93"
|
||||
#define DIV_ENGINE_VERSION 93
|
||||
#define DIV_VERSION "dev94"
|
||||
#define DIV_ENGINE_VERSION 94
|
||||
|
||||
// for imports
|
||||
#define DIV_VERSION_MOD 0xff01
|
||||
|
@ -484,10 +484,10 @@ class DivEngine {
|
|||
double calcBaseFreq(double clock, double divider, int note, bool period);
|
||||
|
||||
// calculate base frequency in f-num/block format
|
||||
unsigned short calcBaseFreqFNumBlock(double clock, double divider, int note, int bits);
|
||||
int calcBaseFreqFNumBlock(double clock, double divider, int note, int bits);
|
||||
|
||||
// calculate frequency/period
|
||||
int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0);
|
||||
int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0, double clock=1.0, double divider=1.0, int blockBits=0);
|
||||
|
||||
// convert panning formats
|
||||
int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range);
|
||||
|
|
|
@ -139,7 +139,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
|
||||
// compatibility flags
|
||||
ds.limitSlides=true;
|
||||
ds.linearPitch=true;
|
||||
ds.linearPitch=1;
|
||||
ds.loopModality=0;
|
||||
ds.properNoiseLayout=false;
|
||||
ds.waveDutyIsVol=false;
|
||||
|
@ -173,12 +173,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
ds.legacyVolumeSlides=false;
|
||||
}
|
||||
|
||||
// Neo Geo detune
|
||||
// Neo Geo detune is caused by Defle running Neo Geo at the wrong clock.
|
||||
/*
|
||||
if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT
|
||||
|| ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT
|
||||
|| ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) {
|
||||
ds.tuning=443.23;
|
||||
}
|
||||
*/
|
||||
|
||||
logI("reading module data...");
|
||||
if (ds.version>0x0c) {
|
||||
|
@ -948,7 +950,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
|
||||
if (ds.version<37) { // compat flags not stored back then
|
||||
ds.limitSlides=true;
|
||||
ds.linearPitch=true;
|
||||
ds.linearPitch=1;
|
||||
ds.loopModality=0;
|
||||
}
|
||||
if (ds.version<43) {
|
||||
|
@ -1390,7 +1392,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
for (int i=0; i<19; i++) {
|
||||
if (ds.version>=94) {
|
||||
ds.pitchSlideSpeed=reader.readC();
|
||||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
for (int i=0; i<18; i++) {
|
||||
reader.readC();
|
||||
}
|
||||
}
|
||||
|
@ -1647,7 +1654,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
DivSong ds;
|
||||
ds.tuning=436.0;
|
||||
ds.version=DIV_VERSION_MOD;
|
||||
ds.linearPitch=false;
|
||||
ds.linearPitch=0;
|
||||
ds.noSlidesOnFirstTick=true;
|
||||
ds.rowResetsArpPos=true;
|
||||
ds.ignoreJumpAtEnd=false;
|
||||
|
@ -2700,7 +2707,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
w->writeC(song.fbPortaPause);
|
||||
w->writeC(song.snDutyReset);
|
||||
w->writeC(song.pitchMacroIsLinear);
|
||||
for (int i=0; i<19; i++) {
|
||||
w->writeC(song.pitchSlideSpeed);
|
||||
for (int i=0; i<18; i++) {
|
||||
w->writeC(0);
|
||||
}
|
||||
|
||||
|
|
|
@ -220,7 +220,7 @@ void DivPlatformAmiga::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].keyOn) {
|
||||
|
|
|
@ -251,7 +251,7 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
if (chan[i].keyOn) {
|
||||
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
||||
|
|
|
@ -280,7 +280,7 @@ void DivPlatformAY8930::tick(bool sysTick) {
|
|||
immWrite(0x1a,ayNoiseOr);
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
if (chan[i].keyOn) {
|
||||
if (chan[i].insChanged) {
|
||||
|
|
|
@ -137,7 +137,7 @@ void DivPlatformBubSysWSG::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SCC);
|
||||
chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)+chan[i].pitch2;
|
||||
chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
k005289->load(i,chan[i].freq);
|
||||
|
|
|
@ -217,7 +217,7 @@ void DivPlatformC64::tick(bool sysTick) {
|
|||
}
|
||||
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
|
||||
if (chan[i].keyOn) {
|
||||
rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay));
|
||||
|
|
|
@ -61,7 +61,7 @@ void DivPlatformDummy::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freqChanged=false;
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,0,chipClock,CHIP_FREQBASE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -220,7 +220,7 @@ void DivPlatformFDS::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].keyOn) {
|
||||
|
|
|
@ -237,7 +237,7 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
if (ntPos>255) ntPos=255;
|
||||
chan[i].freq=noiseTable[ntPos];
|
||||
} else {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].freq>2047) chan[i].freq=2047;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
}
|
||||
|
|
|
@ -471,17 +471,21 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
for (int i=0; i<6; i++) {
|
||||
if (i==2 && extMode) continue;
|
||||
if (chan[i].freqChanged) {
|
||||
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2);
|
||||
int block=(chan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
while (block<7) {
|
||||
fNum>>=1;
|
||||
block++;
|
||||
if (parent->song.linearPitch==2) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
} else {
|
||||
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
int block=(chan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
while (block<7) {
|
||||
fNum>>=1;
|
||||
block++;
|
||||
}
|
||||
if (fNum>2047) fNum=2047;
|
||||
}
|
||||
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;
|
||||
immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8);
|
||||
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
||||
|
@ -495,7 +499,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
off=(double)s->centerRate/8363.0;
|
||||
}
|
||||
}
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4)+chan[i].pitch2;
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,1,1);
|
||||
dacRate=chan[i].freq*off;
|
||||
if (dacRate<1) dacRate=1;
|
||||
if (dumpWrites) addWrite(0xffff0001,dacRate);
|
||||
|
@ -702,6 +706,29 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
if (parent->song.linearPitch==2) {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value;
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (c.chan==5 && chan[c.chan].furnaceDac && dacMode) {
|
||||
int destFreq=parent->calcBaseFreq(1,1,c.value2,false);
|
||||
bool return2=false;
|
||||
|
@ -725,6 +752,8 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
|
||||
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
|
||||
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||
int newFreq;
|
||||
bool return2=false;
|
||||
|
@ -747,13 +776,13 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
// check for octave boundary
|
||||
// what the heck!
|
||||
if (!chan[c.chan].portaPause) {
|
||||
if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) {
|
||||
chan[c.chan].portaPauseFreq=(644)|((newFreq+0x800)&0xf800);
|
||||
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||
chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||
chan[c.chan].portaPause=true;
|
||||
break;
|
||||
}
|
||||
if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) {
|
||||
chan[c.chan].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800);
|
||||
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||
chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||
chan[c.chan].portaPause=true;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -127,6 +127,31 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
if (parent->song.linearPitch==2) {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>opChan[ch].baseFreq) {
|
||||
opChan[ch].baseFreq+=c.value;
|
||||
if (opChan[ch].baseFreq>=destFreq) {
|
||||
opChan[ch].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
opChan[ch].baseFreq-=c.value;
|
||||
if (opChan[ch].baseFreq<=destFreq) {
|
||||
opChan[ch].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
opChan[ch].freqChanged=true;
|
||||
if (return2) {
|
||||
//opChan[ch].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
|
||||
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
|
||||
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||
int newFreq;
|
||||
bool return2=false;
|
||||
|
@ -148,18 +173,18 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
}
|
||||
// what the heck!
|
||||
if (!opChan[ch].portaPause) {
|
||||
if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) {
|
||||
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||
if (parent->song.fbPortaPause) {
|
||||
opChan[ch].portaPauseFreq=(644)|((newFreq+0x800)&0xf800);
|
||||
opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
} else {
|
||||
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
|
||||
}
|
||||
}
|
||||
if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) {
|
||||
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||
if (parent->song.fbPortaPause) {
|
||||
opChan[ch].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800);
|
||||
opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
} else {
|
||||
|
@ -435,17 +460,21 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
unsigned char writeMask=2;
|
||||
if (extMode) for (int i=0; i<4; i++) {
|
||||
if (opChan[i].freqChanged) {
|
||||
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
|
||||
int block=(opChan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
while (block<7) {
|
||||
fNum>>=1;
|
||||
block++;
|
||||
if (parent->song.linearPitch==2) {
|
||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
} else {
|
||||
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
|
||||
int block=(opChan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
while (block<7) {
|
||||
fNum>>=1;
|
||||
block++;
|
||||
}
|
||||
if (fNum>2047) fNum=2047;
|
||||
}
|
||||
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;
|
||||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||
|
|
|
@ -201,7 +201,7 @@ void DivPlatformLynx::tick(bool sysTick) {
|
|||
WRITE_OTHER(i, ((chan[i].lfsr&0xf00)>>4));
|
||||
chan[i].lfsr=-1;
|
||||
}
|
||||
chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
WRITE_FEEDBACK(i, chan[i].duty.feedback);
|
||||
|
|
|
@ -146,7 +146,7 @@ void DivPlatformMMC5::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
|
||||
if (chan[i].freq>2047) chan[i].freq=2047;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].keyOn) {
|
||||
|
|
|
@ -357,7 +357,7 @@ void DivPlatformN163::tick(bool sysTick) {
|
|||
chan[i].waveUpdated=false;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff;
|
||||
if (chan[i].keyOn) {
|
||||
|
|
|
@ -308,7 +308,7 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
if (ntPos>252) ntPos=252;
|
||||
chan[i].freq=(parent->song.properNoiseLayout)?(15-(chan[i].baseFreq&15)):(noiseTable[ntPos]);
|
||||
} else {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
|
||||
if (chan[i].freq>2047) chan[i].freq=2047;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
}
|
||||
|
|
|
@ -511,7 +511,7 @@ void DivPlatformOPL::tick(bool sysTick) {
|
|||
bool updateDrums=false;
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].freq>131071) chan[i].freq=131071;
|
||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
||||
chan[i].freqH=freqt>>8;
|
||||
|
@ -800,19 +800,19 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
int newFreq;
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
|
||||
newFreq=chan[c.chan].baseFreq+c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq));
|
||||
if (newFreq>=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
|
||||
newFreq=chan[c.chan].baseFreq-c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq));
|
||||
if (newFreq<=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
if (!chan[c.chan].portaPause) {
|
||||
if (!chan[c.chan].portaPause && parent->song.linearPitch!=2) {
|
||||
if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) {
|
||||
chan[c.chan].portaPause=true;
|
||||
break;
|
||||
|
|
|
@ -300,7 +300,7 @@ void DivPlatformOPLL::tick(bool sysTick) {
|
|||
|
||||
for (int i=0; i<11; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].freq>262143) chan[i].freq=262143;
|
||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
||||
chan[i].freqL=freqt&0xff;
|
||||
|
@ -578,13 +578,13 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
|
|||
int newFreq;
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
|
||||
newFreq=chan[c.chan].baseFreq+c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq));
|
||||
if (newFreq>=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
|
||||
newFreq=chan[c.chan].baseFreq-c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq));
|
||||
if (newFreq<=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
|
|
|
@ -227,7 +227,7 @@ void DivPlatformPCE::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].furnaceDac) {
|
||||
double off=1.0;
|
||||
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
|
||||
|
|
|
@ -223,7 +223,7 @@ void DivPlatformPCSpeaker::tick(bool sysTick) {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
if (chan[i].keyOn) {
|
||||
|
|
|
@ -118,7 +118,7 @@ void DivPlatformPET::tick(bool sysTick) {
|
|||
chan.freqChanged=true;
|
||||
}
|
||||
if (chan.freqChanged || chan.keyOn || chan.keyOff) {
|
||||
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2);
|
||||
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan.freq>257) chan.freq=257;
|
||||
if (chan.freq<2) chan.freq=2;
|
||||
rWrite(8,chan.freq-2);
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#include <map>
|
||||
|
||||
#define CHIP_DIVIDER (1248*2)
|
||||
#define QS_NOTE_FREQUENCY(x) parent->calcBaseFreq(440,0x1000,(x)-3,false)
|
||||
#define QS_NOTE_FREQUENCY(x) parent->calcBaseFreq(440,4096,(x)-3,false)
|
||||
|
||||
#define rWrite(a,v) {if(!skipRegisterWrites) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v); }}
|
||||
#define immWrite(a,v) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v);}
|
||||
|
@ -296,14 +296,8 @@ void DivPlatformQSound::tick(bool sysTick) {
|
|||
uint16_t qsound_addr = 0;
|
||||
uint16_t qsound_loop = 0;
|
||||
uint16_t qsound_end = 0;
|
||||
double off=1.0;
|
||||
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=(double)s->centerRate/24038.0/16.0;
|
||||
}
|
||||
qsound_bank = 0x8000 | (s->offQSound >> 16);
|
||||
qsound_addr = s->offQSound & 0xffff;
|
||||
|
||||
|
@ -322,15 +316,15 @@ void DivPlatformQSound::tick(bool sysTick) {
|
|||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
@ -354,7 +348,16 @@ void DivPlatformQSound::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
|
||||
double off=1.0;
|
||||
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=(double)s->centerRate/24038.0/16.0;
|
||||
}
|
||||
}
|
||||
chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,440.0,4096.0);
|
||||
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
|
||||
if (chan[i].keyOn) {
|
||||
rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank);
|
||||
|
@ -388,17 +391,8 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
chan[c.chan].sample=ins->amiga.initSample;
|
||||
double off=1.0;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=(double)s->centerRate/24038.0/16.0;
|
||||
}
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value);
|
||||
}
|
||||
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
|
||||
chan[c.chan].sample=-1;
|
||||
|
@ -467,16 +461,7 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
double off=1.0;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=(double)s->centerRate/24038.0/16.0;
|
||||
}
|
||||
}
|
||||
int destFreq=off*QS_NOTE_FREQUENCY(c.value2);
|
||||
int destFreq=QS_NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
|
@ -499,16 +484,7 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO: {
|
||||
double off=1.0;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=(double)s->centerRate/24038.0/16.0;
|
||||
}
|
||||
}
|
||||
chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
|
||||
chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
|
|
|
@ -200,7 +200,7 @@ void DivPlatformSAA1099::tick(bool sysTick) {
|
|||
rWrite(0x18+(i/3),saaEnv[i/3]);
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
if (chan[i].freq>=32768) {
|
||||
chan[i].freqH=7;
|
||||
|
@ -311,13 +311,13 @@ int DivPlatformSAA1099::dispatch(DivCommand c) {
|
|||
int destFreq=NOTE_PERIODIC(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value*(8-chan[c.chan].freqH);
|
||||
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(8-chan[c.chan].freqH));
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value*(8-chan[c.chan].freqH);
|
||||
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:(8-chan[c.chan].freqH));
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
|
|
391
src/engine/platform/scc.cpp
Normal file
391
src/engine/platform/scc.cpp
Normal 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
91
src/engine/platform/scc.h
Normal 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
|
|
@ -121,7 +121,7 @@ void DivPlatformSMS::tick(bool sysTick) {
|
|||
}
|
||||
for (int i=0; i<3; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,64);
|
||||
if (chan[i].freq>1023) chan[i].freq=1023;
|
||||
if (chan[i].freq<8) chan[i].freq=1;
|
||||
//if (chan[i].actualNote>0x5d) chan[i].freq=0x01;
|
||||
|
@ -136,7 +136,7 @@ void DivPlatformSMS::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[3].freqChanged || updateSNMode) {
|
||||
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2);
|
||||
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,isRealSN?60:64);
|
||||
if (chan[3].freq>1023) chan[3].freq=1023;
|
||||
if (chan[3].actualNote>0x5d) chan[3].freq=0x01;
|
||||
if (snNoiseMode&2) { // take period from channel 3
|
||||
|
|
612
src/engine/platform/sound/scc/scc.cpp
Normal file
612
src/engine/platform/sound/scc/scc.cpp
Normal 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;
|
||||
}
|
137
src/engine/platform/sound/scc/scc.hpp
Normal file
137
src/engine/platform/sound/scc/scc.hpp
Normal 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
|
|
@ -183,7 +183,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].pcm) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
|
||||
DivSample* sample=parent->getSample(ins->amiga.initSample);
|
||||
|
@ -298,13 +298,13 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
|||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value*(1+(chan[c.chan].baseFreq>>9));
|
||||
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9)));
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value*(1+(chan[c.chan].baseFreq>>9));
|
||||
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9)));
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
|
|
|
@ -203,7 +203,7 @@ void DivPlatformSwan::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (i==1 && pcm && furnaceDac) {
|
||||
double off=1.0;
|
||||
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
|
||||
|
|
|
@ -151,16 +151,16 @@ int DivPlatformVERA::calcNoteFreq(int ch, int note) {
|
|||
if (ch<16) {
|
||||
return parent->calcBaseFreq(chipClock,2097152,note,false);
|
||||
} else {
|
||||
double off=1.0;
|
||||
double off=65536.0;
|
||||
if (chan[ch].pcm.sample>=0 && chan[ch].pcm.sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[ch].pcm.sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
off=65536.0;
|
||||
} else {
|
||||
off=s->centerRate/8363.0;
|
||||
off=65536.0*(s->centerRate/8363.0);
|
||||
}
|
||||
}
|
||||
return (int)(off*parent->calcBaseFreq(chipClock,65536,note,false));
|
||||
return (int)(parent->calcBaseFreq(chipClock,off,note,false));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,7 +208,7 @@ void DivPlatformVERA::tick(bool sysTick) {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2,chipClock,2097152);
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
rWrite(i,0,chan[i].freq&0xff);
|
||||
rWrite(i,1,(chan[i].freq>>8)&0xff);
|
||||
|
@ -237,7 +237,16 @@ void DivPlatformVERA::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[16].freqChanged) {
|
||||
chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8,chan[16].pitch2);
|
||||
double off=65536.0;
|
||||
if (chan[16].pcm.sample>=0 && chan[16].pcm.sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[16].pcm.sample);
|
||||
if (s->centerRate<1) {
|
||||
off=65536.0;
|
||||
} else {
|
||||
off=65536.0*(s->centerRate/8363.0);
|
||||
}
|
||||
}
|
||||
chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8,chan[16].pitch2,chipClock,off);
|
||||
if (chan[16].freq>128) chan[16].freq=128;
|
||||
rWritePCMRate(chan[16].freq&0xff);
|
||||
chan[16].freqChanged=false;
|
||||
|
@ -248,8 +257,8 @@ int DivPlatformVERA::dispatch(DivCommand c) {
|
|||
int tmp;
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON:
|
||||
if(c.chan<16) {
|
||||
rWriteLo(c.chan,2,chan[c.chan].vol)
|
||||
if (c.chan<16) {
|
||||
rWriteLo(c.chan,2,chan[c.chan].vol);
|
||||
} else {
|
||||
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) {
|
||||
|
@ -297,11 +306,15 @@ int DivPlatformVERA::dispatch(DivCommand c) {
|
|||
if (c.chan<16) {
|
||||
tmp=c.value&0x3f;
|
||||
chan[c.chan].vol=tmp;
|
||||
rWriteLo(c.chan,2,tmp);
|
||||
if (chan[c.chan].active) {
|
||||
rWriteLo(c.chan,2,tmp);
|
||||
}
|
||||
} else {
|
||||
tmp=c.value&0x0f;
|
||||
chan[c.chan].vol=tmp;
|
||||
rWritePCMVol(tmp);
|
||||
if (chan[c.chan].active) {
|
||||
rWritePCMVol(tmp);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
|
@ -401,7 +414,7 @@ void DivPlatformVERA::muteChannel(int ch, bool mute) {
|
|||
}
|
||||
|
||||
float DivPlatformVERA::getPostAmp() {
|
||||
return 8.0f;
|
||||
return 4.0f;
|
||||
}
|
||||
|
||||
bool DivPlatformVERA::isStereo() {
|
||||
|
|
|
@ -132,7 +132,7 @@ void DivPlatformVIC20::tick(bool sysTick) {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (i<3) {
|
||||
chan[i].freq>>=(2-i);
|
||||
} else {
|
||||
|
|
|
@ -197,9 +197,9 @@ void DivPlatformVRC6::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
if (i==2) { // sawtooth
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,14)-1;
|
||||
} else { // pulse
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,16)-1;
|
||||
if (chan[i].furnaceDac) {
|
||||
double off=1.0;
|
||||
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
|
||||
|
|
|
@ -262,17 +262,17 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l
|
|||
|
||||
double DivPlatformX1_010::NoteX1_010(int ch, int note) {
|
||||
if (chan[ch].pcm) { // PCM note
|
||||
double off=1.0;
|
||||
double off=8192.0;
|
||||
int sample=chan[ch].sample;
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
off=8192.0;
|
||||
} else {
|
||||
off=s->centerRate/8363.0;
|
||||
off=8192.0*(s->centerRate/8363.0);
|
||||
}
|
||||
}
|
||||
return off*parent->calcBaseFreq(chipClock,8192,note,false);
|
||||
return parent->calcBaseFreq(chipClock,off,note,false);
|
||||
}
|
||||
// Wavetable note
|
||||
return NOTE_FREQUENCY(note);
|
||||
|
@ -487,7 +487,19 @@ void DivPlatformX1_010::tick(bool sysTick) {
|
|||
chan[i].envChanged=false;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
|
||||
double off=8192.0;
|
||||
if (chan[i].pcm) {
|
||||
int sample=chan[i].sample;
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(sample);
|
||||
if (s->centerRate<1) {
|
||||
off=8192.0;
|
||||
} else {
|
||||
off=8192.0*(s->centerRate/8363.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,chan[i].pcm?off:CHIP_FREQBASE);
|
||||
if (chan[i].pcm) {
|
||||
if (chan[i].freq<1) chan[i].freq=1;
|
||||
if (chan[i].freq>255) chan[i].freq=255;
|
||||
|
|
1127
src/engine/platform/ym2203.cpp
Normal file
1127
src/engine/platform/ym2203.cpp
Normal file
File diff suppressed because it is too large
Load diff
134
src/engine/platform/ym2203.h
Normal file
134
src/engine/platform/ym2203.h
Normal 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
|
45
src/engine/platform/ym2203shared.h
Normal file
45
src/engine/platform/ym2203shared.h
Normal 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
|
1449
src/engine/platform/ym2608.cpp
Normal file
1449
src/engine/platform/ym2608.cpp
Normal file
File diff suppressed because it is too large
Load diff
130
src/engine/platform/ym2608.h
Normal file
130
src/engine/platform/ym2608.h
Normal 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
|
|
@ -456,13 +456,13 @@ double DivPlatformYM2610::NOTE_OPNB(int ch, int note) {
|
|||
return NOTE_PERIODIC(note);
|
||||
}
|
||||
// FM
|
||||
return NOTE_FREQUENCY(note);
|
||||
return NOTE_FNUM_BLOCK(note,11);
|
||||
}
|
||||
|
||||
double DivPlatformYM2610::NOTE_ADPCMB(int note) {
|
||||
if (chan[13].sample>=0 && chan[13].sample<parent->song.sampleLen) {
|
||||
double off=(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0;
|
||||
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false);
|
||||
double off=65535.0*(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0;
|
||||
return parent->calcBaseFreq((double)chipClock/144,off,note,false);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -559,15 +559,15 @@ void DivPlatformYM2610::tick(bool sysTick) {
|
|||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
@ -726,7 +726,12 @@ void DivPlatformYM2610::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[13].freqChanged) {
|
||||
chan[13].freq=parent->calcFreq(chan[13].baseFreq,chan[13].pitch,false,4);
|
||||
if (chan[13].sample>=0 && chan[13].sample<parent->song.sampleLen) {
|
||||
double off=65535.0*(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0;
|
||||
chan[13].freq=parent->calcFreq(chan[13].baseFreq,chan[13].pitch,false,4,chan[13].pitch2,(double)chipClock/144,off);
|
||||
} else {
|
||||
chan[13].freq=0;
|
||||
}
|
||||
immWrite(0x19,chan[13].freq&0xff);
|
||||
immWrite(0x1a,(chan[13].freq>>8)&0xff);
|
||||
chan[13].freqChanged=false;
|
||||
|
@ -742,11 +747,24 @@ void DivPlatformYM2610::tick(bool sysTick) {
|
|||
for (int i=0; i<4; i++) {
|
||||
if (i==1 && extMode) continue;
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
|
||||
if (chan[i].freq>262143) chan[i].freq=262143;
|
||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
||||
immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8);
|
||||
immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff);
|
||||
if (parent->song.linearPitch==2) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
} else {
|
||||
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
int block=(chan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
while (block<7) {
|
||||
fNum>>=1;
|
||||
block++;
|
||||
}
|
||||
if (fNum>2047) fNum=2047;
|
||||
}
|
||||
chan[i].freq=(block<<11)|fNum;
|
||||
}
|
||||
if (chan[i].freq>0x3fff) chan[i].freq=0x3fff;
|
||||
immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8);
|
||||
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
|
@ -756,47 +774,6 @@ void DivPlatformYM2610::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
|
||||
int DivPlatformYM2610::octave(int freq) {
|
||||
if (freq>=622.0f*128) {
|
||||
return 128;
|
||||
} else if (freq>=622.0f*64) {
|
||||
return 64;
|
||||
} else if (freq>=622.0f*32) {
|
||||
return 32;
|
||||
} else if (freq>=622.0f*16) {
|
||||
return 16;
|
||||
} else if (freq>=622.0f*8) {
|
||||
return 8;
|
||||
} else if (freq>=622.0f*4) {
|
||||
return 4;
|
||||
} else if (freq>=622.0f*2) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DivPlatformYM2610::toFreq(int freq) {
|
||||
if (freq>=622.0f*128) {
|
||||
return 0x3800|((freq>>7)&0x7ff);
|
||||
} else if (freq>=622.0f*64) {
|
||||
return 0x3000|((freq>>6)&0x7ff);
|
||||
} else if (freq>=622.0f*32) {
|
||||
return 0x2800|((freq>>5)&0x7ff);
|
||||
} else if (freq>=622.0f*16) {
|
||||
return 0x2000|((freq>>4)&0x7ff);
|
||||
} else if (freq>=622.0f*8) {
|
||||
return 0x1800|((freq>>3)&0x7ff);
|
||||
} else if (freq>=622.0f*4) {
|
||||
return 0x1000|((freq>>2)&0x7ff);
|
||||
} else if (freq>=622.0f*2) {
|
||||
return 0x800|((freq>>1)&0x7ff);
|
||||
} else {
|
||||
return freq&0x7ff;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformYM2610::dispatch(DivCommand c) {
|
||||
if (c.chan>3 && c.chan<7) {
|
||||
c.chan-=4;
|
||||
|
@ -928,7 +905,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
chan[c.chan].insChanged=false;
|
||||
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
chan[c.chan].portaPause=false;
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
|
@ -1025,7 +1002,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
if (c.chan>3) { // PSG, ADPCM-B
|
||||
if (c.chan>3 || parent->song.linearPitch==2) { // PSG, ADPCM-B
|
||||
int destFreq=NOTE_OPNB(c.chan,c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
|
@ -1048,33 +1025,48 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
|
||||
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
|
||||
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||
int newFreq;
|
||||
bool return2=false;
|
||||
if (chan[c.chan].portaPause) {
|
||||
chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq;
|
||||
}
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
|
||||
newFreq=chan[c.chan].baseFreq+c.value;
|
||||
if (newFreq>=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
|
||||
newFreq=chan[c.chan].baseFreq-c.value;
|
||||
if (newFreq<=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
// check for octave boundary
|
||||
// what the heck!
|
||||
if (!chan[c.chan].portaPause) {
|
||||
if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) {
|
||||
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||
chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||
chan[c.chan].portaPause=true;
|
||||
break;
|
||||
}
|
||||
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||
chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||
chan[c.chan].portaPause=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
chan[c.chan].baseFreq=newFreq;
|
||||
chan[c.chan].portaPause=false;
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) return 2;
|
||||
chan[c.chan].baseFreq=newFreq;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
|
|
|
@ -61,7 +61,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
|
|||
struct Channel {
|
||||
DivInstrumentFM state;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
|
||||
|
@ -80,6 +80,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
|
|||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
portaPauseFreq(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
psgMode(1),
|
||||
|
@ -125,8 +126,6 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
|
|||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
||||
int octave(int freq);
|
||||
int toFreq(int freq);
|
||||
double NOTE_OPNB(int ch, int note);
|
||||
double NOTE_ADPCMB(int note);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
|
|
@ -436,13 +436,13 @@ double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) {
|
|||
return NOTE_PERIODIC(note);
|
||||
}
|
||||
// FM
|
||||
return NOTE_FREQUENCY(note);
|
||||
return NOTE_FNUM_BLOCK(note,11);
|
||||
}
|
||||
|
||||
double DivPlatformYM2610B::NOTE_ADPCMB(int note) {
|
||||
if (chan[15].sample>=0 && chan[15].sample<parent->song.sampleLen) {
|
||||
double off=(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0;
|
||||
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false);
|
||||
double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0;
|
||||
return parent->calcBaseFreq((double)chipClock/144,off,note,false);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -538,15 +538,15 @@ void DivPlatformYM2610B::tick(bool sysTick) {
|
|||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
@ -704,7 +704,12 @@ void DivPlatformYM2610B::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[15].freqChanged) {
|
||||
chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4);
|
||||
if (chan[15].sample>=0 && chan[15].sample<parent->song.sampleLen) {
|
||||
double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0;
|
||||
chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4,chan[15].pitch2,(double)chipClock/144,off);
|
||||
} else {
|
||||
chan[15].freq=0;
|
||||
}
|
||||
immWrite(0x19,chan[15].freq&0xff);
|
||||
immWrite(0x1a,(chan[15].freq>>8)&0xff);
|
||||
chan[15].freqChanged=false;
|
||||
|
@ -720,11 +725,24 @@ void DivPlatformYM2610B::tick(bool sysTick) {
|
|||
for (int i=0; i<6; i++) {
|
||||
if (i==2 && extMode) continue;
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
|
||||
if (chan[i].freq>262143) chan[i].freq=262143;
|
||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
||||
immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8);
|
||||
immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff);
|
||||
if (parent->song.linearPitch==2) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
} else {
|
||||
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2);
|
||||
int block=(chan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
while (block<7) {
|
||||
fNum>>=1;
|
||||
block++;
|
||||
}
|
||||
if (fNum>2047) fNum=2047;
|
||||
}
|
||||
chan[i].freq=(block<<11)|fNum;
|
||||
}
|
||||
if (chan[i].freq>0x3fff) chan[i].freq=0x3fff;
|
||||
immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8);
|
||||
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
|
@ -734,47 +752,6 @@ void DivPlatformYM2610B::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
|
||||
int DivPlatformYM2610B::octave(int freq) {
|
||||
if (freq>=622.0f*128) {
|
||||
return 128;
|
||||
} else if (freq>=622.0f*64) {
|
||||
return 64;
|
||||
} else if (freq>=622.0f*32) {
|
||||
return 32;
|
||||
} else if (freq>=622.0f*16) {
|
||||
return 16;
|
||||
} else if (freq>=622.0f*8) {
|
||||
return 8;
|
||||
} else if (freq>=622.0f*4) {
|
||||
return 4;
|
||||
} else if (freq>=622.0f*2) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DivPlatformYM2610B::toFreq(int freq) {
|
||||
if (freq>=622.0f*128) {
|
||||
return 0x3800|((freq>>7)&0x7ff);
|
||||
} else if (freq>=622.0f*64) {
|
||||
return 0x3000|((freq>>6)&0x7ff);
|
||||
} else if (freq>=622.0f*32) {
|
||||
return 0x2800|((freq>>5)&0x7ff);
|
||||
} else if (freq>=622.0f*16) {
|
||||
return 0x2000|((freq>>4)&0x7ff);
|
||||
} else if (freq>=622.0f*8) {
|
||||
return 0x1800|((freq>>3)&0x7ff);
|
||||
} else if (freq>=622.0f*4) {
|
||||
return 0x1000|((freq>>2)&0x7ff);
|
||||
} else if (freq>=622.0f*2) {
|
||||
return 0x800|((freq>>1)&0x7ff);
|
||||
} else {
|
||||
return freq&0x7ff;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformYM2610B::dispatch(DivCommand c) {
|
||||
if (c.chan>5 && c.chan<9) {
|
||||
c.chan-=6;
|
||||
|
@ -906,7 +883,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
chan[c.chan].insChanged=false;
|
||||
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
chan[c.chan].portaPause=false;
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
|
@ -1003,7 +980,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
if (c.chan>5) { // PSG, ADPCM-B
|
||||
if (c.chan>5 || parent->song.linearPitch==2) { // PSG, ADPCM-B
|
||||
int destFreq=NOTE_OPNB(c.chan,c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
|
@ -1026,33 +1003,48 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
|
||||
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
|
||||
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||
int newFreq;
|
||||
bool return2=false;
|
||||
if (chan[c.chan].portaPause) {
|
||||
chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq;
|
||||
}
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
|
||||
newFreq=chan[c.chan].baseFreq+c.value;
|
||||
if (newFreq>=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
|
||||
newFreq=chan[c.chan].baseFreq-c.value;
|
||||
if (newFreq<=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
// check for octave boundary
|
||||
// what the heck!
|
||||
if (!chan[c.chan].portaPause) {
|
||||
if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) {
|
||||
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||
chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||
chan[c.chan].portaPause=true;
|
||||
break;
|
||||
}
|
||||
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||
chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||
chan[c.chan].portaPause=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
chan[c.chan].baseFreq=newFreq;
|
||||
chan[c.chan].portaPause=false;
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) return 2;
|
||||
chan[c.chan].baseFreq=newFreq;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
|
|
|
@ -35,7 +35,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
|
|||
struct Channel {
|
||||
DivInstrumentFM state;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
|
||||
|
@ -54,6 +54,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
|
|||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
portaPauseFreq(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
psgMode(1),
|
||||
|
@ -98,8 +99,6 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
|
|||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
||||
int octave(int freq);
|
||||
int toFreq(int freq);
|
||||
double NOTE_OPNB(int ch, int note);
|
||||
double NOTE_ADPCMB(int note);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
|
|
@ -120,31 +120,74 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
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;
|
||||
bool return2=false;
|
||||
if (opChan[ch].portaPause) {
|
||||
opChan[ch].baseFreq=opChan[ch].portaPauseFreq;
|
||||
}
|
||||
if (destFreq>opChan[ch].baseFreq) {
|
||||
newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq);
|
||||
newFreq=opChan[ch].baseFreq+c.value;
|
||||
if (newFreq>=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq);
|
||||
newFreq=opChan[ch].baseFreq-c.value;
|
||||
if (newFreq<=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
// what the heck!
|
||||
if (!opChan[ch].portaPause) {
|
||||
if (octave(opChan[ch].baseFreq)!=octave(newFreq)) {
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||
if (parent->song.fbPortaPause) {
|
||||
opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
} else {
|
||||
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
|
||||
}
|
||||
}
|
||||
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||
if (parent->song.fbPortaPause) {
|
||||
opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
} else {
|
||||
newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800);
|
||||
}
|
||||
}
|
||||
}
|
||||
opChan[ch].baseFreq=newFreq;
|
||||
opChan[ch].portaPause=false;
|
||||
opChan[ch].freqChanged=true;
|
||||
opChan[ch].baseFreq=newFreq;
|
||||
if (return2) return 2;
|
||||
break;
|
||||
}
|
||||
|
@ -364,14 +407,24 @@ void DivPlatformYM2610BExt::tick(bool sysTick) {
|
|||
unsigned char writeMask=2;
|
||||
if (extMode) for (int i=0; i<4; i++) {
|
||||
if (opChan[i].freqChanged) {
|
||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,octave(opChan[i].baseFreq),opChan[i].pitch2);
|
||||
if (opChan[i].freq>262143) opChan[i].freq=262143;
|
||||
int freqt=toFreq(opChan[i].freq);
|
||||
opChan[i].freqH=freqt>>8;
|
||||
opChan[i].freqL=freqt&0xff;
|
||||
immWrite(opChanOffsH[i],opChan[i].freqH);
|
||||
immWrite(opChanOffsL[i],opChan[i].freqL);
|
||||
opChan[i].freqChanged=false;
|
||||
if (parent->song.linearPitch==2) {
|
||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
} else {
|
||||
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
|
||||
int block=(opChan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
while (block<7) {
|
||||
fNum>>=1;
|
||||
block++;
|
||||
}
|
||||
if (fNum>2047) fNum=2047;
|
||||
}
|
||||
opChan[i].freq=(block<<11)|fNum;
|
||||
}
|
||||
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
|
||||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||
}
|
||||
writeMask|=opChan[i].active<<(4+i);
|
||||
if (opChan[i].keyOn) {
|
||||
|
|
|
@ -25,12 +25,12 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B {
|
|||
struct OpChannel {
|
||||
DivMacroInt std;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, ins;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
|
||||
int vol;
|
||||
unsigned char pan;
|
||||
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
|
||||
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
|
||||
};
|
||||
OpChannel opChan[4];
|
||||
bool isOpMuted[4];
|
||||
|
|
|
@ -63,7 +63,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
|
|||
opChan[ch].insChanged=false;
|
||||
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
opChan[ch].portaPause=false;
|
||||
opChan[ch].freqChanged=true;
|
||||
}
|
||||
|
@ -120,36 +120,79 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
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;
|
||||
bool return2=false;
|
||||
if (opChan[ch].portaPause) {
|
||||
opChan[ch].baseFreq=opChan[ch].portaPauseFreq;
|
||||
}
|
||||
if (destFreq>opChan[ch].baseFreq) {
|
||||
newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq);
|
||||
newFreq=opChan[ch].baseFreq+c.value;
|
||||
if (newFreq>=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq);
|
||||
newFreq=opChan[ch].baseFreq-c.value;
|
||||
if (newFreq<=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
// what the heck!
|
||||
if (!opChan[ch].portaPause) {
|
||||
if (octave(opChan[ch].baseFreq)!=octave(newFreq)) {
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||
if (parent->song.fbPortaPause) {
|
||||
opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
} else {
|
||||
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
|
||||
}
|
||||
}
|
||||
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||
if (parent->song.fbPortaPause) {
|
||||
opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
} else {
|
||||
newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800);
|
||||
}
|
||||
}
|
||||
}
|
||||
opChan[ch].baseFreq=newFreq;
|
||||
opChan[ch].portaPause=false;
|
||||
opChan[ch].freqChanged=true;
|
||||
opChan[ch].baseFreq=newFreq;
|
||||
if (return2) return 2;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO: {
|
||||
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
opChan[ch].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
|
@ -364,14 +407,24 @@ void DivPlatformYM2610Ext::tick(bool sysTick) {
|
|||
unsigned char writeMask=2;
|
||||
if (extMode) for (int i=0; i<4; i++) {
|
||||
if (opChan[i].freqChanged) {
|
||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,octave(opChan[i].baseFreq),opChan[i].pitch2);
|
||||
if (opChan[i].freq>262143) opChan[i].freq=262143;
|
||||
int freqt=toFreq(opChan[i].freq);
|
||||
opChan[i].freqH=freqt>>8;
|
||||
opChan[i].freqL=freqt&0xff;
|
||||
immWrite(opChanOffsH[i],opChan[i].freqH);
|
||||
immWrite(opChanOffsL[i],opChan[i].freqL);
|
||||
opChan[i].freqChanged=false;
|
||||
if (parent->song.linearPitch==2) {
|
||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
} else {
|
||||
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
|
||||
int block=(opChan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
while (block<7) {
|
||||
fNum>>=1;
|
||||
block++;
|
||||
}
|
||||
if (fNum>2047) fNum=2047;
|
||||
}
|
||||
opChan[i].freq=(block<<11)|fNum;
|
||||
}
|
||||
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
|
||||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||
}
|
||||
writeMask|=opChan[i].active<<(4+i);
|
||||
if (opChan[i].keyOn) {
|
||||
|
|
|
@ -25,12 +25,12 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 {
|
|||
struct OpChannel {
|
||||
DivMacroInt std;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, ins;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
|
||||
int vol;
|
||||
unsigned char pan;
|
||||
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
|
||||
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
|
||||
};
|
||||
OpChannel opChan[4];
|
||||
bool isOpMuted[4];
|
||||
|
|
|
@ -268,26 +268,53 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
int whatRow=afterDelay?chan[i].delayRow:curRow;
|
||||
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][whatOrder],false);
|
||||
// pre effects
|
||||
if (!afterDelay) for (int j=0; j<song.pat[i].effectCols; j++) {
|
||||
short effect=pat->data[whatRow][4+(j<<1)];
|
||||
short effectVal=pat->data[whatRow][5+(j<<1)];
|
||||
if (!afterDelay) {
|
||||
bool returnAfterPre=false;
|
||||
for (int j=0; j<song.pat[i].effectCols; j++) {
|
||||
short effect=pat->data[whatRow][4+(j<<1)];
|
||||
short effectVal=pat->data[whatRow][5+(j<<1)];
|
||||
|
||||
if (effectVal==-1) effectVal=0;
|
||||
if (effect==0xed && effectVal!=0) {
|
||||
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;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
chan[i].delayLocked=false;
|
||||
if (effectVal==-1) effectVal=0;
|
||||
|
||||
switch (effect) {
|
||||
case 0x09: // speed 1
|
||||
if (effectVal>0) speed1=effectVal;
|
||||
break;
|
||||
case 0x0f: // speed 2
|
||||
if (effectVal>0) speed2=effectVal;
|
||||
break;
|
||||
case 0x0b: // change order
|
||||
if (changeOrd==-1) {
|
||||
changeOrd=effectVal;
|
||||
changePos=0;
|
||||
}
|
||||
break;
|
||||
case 0x0d: // next order
|
||||
if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) {
|
||||
changeOrd=-2;
|
||||
changePos=effectVal;
|
||||
}
|
||||
break;
|
||||
case 0xed: // delay
|
||||
if (effectVal!=0) {
|
||||
if (effectVal<=nextSpeed) {
|
||||
chan[i].rowDelay=effectVal+1;
|
||||
chan[i].delayOrder=whatOrder;
|
||||
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;
|
||||
|
@ -386,24 +413,6 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
|
||||
// per-system effect
|
||||
if (!perSystemEffect(i,effect,effectVal)) switch (effect) {
|
||||
case 0x09: // speed 1
|
||||
if (effectVal>0) speed1=effectVal;
|
||||
break;
|
||||
case 0x0f: // speed 2
|
||||
if (effectVal>0) speed2=effectVal;
|
||||
break;
|
||||
case 0x0b: // change order
|
||||
if (changeOrd==-1) {
|
||||
changeOrd=effectVal;
|
||||
changePos=0;
|
||||
}
|
||||
break;
|
||||
case 0x0d: // next order
|
||||
if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) {
|
||||
changeOrd=-2;
|
||||
changePos=effectVal;
|
||||
}
|
||||
break;
|
||||
case 0x08: // panning (split 4-bit)
|
||||
chan[i].panL=(effectVal>>4)|(effectVal&0xf0);
|
||||
chan[i].panR=(effectVal&15)|((effectVal&15)<<4);
|
||||
|
@ -631,7 +640,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
chan[i].scheduledSlideReset=false;
|
||||
chan[i].inPorta=false;
|
||||
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0));
|
||||
dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote));
|
||||
dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote));
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
chan[i].inPorta=false;
|
||||
|
@ -954,7 +963,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
}
|
||||
if (!song.noSlidesOnFirstTick || !firstTick) {
|
||||
if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) {
|
||||
if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) {
|
||||
if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) {
|
||||
chan[i].portaSpeed=0;
|
||||
chan[i].oldNote=chan[i].note;
|
||||
chan[i].note=chan[i].portaNote;
|
||||
|
|
|
@ -301,7 +301,12 @@ struct DivSong {
|
|||
|
||||
// compatibility flags
|
||||
bool limitSlides;
|
||||
bool linearPitch;
|
||||
// linear pitch
|
||||
// 0: not linear
|
||||
// 1: only pitch changes (04xy/E5xx) linear
|
||||
// 2: full linear
|
||||
unsigned char linearPitch;
|
||||
unsigned char pitchSlideSpeed;
|
||||
// loop behavior
|
||||
// 0: reset on loop
|
||||
// 1: fake reset on loop
|
||||
|
@ -371,7 +376,6 @@ struct DivSong {
|
|||
*/
|
||||
void clearSamples();
|
||||
|
||||
|
||||
/**
|
||||
* unloads the song, freeing all memory associated with it.
|
||||
* use before destroying the object.
|
||||
|
@ -412,7 +416,8 @@ struct DivSong {
|
|||
masterVol(1.0f),
|
||||
tuning(440.0f),
|
||||
limitSlides(false),
|
||||
linearPitch(true),
|
||||
linearPitch(2),
|
||||
pitchSlideSpeed(4),
|
||||
loopModality(0),
|
||||
properNoiseLayout(false),
|
||||
waveDutyIsVol(false),
|
||||
|
|
|
@ -1259,11 +1259,14 @@ void DivEngine::registerSystems() {
|
|||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_OPN]=new DivSysDef(
|
||||
"Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, false, 0, false,
|
||||
"Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, false, 0x151, false,
|
||||
{"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"},
|
||||
{"F1", "F2", "F3", "S1", "S2", "S3"},
|
||||
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
|
||||
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}
|
||||
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY},
|
||||
{},
|
||||
fmHardResetEffectHandler,
|
||||
fmPostEffectHandler
|
||||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_PC98]=new DivSysDef(
|
||||
|
@ -1459,11 +1462,13 @@ void DivEngine::registerSystems() {
|
|||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_SCC]=new DivSysDef(
|
||||
"Konami SCC", NULL, 0xa1, 0, 5, false, true, 0, false,
|
||||
"Konami SCC", NULL, 0xa1, 0, 5, false, true, 0x161, false,
|
||||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"},
|
||||
{"CH1", "CH2", "CH3", "CH4", "CH5"},
|
||||
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
|
||||
{DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}
|
||||
{DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC},
|
||||
{},
|
||||
waveOnlyEffectHandler
|
||||
);
|
||||
|
||||
auto oplDrumsEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool {
|
||||
|
@ -1740,11 +1745,13 @@ void DivEngine::registerSystems() {
|
|||
);
|
||||
|
||||
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"},
|
||||
{"CH1", "CH2", "CH3", "CH4", "CH5"},
|
||||
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
|
||||
{DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}
|
||||
{DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC},
|
||||
{},
|
||||
waveOnlyEffectHandler
|
||||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_SOUND_UNIT]=new DivSysDef(
|
||||
|
|
|
@ -530,6 +530,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_OPN:
|
||||
w->writeC(5|baseAddr1);
|
||||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_OPLL:
|
||||
case DIV_SYSTEM_OPLL_DRUMS:
|
||||
case DIV_SYSTEM_VRC7:
|
||||
|
@ -598,6 +603,71 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_SCC:
|
||||
if (write.addr<0x80) {
|
||||
w->writeC(0xd2);
|
||||
w->writeC(baseAddr2|0);
|
||||
w->writeC(write.addr&0x7f);
|
||||
w->writeC(write.val&0xff);
|
||||
} else if (write.addr<0x8a) {
|
||||
w->writeC(0xd2);
|
||||
w->writeC(baseAddr2|1);
|
||||
w->writeC((write.addr-0x80)&0x7f);
|
||||
w->writeC(write.val&0xff);
|
||||
} else if (write.addr<0x8f) {
|
||||
w->writeC(0xd2);
|
||||
w->writeC(baseAddr2|2);
|
||||
w->writeC((write.addr-0x8a)&0x7f);
|
||||
w->writeC(write.val&0xff);
|
||||
} else if (write.addr<0x90) {
|
||||
w->writeC(0xd2);
|
||||
w->writeC(baseAddr2|3);
|
||||
w->writeC((write.addr-0x8f)&0x7f);
|
||||
w->writeC(write.val&0xff);
|
||||
} else if (write.addr>=0xe0) {
|
||||
w->writeC(0xd2);
|
||||
w->writeC(baseAddr2|5);
|
||||
w->writeC((write.addr-0xe0)&0x7f);
|
||||
w->writeC(write.val&0xff);
|
||||
} else {
|
||||
logW("SCC: writing to unmapped address %.2x!",write.addr);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_SCC_PLUS:
|
||||
if (write.addr<0x80) {
|
||||
w->writeC(0xd2);
|
||||
w->writeC(baseAddr2|0);
|
||||
w->writeC(write.addr&0x7f);
|
||||
w->writeC(write.val&0xff);
|
||||
} else if (write.addr<0xa0) {
|
||||
w->writeC(0xd2);
|
||||
w->writeC(baseAddr2|4);
|
||||
w->writeC(write.addr);
|
||||
w->writeC(write.val&0xff);
|
||||
} else if (write.addr<0xaa) {
|
||||
w->writeC(0xd2);
|
||||
w->writeC(baseAddr2|1);
|
||||
w->writeC((write.addr-0xa0)&0x7f);
|
||||
w->writeC(write.val&0xff);
|
||||
} else if (write.addr<0xaf) {
|
||||
w->writeC(0xd2);
|
||||
w->writeC(baseAddr2|2);
|
||||
w->writeC((write.addr-0xaa)&0x7f);
|
||||
w->writeC(write.val&0xff);
|
||||
} else if (write.addr<0xb0) {
|
||||
w->writeC(0xd2);
|
||||
w->writeC(baseAddr2|3);
|
||||
w->writeC((write.addr-0xaf)&0x7f);
|
||||
w->writeC(write.val&0xff);
|
||||
} else if (write.addr>=0xe0) {
|
||||
w->writeC(0xd2);
|
||||
w->writeC(baseAddr2|5);
|
||||
w->writeC((write.addr-0xe0)&0x7f);
|
||||
w->writeC(write.val&0xff);
|
||||
} else {
|
||||
logW("SCC+: writing to unmapped address %.2x!",write.addr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logW("write not handled!");
|
||||
break;
|
||||
|
@ -884,6 +954,18 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_OPN:
|
||||
if (!hasOPN) {
|
||||
hasOPN=disCont[i].dispatch->chipClock;
|
||||
willExport[i]=true;
|
||||
writeDACSamples=true;
|
||||
} else if (!(hasOPN&0x40000000)) {
|
||||
isSecond[i]=true;
|
||||
willExport[i]=true;
|
||||
hasOPN|=0x40000000;
|
||||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_OPLL:
|
||||
case DIV_SYSTEM_OPLL_DRUMS:
|
||||
case DIV_SYSTEM_VRC7:
|
||||
|
@ -988,6 +1070,24 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_SCC:
|
||||
case DIV_SYSTEM_SCC_PLUS:
|
||||
if (!hasK051649) {
|
||||
hasK051649=disCont[i].dispatch->chipClock;
|
||||
if (song.system[i]==DIV_SYSTEM_SCC_PLUS) {
|
||||
hasK051649|=0x80000000;
|
||||
}
|
||||
willExport[i]=true;
|
||||
} else if (!(hasK051649&0x40000000)) {
|
||||
isSecond[i]=true;
|
||||
willExport[i]=true;
|
||||
hasK051649|=0x40000000;
|
||||
if (song.system[i]==DIV_SYSTEM_SCC_PLUS) {
|
||||
hasK051649|=0x80000000;
|
||||
}
|
||||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
*/
|
||||
|
||||
#include "gui.h"
|
||||
#include "imgui.h"
|
||||
#include "intConst.h"
|
||||
|
||||
void FurnaceGUI::drawCompatFlags() {
|
||||
if (nextWindow==GUI_WINDOW_COMPAT_FLAGS) {
|
||||
|
@ -32,10 +34,6 @@ void FurnaceGUI::drawCompatFlags() {
|
|||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves.");
|
||||
}
|
||||
ImGui::Checkbox("Linear pitch control",&e->song.linearPitch);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work in tonality space\nnon-linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work on frequency/period space");
|
||||
}
|
||||
ImGui::Checkbox("Proper noise layout on NES and PC Engine",&e->song.properNoiseLayout);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("use a proper noise channel note mapping (0-15) instead of a rather unusual compatible one.\nunlocks all noise frequencies on PC Engine.");
|
||||
|
@ -126,6 +124,35 @@ void FurnaceGUI::drawCompatFlags() {
|
|||
ImGui::SetTooltip("when enabled, the pitch macro of an instrument is in linear space.");
|
||||
}
|
||||
|
||||
ImGui::Text("Pitch linearity:");
|
||||
if (ImGui::RadioButton("None",e->song.linearPitch==0)) {
|
||||
e->song.linearPitch=0;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("like ProTracker/FamiTracker");
|
||||
}
|
||||
if (ImGui::RadioButton("Partial (only 04xy/E5xx)",e->song.linearPitch==1)) {
|
||||
e->song.linearPitch=1;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("like DefleMask");
|
||||
}
|
||||
if (ImGui::RadioButton("Full",e->song.linearPitch==2)) {
|
||||
e->song.linearPitch=2;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("like Impulse Tracker");
|
||||
}
|
||||
|
||||
if (e->song.linearPitch==2) {
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(120.0f*dpiScale);
|
||||
if (ImGui::InputScalar("Pitch slide speed multiplier",ImGuiDataType_U8,&e->song.pitchSlideSpeed,&_ONE,&_ONE)) {
|
||||
if (e->song.pitchSlideSpeed<1) e->song.pitchSlideSpeed=1;
|
||||
if (e->song.pitchSlideSpeed>64) e->song.pitchSlideSpeed=64;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Text("Loop modality:");
|
||||
if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) {
|
||||
e->song.loopModality=0;
|
||||
|
|
|
@ -47,7 +47,20 @@ void FurnaceGUI::drawInsList() {
|
|||
}
|
||||
ImGui::SameLine();
|
||||
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();
|
||||
if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) {
|
||||
|
|
|
@ -25,6 +25,11 @@
|
|||
#include "actionUtil.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) {
|
||||
switch (what) {
|
||||
case GUI_ACTION_OPEN:
|
||||
|
@ -141,6 +146,17 @@ void FurnaceGUI::doAction(int what) {
|
|||
fullScreen=!fullScreen;
|
||||
SDL_SetWindowFullscreen(sdlWin,fullScreen?(SDL_WINDOW_FULLSCREEN|SDL_WINDOW_FULLSCREEN_DESKTOP):0);
|
||||
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:
|
||||
e->syncReset();
|
||||
break;
|
||||
|
|
|
@ -59,7 +59,7 @@ extern "C" {
|
|||
#define BACKUP_FUR "/backup.fur"
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef IS_MOBILE
|
||||
#define MOBILE_UI_DEFAULT true
|
||||
#else
|
||||
#define MOBILE_UI_DEFAULT false
|
||||
|
@ -2469,6 +2469,15 @@ bool FurnaceGUI::loop() {
|
|||
|
||||
wantCaptureKeyboard=ImGui::GetIO().WantTextInput;
|
||||
|
||||
if (wantCaptureKeyboard!=oldWantCaptureKeyboard) {
|
||||
oldWantCaptureKeyboard=wantCaptureKeyboard;
|
||||
if (wantCaptureKeyboard) {
|
||||
SDL_StartTextInput();
|
||||
} else {
|
||||
SDL_StopTextInput();
|
||||
}
|
||||
}
|
||||
|
||||
if (wantCaptureKeyboard) {
|
||||
WAKE_UP;
|
||||
}
|
||||
|
@ -2791,9 +2800,11 @@ bool FurnaceGUI::loop() {
|
|||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("settings")) {
|
||||
#ifndef IS_MOBILE
|
||||
if (ImGui::MenuItem("full screen",BIND_FOR(GUI_ACTION_FULLSCREEN),fullScreen)) {
|
||||
doAction(GUI_ACTION_FULLSCREEN);
|
||||
}
|
||||
#endif
|
||||
if (ImGui::MenuItem("lock layout",NULL,lockLayout)) {
|
||||
lockLayout=!lockLayout;
|
||||
}
|
||||
|
@ -3673,7 +3684,11 @@ bool FurnaceGUI::init() {
|
|||
tempoView=e->getConfBool("tempoView",true);
|
||||
waveHex=e->getConfBool("waveHex",false);
|
||||
lockLayout=e->getConfBool("lockLayout",false);
|
||||
#ifdef IS_MOBILE
|
||||
fullScreen=true;
|
||||
#else
|
||||
fullScreen=e->getConfBool("fullScreen",false);
|
||||
#endif
|
||||
mobileUI=e->getConfBool("mobileUI",MOBILE_UI_DEFAULT);
|
||||
|
||||
syncSettings();
|
||||
|
@ -3695,6 +3710,7 @@ bool FurnaceGUI::init() {
|
|||
#endif
|
||||
|
||||
SDL_SetHint("SDL_HINT_VIDEO_ALLOW_SCREENSAVER","1");
|
||||
SDL_SetHint("SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH","1");
|
||||
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
|
||||
|
@ -3886,6 +3902,7 @@ FurnaceGUI::FurnaceGUI():
|
|||
displayExporting(false),
|
||||
vgmExportLoop(true),
|
||||
wantCaptureKeyboard(false),
|
||||
oldWantCaptureKeyboard(false),
|
||||
displayMacroMenu(false),
|
||||
displayNew(false),
|
||||
fullScreen(false),
|
||||
|
|
|
@ -306,6 +306,7 @@ enum FurnaceGUIActions {
|
|||
GUI_ACTION_FOLLOW_ORDERS,
|
||||
GUI_ACTION_FOLLOW_PATTERN,
|
||||
GUI_ACTION_FULLSCREEN,
|
||||
GUI_ACTION_TX81Z_REQUEST,
|
||||
GUI_ACTION_PANIC,
|
||||
|
||||
GUI_ACTION_WINDOW_EDIT_CONTROLS,
|
||||
|
@ -768,7 +769,7 @@ class FurnaceGUI {
|
|||
String mmlString[17];
|
||||
String mmlStringW;
|
||||
|
||||
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, displayMacroMenu;
|
||||
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
|
||||
bool displayNew, fullScreen, preserveChanPos;
|
||||
bool willExport[32];
|
||||
int vgmExportVersion;
|
||||
|
@ -887,7 +888,6 @@ class FurnaceGUI {
|
|||
int eventDelay;
|
||||
int moveWindowTitle;
|
||||
int hiddenSystems;
|
||||
int insLoadAlwaysReplace;
|
||||
int horizontalDataView;
|
||||
int noMultiSystem;
|
||||
unsigned int maxUndoSteps;
|
||||
|
@ -970,9 +970,8 @@ class FurnaceGUI {
|
|||
powerSave(1),
|
||||
absorbInsInput(0),
|
||||
eventDelay(0),
|
||||
moveWindowTitle(0),
|
||||
moveWindowTitle(1),
|
||||
hiddenSystems(0),
|
||||
insLoadAlwaysReplace(1),
|
||||
horizontalDataView(0),
|
||||
noMultiSystem(0),
|
||||
maxUndoSteps(100),
|
||||
|
|
|
@ -459,6 +459,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
|
|||
D("FOLLOW_ORDERS", "Follow orders", 0),
|
||||
D("FOLLOW_PATTERN", "Follow pattern", 0),
|
||||
D("FULLSCREEN", "Toggle full-screen", SDLK_F11),
|
||||
D("TX81Z_REQUEST", "Request voice from TX81Z", 0),
|
||||
D("PANIC", "Panic", SDLK_F12),
|
||||
|
||||
D("WINDOW_EDIT_CONTROLS", "Edit Controls", 0),
|
||||
|
@ -833,6 +834,8 @@ const int availableSystems[]={
|
|||
DIV_SYSTEM_YMU759,
|
||||
DIV_SYSTEM_DUMMY,
|
||||
DIV_SYSTEM_SOUND_UNIT,
|
||||
DIV_SYSTEM_OPN,
|
||||
DIV_SYSTEM_PC98,
|
||||
DIV_SYSTEM_OPLL,
|
||||
DIV_SYSTEM_OPLL_DRUMS,
|
||||
DIV_SYSTEM_VRC7,
|
||||
|
@ -858,6 +861,8 @@ const int availableSystems[]={
|
|||
DIV_SYSTEM_VRC6,
|
||||
DIV_SYSTEM_FDS,
|
||||
DIV_SYSTEM_MMC5,
|
||||
DIV_SYSTEM_SCC,
|
||||
DIV_SYSTEM_SCC_PLUS,
|
||||
0 // don't remove this last one!
|
||||
};
|
||||
|
||||
|
|
|
@ -27,10 +27,6 @@
|
|||
#include <imgui.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]={
|
||||
"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::TableNextColumn();
|
||||
// TODO: load replace
|
||||
if (ImGui::Button(ICON_FA_FOLDER_OPEN "##IELoad")) {
|
||||
doAction(GUI_ACTION_INS_LIST_OPEN_REPLACE);
|
||||
}
|
||||
|
@ -1477,14 +1472,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::TableNextColumn();
|
||||
drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
|
||||
if (ImGui::Button("Request from TX81Z")) {
|
||||
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?)");
|
||||
}
|
||||
doAction(GUI_ACTION_TX81Z_REQUEST);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Send to TX81Z")) {
|
||||
|
|
|
@ -425,6 +425,26 @@ void FurnaceGUI::initSystemPresets() {
|
|||
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(
|
||||
"ZX Spectrum (48K)", {
|
||||
DIV_SYSTEM_AY8910, 64, 0, 2,
|
||||
|
@ -585,7 +605,7 @@ void FurnaceGUI::initSystemPresets() {
|
|||
cat.systems.push_back(FurnaceGUISysDef(
|
||||
"Commander X16", {
|
||||
DIV_SYSTEM_VERA, 64, 0, 0,
|
||||
DIV_SYSTEM_YM2151, 64, 0, 0,
|
||||
DIV_SYSTEM_YM2151, 32, 0, 0,
|
||||
0
|
||||
}
|
||||
));
|
||||
|
|
|
@ -419,11 +419,6 @@ void FurnaceGUI::drawSettings() {
|
|||
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;
|
||||
if (ImGui::Checkbox("Use system file picker",&sysFileDialogB)) {
|
||||
settings.sysFileDialog=sysFileDialogB;
|
||||
|
@ -1850,9 +1845,8 @@ void FurnaceGUI::syncSettings() {
|
|||
settings.powerSave=e->getConfInt("powerSave",POWER_SAVE_DEFAULT);
|
||||
settings.absorbInsInput=e->getConfInt("absorbInsInput",0);
|
||||
settings.eventDelay=e->getConfInt("eventDelay",0);
|
||||
settings.moveWindowTitle=e->getConfInt("moveWindowTitle",0);
|
||||
settings.moveWindowTitle=e->getConfInt("moveWindowTitle",1);
|
||||
settings.hiddenSystems=e->getConfInt("hiddenSystems",0);
|
||||
settings.insLoadAlwaysReplace=e->getConfInt("insLoadAlwaysReplace",1);
|
||||
settings.horizontalDataView=e->getConfInt("horizontalDataView",0);
|
||||
settings.noMultiSystem=e->getConfInt("noMultiSystem",0);
|
||||
|
||||
|
@ -1925,7 +1919,6 @@ void FurnaceGUI::syncSettings() {
|
|||
clampSetting(settings.eventDelay,0,1);
|
||||
clampSetting(settings.moveWindowTitle,0,1);
|
||||
clampSetting(settings.hiddenSystems,0,1);
|
||||
clampSetting(settings.insLoadAlwaysReplace,0,1);
|
||||
clampSetting(settings.horizontalDataView,0,1);
|
||||
clampSetting(settings.noMultiSystem,0,1)
|
||||
|
||||
|
@ -2039,7 +2032,6 @@ void FurnaceGUI::commitSettings() {
|
|||
e->setConf("moveWindowTitle",settings.moveWindowTitle);
|
||||
e->setConf("hiddenSystems",settings.hiddenSystems);
|
||||
e->setConf("initialSys",e->encodeSysDesc(settings.initialSys));
|
||||
e->setConf("insLoadAlwaysReplace",settings.insLoadAlwaysReplace);
|
||||
e->setConf("horizontalDataView",settings.horizontalDataView);
|
||||
e->setConf("noMultiSystem",settings.noMultiSystem);
|
||||
|
||||
|
|
|
@ -355,6 +355,21 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_OPN: {
|
||||
if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) {
|
||||
copyOfFlags=(flags&0x80000000)|0;
|
||||
}
|
||||
if (ImGui::RadioButton("PAL (3.54MHz)",(flags&3)==1)) {
|
||||
copyOfFlags=(flags&0x80000000)|1;
|
||||
}
|
||||
if (ImGui::RadioButton("Arcade (4MHz)",(flags&3)==2)) {
|
||||
copyOfFlags=(flags&0x80000000)|2;
|
||||
}
|
||||
if (ImGui::RadioButton("PC-9801-26K? TODO: CONFIRM (3MHz)",(flags&3)==3)) {
|
||||
copyOfFlags=(flags&0x80000000)|3;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_GB:
|
||||
case DIV_SYSTEM_SWAN:
|
||||
case DIV_SYSTEM_VERA:
|
||||
|
@ -367,6 +382,8 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
|
|||
case DIV_SYSTEM_YM2610B_EXT:
|
||||
case DIV_SYSTEM_YMU759:
|
||||
case DIV_SYSTEM_PET:
|
||||
case DIV_SYSTEM_SCC:
|
||||
case DIV_SYSTEM_SCC_PLUS:
|
||||
ImGui::Text("nothing to configure");
|
||||
break;
|
||||
default:
|
||||
|
|
Loading…
Reference in a new issue