Merge branch 'master' of https://github.com/tildearrow/furnace into x16
This commit is contained in:
commit
43cfb09acf
|
@ -265,6 +265,8 @@ src/engine/platform/sound/lynx/Mikey.cpp
|
|||
|
||||
src/engine/platform/sound/qsound.c
|
||||
|
||||
src/engine/platform/sound/swan.cpp
|
||||
|
||||
src/engine/platform/ym2610Interface.cpp
|
||||
|
||||
src/engine/blip_buf.c
|
||||
|
@ -306,9 +308,10 @@ src/engine/platform/amiga.cpp
|
|||
src/engine/platform/pcspkr.cpp
|
||||
src/engine/platform/segapcm.cpp
|
||||
src/engine/platform/qsound.cpp
|
||||
src/engine/platform/lynx.cpp
|
||||
src/engine/platform/swan.cpp
|
||||
src/engine/platform/vera.cpp
|
||||
src/engine/platform/dummy.cpp
|
||||
src/engine/platform/lynx.cpp
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
|
|
@ -10,12 +10,13 @@ double-click to open the instrument editor.
|
|||
|
||||
every instrument can be renamed and have its type changed.
|
||||
|
||||
depending on the instrument type, there are currently 10 different types of an instrument editor:
|
||||
depending on the instrument type, there are currently 12 different types of an instrument editor:
|
||||
|
||||
- [FM synthesis](fm.md) - for use with YM2612, YM2151 and FM block portion of YM2610.
|
||||
- [Standard](standard.md) - for use with NES and Sega Master System's PSG sound source and its derivatives.
|
||||
- [Game Boy](game-boy.md) - for use with Game Boy APU.
|
||||
- [PC Engine/TurboGrafx-16](pce.md) - for use with PC Engine's wavetable synthesizer.
|
||||
- [WonderSwan](wonderswan.md) - for use with WonderSwan's wavetable synthesizer.
|
||||
- [AY8930](8930.md) - for use with Microchip AY8930 E-PSG sound source.
|
||||
- [Commodore 64](c64.md) - for use with Commodore 64 SID.
|
||||
- [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source.
|
||||
|
|
8
papers/doc/4-instrument/wonderswan.md
Normal file
8
papers/doc/4-instrument/wonderswan.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# WonderSwan instrument editor
|
||||
|
||||
WS instrument editor consists of only four macros, similar to PCE but with different volume and noise range:
|
||||
|
||||
- [Volume] - volume sequence
|
||||
- [Arpeggio] - pitch sequencr
|
||||
- [Noise] - noise LFSR tap sequence
|
||||
- [Waveform] - spicifies wavetables sequence
|
|
@ -1,5 +1,5 @@
|
|||
# wavetable editor
|
||||
|
||||
Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.5, wavetable editor affects PC Engine and channel 3 of Game Boy.
|
||||
Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy.
|
||||
|
||||
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: both Game Boy and PCE can handle max 32 byte waveforms as of now, width 16-level height for GB and 32-level height for PCE. If larger wave will be defined for these two systems, it will be squashed to fit in the constrains of the system.
|
||||
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms as of now, with 16-level height for GB and WS, and 32-level height for PCE. If larger wave will be defined for these two systems, it will be squashed to fit within the constraints of the system.
|
||||
|
|
|
@ -18,5 +18,6 @@ this is a list of systems that Furnace supports, including each system's effects
|
|||
- [Atari 2600](tia.md)
|
||||
- [Philips SAA1099](saa1099.md)
|
||||
- [Microchip AY8930](ay8930.md)
|
||||
- [WonderSwan](wonderswan.md)
|
||||
|
||||
Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all.
|
||||
|
|
20
papers/doc/7-systems/wonderswan.md
Normal file
20
papers/doc/7-systems/wonderswan.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
# WonderSwan
|
||||
|
||||
A handheld console released only in Japan by Bandai. Designed by the same
|
||||
people behind Game Boy and Virtual Boy, it has lots of similar elements from
|
||||
those two systems in the sound department.
|
||||
|
||||
It has 4 wavetable channels, one channel could play PCM, the other has hardware
|
||||
sweep and the other could play noise.
|
||||
|
||||
# effects
|
||||
|
||||
- `10xx`: change wave.
|
||||
- `11xx`: setup noise mode (channel 4 only).
|
||||
- 0: disable.
|
||||
- 1-8: enable and set tap preset.
|
||||
- `12xx`: setup sweep period (channel 3 only).
|
||||
- 0: disable.
|
||||
- 1-32: enable and set period.
|
||||
- `13xx`: setup sweep amount (channel 3 only).
|
||||
- `17xx`: toggle PCM mode (channel 2 only).
|
|
@ -248,7 +248,9 @@ size | description
|
|||
1 | feedback
|
||||
1 | fms
|
||||
1 | ams
|
||||
1 | operator count (always 4)
|
||||
1 | operator count
|
||||
| - this is either 2 or 4, and is ignored on non-OPL systems.
|
||||
| - always read 4 ops regardless of this value.
|
||||
1 | OPLL preset (>=60) or reserved
|
||||
| - 0: custom
|
||||
| - 1-15: pre-defined patches
|
||||
|
|
|
@ -109,6 +109,9 @@ enum DivDispatchCmds {
|
|||
DIV_CMD_QSOUND_ECHO_DELAY,
|
||||
DIV_CMD_QSOUND_ECHO_LEVEL,
|
||||
|
||||
DIV_CMD_WS_SWEEP_TIME,
|
||||
DIV_CMD_WS_SWEEP_AMOUNT,
|
||||
|
||||
DIV_ALWAYS_SET_VOLUME,
|
||||
|
||||
DIV_CMD_MAX
|
||||
|
|
|
@ -41,9 +41,10 @@
|
|||
#include "platform/pcspkr.h"
|
||||
#include "platform/segapcm.h"
|
||||
#include "platform/qsound.h"
|
||||
#include "platform/lynx.h"
|
||||
#include "platform/swan.h"
|
||||
#include "platform/vera.h"
|
||||
#include "platform/dummy.h"
|
||||
#include "platform/lynx.h"
|
||||
#include "../ta-log.h"
|
||||
#include "song.h"
|
||||
|
||||
|
@ -212,8 +213,29 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
((DivPlatformOPLL*)dispatch)->setVRC7(sys==DIV_SYSTEM_VRC7);
|
||||
((DivPlatformOPLL*)dispatch)->setProperDrums(sys==DIV_SYSTEM_OPLL_DRUMS);
|
||||
break;
|
||||
case DIV_SYSTEM_OPL:
|
||||
dispatch=new DivPlatformOPL;
|
||||
((DivPlatformOPL*)dispatch)->setOPLType(1,false);
|
||||
break;
|
||||
case DIV_SYSTEM_OPL_DRUMS:
|
||||
dispatch=new DivPlatformOPL;
|
||||
((DivPlatformOPL*)dispatch)->setOPLType(1,true);
|
||||
break;
|
||||
case DIV_SYSTEM_OPL2:
|
||||
dispatch=new DivPlatformOPL;
|
||||
((DivPlatformOPL*)dispatch)->setOPLType(2,false);
|
||||
break;
|
||||
case DIV_SYSTEM_OPL2_DRUMS:
|
||||
dispatch=new DivPlatformOPL;
|
||||
((DivPlatformOPL*)dispatch)->setOPLType(2,true);
|
||||
break;
|
||||
case DIV_SYSTEM_OPL3:
|
||||
dispatch=new DivPlatformOPL;
|
||||
((DivPlatformOPL*)dispatch)->setOPLType(3,false);
|
||||
break;
|
||||
case DIV_SYSTEM_OPL3_DRUMS:
|
||||
dispatch=new DivPlatformOPL;
|
||||
((DivPlatformOPL*)dispatch)->setOPLType(3,true);
|
||||
break;
|
||||
case DIV_SYSTEM_SAA1099: {
|
||||
int saaCore=eng->getConfInt("saaCore",0);
|
||||
|
@ -235,8 +257,11 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_SEGAPCM_COMPAT:
|
||||
dispatch=new DivPlatformSegaPCM;
|
||||
break;
|
||||
case DIV_SYSTEM_SWAN:
|
||||
dispatch=new DivPlatformSwan;
|
||||
break;
|
||||
case DIV_SYSTEM_VERA:
|
||||
dispatch = new DivPlatformVERA;
|
||||
dispatch=new DivPlatformVERA;
|
||||
break;
|
||||
default:
|
||||
logW("this system is not supported yet! using dummy platform.\n");
|
||||
|
|
|
@ -39,7 +39,7 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
|||
w->writeC(fm.fb);
|
||||
w->writeC(fm.fms);
|
||||
w->writeC(fm.ams);
|
||||
w->writeC(4); // operator count; always 4
|
||||
w->writeC(fm.ops);
|
||||
w->writeC(fm.opllPreset);
|
||||
w->writeC(0); // reserved
|
||||
w->writeC(0);
|
||||
|
|
|
@ -42,7 +42,7 @@ static bool isOutput[8][4]={
|
|||
{true ,true ,true ,true},
|
||||
};
|
||||
static unsigned char dtTable[8]={
|
||||
7,6,5,0,1,2,3,0
|
||||
7,6,5,0,1,2,3,4
|
||||
};
|
||||
|
||||
static int orderedOps[4]={
|
||||
|
|
|
@ -35,7 +35,7 @@ static bool isOutput[8][4]={
|
|||
{true ,true ,true ,true},
|
||||
};
|
||||
static unsigned char dtTable[8]={
|
||||
7,6,5,0,1,2,3,0
|
||||
7,6,5,0,1,2,3,4
|
||||
};
|
||||
|
||||
static int orderedOps[4]={
|
||||
|
@ -45,4 +45,4 @@ static int orderedOps[4]={
|
|||
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
||||
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
#include "fmshared_OPN.h"
|
||||
#include "fmshared_OPN.h"
|
||||
|
|
|
@ -25,49 +25,116 @@
|
|||
#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 4720272
|
||||
#define CHIP_FREQBASE chipFreqBase
|
||||
|
||||
// N = invalid
|
||||
#define N 255
|
||||
|
||||
const unsigned char slotsOPL2[4][20]={
|
||||
const unsigned char slotsOPL2i[4][20]={
|
||||
{0, 1, 2, 6, 7, 8, 12, 13, 14}, // OP1
|
||||
{3, 4, 5, 9, 10, 11, 15, 16, 17}, // OP2
|
||||
{N, N, N, N, N, N, N, N, N},
|
||||
{N, N, N, N, N, N, N, N, N}
|
||||
};
|
||||
|
||||
const unsigned char slotsOPL2Drums[4][20]={
|
||||
const unsigned char slotsOPL2Drumsi[4][20]={
|
||||
{0, 1, 2, 6, 7, 8, 12, 16, 14, 17, 13}, // OP1
|
||||
{3, 4, 5, 9, 10, 11, 15, N, N, N, N}, // OP2
|
||||
{N, N, N, N, N, N, N, N, N, N, N},
|
||||
{N, N, N, N, N, N, N, N, N, N, N}
|
||||
};
|
||||
|
||||
const unsigned char chanMapOPL2[20]={
|
||||
const unsigned short chanMapOPL2[20]={
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, N, N, N, N, N, N, N, N, N, N, N
|
||||
};
|
||||
|
||||
const unsigned char slotsOPL3[4][20]={
|
||||
const unsigned char* slotsOPL2[4]={
|
||||
slotsOPL2i[0],
|
||||
slotsOPL2i[1],
|
||||
slotsOPL2i[2],
|
||||
slotsOPL2i[3]
|
||||
};
|
||||
|
||||
const unsigned char* slotsOPL2Drums[4]={
|
||||
slotsOPL2Drumsi[0],
|
||||
slotsOPL2Drumsi[1],
|
||||
slotsOPL2Drumsi[2],
|
||||
slotsOPL2Drumsi[3]
|
||||
};
|
||||
|
||||
const unsigned char slotsOPL3i[4][20]={
|
||||
{0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 13, 14}, // OP1
|
||||
{3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, 15, 16, 17}, // OP2
|
||||
{6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N}, // OP3
|
||||
{9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N} // OP4
|
||||
};
|
||||
|
||||
const unsigned char slotsOPL3Drums[4][20]={
|
||||
const unsigned char slotsOPL3Drumsi[4][20]={
|
||||
{0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 16, 14, 17, 13}, // OP1
|
||||
{3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, N, N, N, N, N}, // OP2
|
||||
{6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N, N, N}, // OP3
|
||||
{9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N, N, N} // OP4
|
||||
};
|
||||
|
||||
const unsigned char chanMapOPL3[20]={
|
||||
0, 3, 1, 4, 2, 5, 9, 12, 10, 13, 11, 14, 15, 16, 17, 6, 7, 8, N, N
|
||||
const unsigned short chanMapOPL3[20]={
|
||||
0, 3, 1, 4, 2, 5, 0x100, 0x103, 0x101, 0x104, 0x102, 0x105, 0x106, 0x107, 0x108, 6, 7, 8, N, N
|
||||
};
|
||||
|
||||
const unsigned char* slotsOPL3[4]={
|
||||
slotsOPL3i[0],
|
||||
slotsOPL3i[1],
|
||||
slotsOPL3i[2],
|
||||
slotsOPL3i[3]
|
||||
};
|
||||
|
||||
const unsigned char* slotsOPL3Drums[4]={
|
||||
slotsOPL3Drumsi[0],
|
||||
slotsOPL3Drumsi[1],
|
||||
slotsOPL3Drumsi[2],
|
||||
slotsOPL3Drumsi[3]
|
||||
};
|
||||
|
||||
const unsigned int slotMap[36]={
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
|
||||
|
||||
0x100, 0x101, 0x102, 0x103, 0x104, 0x105,
|
||||
0x108, 0x109, 0x10a, 0x10b, 0x10c, 0x10d,
|
||||
0x110, 0x111, 0x112, 0x113, 0x114, 0x115,
|
||||
};
|
||||
|
||||
const bool isOutputL[2][4][4]={
|
||||
{ // 2-op
|
||||
{false, true, false, false}, // 1 > 2
|
||||
{ true, true, false, false}, // 1 + 2
|
||||
{false, true, false, false}, // ditto, 0
|
||||
{ true, true, false, false}, // ditto, 1
|
||||
},
|
||||
{ // 4-op
|
||||
{false, false, false, true}, // 1 > 2 > 3 > 4
|
||||
{ true, false, false, true}, // 1 + (2 > 3 > 4)
|
||||
{false, true, false, true}, // (1 > 2) + (3 > 4)
|
||||
{ true, false, true, true} // 1 + (2 > 3) + 4
|
||||
}
|
||||
};
|
||||
|
||||
#undef N
|
||||
|
||||
const int orderedOpsL[4]={
|
||||
0,2,1,3
|
||||
};
|
||||
|
||||
#define ADDR_AM_VIB_SUS_KSR_MULT 0x20
|
||||
#define ADDR_KSL_TL 0x40
|
||||
#define ADDR_AR_DR 0x60
|
||||
#define ADDR_SL_RR 0x80
|
||||
#define ADDR_WS 0xe0
|
||||
|
||||
#define ADDR_FREQ 0xa0
|
||||
#define ADDR_FREQH 0xb0
|
||||
#define ADDR_LR_FB_ALG 0xc0
|
||||
|
||||
const char* DivPlatformOPL::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
|
@ -126,20 +193,11 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
for (size_t h=start; h<start+len; h++) {
|
||||
os[0]=0; os[1]=0;
|
||||
if (!writes.empty() && --delay<0) {
|
||||
delay=12;
|
||||
delay=1;
|
||||
QueuedWrite& w=writes.front();
|
||||
if (w.addrOrVal) {
|
||||
OPL3_WriteReg(&fm,0x1+((w.addr>>8)<<1),w.val);
|
||||
//printf("write: %x = %.2x\n",w.addr,w.val);
|
||||
lastBusy=0;
|
||||
regPool[w.addr&0x1ff]=w.val;
|
||||
writes.pop();
|
||||
} else {
|
||||
lastBusy++;
|
||||
//printf("busycounter: %d\n",lastBusy);
|
||||
OPL3_WriteReg(&fm,0x0+((w.addr>>8)<<1),w.addr);
|
||||
w.addrOrVal=true;
|
||||
}
|
||||
OPL3_WriteReg(&fm,w.addr,w.val);
|
||||
regPool[w.addr&511]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1];
|
||||
|
@ -164,11 +222,10 @@ void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
}
|
||||
|
||||
void DivPlatformOPL::tick() {
|
||||
/*
|
||||
for (int i=0; i<20; i++) {
|
||||
if (i==2 && extMode) continue;
|
||||
chan[i].std.next();
|
||||
|
||||
/*
|
||||
if (chan[i].std.hadVol) {
|
||||
chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127;
|
||||
for (int j=0; j<4; j++) {
|
||||
|
@ -288,13 +345,13 @@ void DivPlatformOPL::tick() {
|
|||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (chan[i].keyOn || chan[i].keyOff) {
|
||||
immWrite(0x28,0x00|konOffs[i]);
|
||||
immWrite(chanMap[i]+ADDR_FREQH,0x00|(chan[i].freqH&31));
|
||||
chan[i].keyOff=false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
for (int i=0; i<512; i++) {
|
||||
if (pendingWrites[i]!=oldWrites[i]) {
|
||||
|
@ -303,52 +360,41 @@ void DivPlatformOPL::tick() {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
for (int i=0; i<20; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq));
|
||||
if (chan[i].freq>262143) chan[i].freq=262143;
|
||||
if (chan[i].freq>131071) chan[i].freq=131071;
|
||||
int freqt=toFreq(chan[i].freq);
|
||||
immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8);
|
||||
immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff);
|
||||
if (chan[i].furnaceDac && dacMode) {
|
||||
double off=1.0;
|
||||
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(dacSample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=8363.0/(double)s->centerRate;
|
||||
}
|
||||
}
|
||||
dacRate=(1280000*1.25*off)/MAX(1,chan[i].baseFreq);
|
||||
if (dacRate<1) dacRate=1;
|
||||
if (dumpWrites) addWrite(0xffff0001,1280000/dacRate);
|
||||
}
|
||||
chan[i].freqChanged=false;
|
||||
chan[i].freqH=freqt>>8;
|
||||
chan[i].freqL=freqt&0xff;
|
||||
immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL);
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
immWrite(0x28,0xf0|konOffs[i]);
|
||||
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(0x20));
|
||||
chan[i].keyOn=false;
|
||||
} else if (chan[i].freqChanged) {
|
||||
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5));
|
||||
}
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
#define OPLL_C_NUM 686
|
||||
|
||||
int DivPlatformOPL::octave(int freq) {
|
||||
if (freq>=82432) {
|
||||
if (freq>=OPLL_C_NUM*64) {
|
||||
return 128;
|
||||
} else if (freq>=41216) {
|
||||
} else if (freq>=OPLL_C_NUM*32) {
|
||||
return 64;
|
||||
} else if (freq>=20608) {
|
||||
} else if (freq>=OPLL_C_NUM*16) {
|
||||
return 32;
|
||||
} else if (freq>=10304) {
|
||||
} else if (freq>=OPLL_C_NUM*8) {
|
||||
return 16;
|
||||
} else if (freq>=5152) {
|
||||
} else if (freq>=OPLL_C_NUM*4) {
|
||||
return 8;
|
||||
} else if (freq>=2576) {
|
||||
} else if (freq>=OPLL_C_NUM*2) {
|
||||
return 4;
|
||||
} else if (freq>=1288) {
|
||||
} else if (freq>=OPLL_C_NUM) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
|
@ -357,22 +403,22 @@ int DivPlatformOPL::octave(int freq) {
|
|||
}
|
||||
|
||||
int DivPlatformOPL::toFreq(int freq) {
|
||||
if (freq>=82432) {
|
||||
return 0x3800|((freq>>7)&0x7ff);
|
||||
} else if (freq>=41216) {
|
||||
return 0x3000|((freq>>6)&0x7ff);
|
||||
} else if (freq>=20608) {
|
||||
return 0x2800|((freq>>5)&0x7ff);
|
||||
} else if (freq>=10304) {
|
||||
return 0x2000|((freq>>4)&0x7ff);
|
||||
} else if (freq>=5152) {
|
||||
return 0x1800|((freq>>3)&0x7ff);
|
||||
} else if (freq>=2576) {
|
||||
return 0x1000|((freq>>2)&0x7ff);
|
||||
} else if (freq>=1288) {
|
||||
return 0x800|((freq>>1)&0x7ff);
|
||||
if (freq>=OPLL_C_NUM*64) {
|
||||
return 0x1c00|((freq>>7)&0x3ff);
|
||||
} else if (freq>=OPLL_C_NUM*32) {
|
||||
return 0x1800|((freq>>6)&0x3ff);
|
||||
} else if (freq>=OPLL_C_NUM*16) {
|
||||
return 0x1400|((freq>>5)&0x3ff);
|
||||
} else if (freq>=OPLL_C_NUM*8) {
|
||||
return 0x1000|((freq>>4)&0x3ff);
|
||||
} else if (freq>=OPLL_C_NUM*4) {
|
||||
return 0xc00|((freq>>3)&0x3ff);
|
||||
} else if (freq>=OPLL_C_NUM*2) {
|
||||
return 0x800|((freq>>2)&0x3ff);
|
||||
} else if (freq>=OPLL_C_NUM) {
|
||||
return 0x400|((freq>>1)&0x3ff);
|
||||
} else {
|
||||
return freq&0x7ff;
|
||||
return freq&0x3ff;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -409,38 +455,45 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
if (!chan[c.chan].std.willVol) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
}
|
||||
|
||||
/*
|
||||
for (int i=0; i<4; i++) {
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(baseAddr+ADDR_TL,127);
|
||||
} else {
|
||||
if (isOutput[chan[c.chan].state.alg][i]) {
|
||||
if (!chan[c.chan].active || chan[c.chan].insChanged) {
|
||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
|
||||
}
|
||||
if (chan[c.chan].insChanged) {
|
||||
int ops=(slots[3][c.chan]!=255 && ins->fm.ops==4 && oplType==3)?4:2;
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][c.chan];
|
||||
if (slot==255) continue;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i];
|
||||
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
|
||||
} else {
|
||||
if (chan[c.chan].insChanged) {
|
||||
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||
if (isOutputL[ops==4][chan[c.chan].state.alg][i]) {
|
||||
rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[c.chan].outVol&0x3f))/63))|(op.ksl<<6));
|
||||
} else {
|
||||
rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6));
|
||||
}
|
||||
}
|
||||
|
||||
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
|
||||
rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr);
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr);
|
||||
if (oplType>1) {
|
||||
rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3));
|
||||
}
|
||||
}
|
||||
if (chan[c.chan].insChanged) {
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1));
|
||||
if (ops==4) {
|
||||
rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1));
|
||||
}
|
||||
} else {
|
||||
rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4));
|
||||
if (ops==4) {
|
||||
rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[c.chan].insChanged) {
|
||||
rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3));
|
||||
rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4));
|
||||
}
|
||||
*/
|
||||
|
||||
chan[c.chan].insChanged=false;
|
||||
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
|
@ -453,19 +506,11 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
if (c.chan==5) {
|
||||
dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002,0);
|
||||
}
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
chan[c.chan].active=false;
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
if (c.chan==5) {
|
||||
dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002,0);
|
||||
}
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
chan[c.chan].active=false;
|
||||
|
@ -558,17 +603,6 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SAMPLE_MODE: {
|
||||
dacMode=c.value;
|
||||
rWrite(0x2b,c.value<<7);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
sampleBank=c.value;
|
||||
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_LEGATO: {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].note=c.value;
|
||||
|
@ -634,7 +668,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
return 0;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 127;
|
||||
return 63;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
chan[c.chan].inPorta=c.value;
|
||||
|
@ -724,18 +758,12 @@ void DivPlatformOPL::reset() {
|
|||
}
|
||||
|
||||
lastBusy=60;
|
||||
dacMode=0;
|
||||
dacPeriod=0;
|
||||
dacPos=0;
|
||||
dacRate=0;
|
||||
dacSample=-1;
|
||||
sampleBank=0;
|
||||
lfoValue=8;
|
||||
properDrums=properDrumsSys;
|
||||
|
||||
extMode=false;
|
||||
|
||||
// LFO
|
||||
immWrite(0x22,lfoValue);
|
||||
if (oplType==3) { // enable OPL3 features
|
||||
immWrite(0x105,1);
|
||||
}
|
||||
|
||||
delay=0;
|
||||
}
|
||||
|
@ -779,20 +807,25 @@ void DivPlatformOPL::setYMFM(bool use) {
|
|||
useYMFM=use;
|
||||
}
|
||||
|
||||
void DivPlatformOPL::setOPLType(int type) {
|
||||
void DivPlatformOPL::setOPLType(int type, bool drums) {
|
||||
switch (type) {
|
||||
case 1: case 2:
|
||||
slotsNonDrums=(const unsigned char**)slotsOPL2;
|
||||
slotsDrums=(const unsigned char**)slotsOPL2Drums;
|
||||
slotsNonDrums=slotsOPL2;
|
||||
slotsDrums=slotsOPL2Drums;
|
||||
slots=drums?slotsDrums:slotsNonDrums;
|
||||
chanMap=chanMapOPL2;
|
||||
chipFreqBase=9440540*0.25;
|
||||
break;
|
||||
case 3:
|
||||
slotsNonDrums=(const unsigned char**)slotsOPL3;
|
||||
slotsDrums=(const unsigned char**)slotsOPL3Drums;
|
||||
slotsNonDrums=slotsOPL3;
|
||||
slotsDrums=slotsOPL3Drums;
|
||||
slots=drums?slotsDrums:slotsNonDrums;
|
||||
chanMap=chanMapOPL3;
|
||||
chipFreqBase=9440540;
|
||||
break;
|
||||
}
|
||||
oplType=type;
|
||||
properDrumsSys=drums;
|
||||
}
|
||||
|
||||
void DivPlatformOPL::setFlags(unsigned int flags) {
|
||||
|
@ -820,14 +853,18 @@ void DivPlatformOPL::setFlags(unsigned int flags) {
|
|||
rate=chipClock/36;
|
||||
}*/
|
||||
|
||||
chipClock=COLOR_NTSC*4.0;
|
||||
rate=chipClock/32;
|
||||
if (oplType==3) {
|
||||
chipClock=COLOR_NTSC*4.0;
|
||||
rate=chipClock/288;
|
||||
} else {
|
||||
chipClock=COLOR_NTSC;
|
||||
rate=chipClock/72;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
ladder=false;
|
||||
skipRegisterWrites=false;
|
||||
for (int i=0; i<20; i++) {
|
||||
isMuted[i]=false;
|
||||
|
|
|
@ -67,22 +67,18 @@ class DivPlatformOPL: public DivDispatch {
|
|||
const unsigned char** slotsNonDrums;
|
||||
const unsigned char** slotsDrums;
|
||||
const unsigned char** slots;
|
||||
const unsigned char* chanMap;
|
||||
const unsigned short* chanMap;
|
||||
double chipFreqBase;
|
||||
int delay, oplType;
|
||||
unsigned char lastBusy;
|
||||
|
||||
unsigned char regPool[512];
|
||||
|
||||
bool dacMode;
|
||||
int dacPeriod;
|
||||
int dacRate;
|
||||
unsigned int dacPos;
|
||||
int dacSample;
|
||||
unsigned char sampleBank;
|
||||
bool properDrums, properDrumsSys;
|
||||
|
||||
unsigned char lfoValue;
|
||||
|
||||
bool extMode, useYMFM;
|
||||
bool ladder;
|
||||
bool useYMFM;
|
||||
|
||||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
@ -107,7 +103,7 @@ class DivPlatformOPL: public DivDispatch {
|
|||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
void setYMFM(bool use);
|
||||
void setOPLType(int type);
|
||||
void setOPLType(int type, bool drums);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
bool keyOffAffectsPorta(int ch);
|
||||
void toggleRegisterDump(bool enable);
|
||||
|
|
409
src/engine/platform/sound/swan.cpp
Normal file
409
src/engine/platform/sound/swan.cpp
Normal file
|
@ -0,0 +1,409 @@
|
|||
/******************************************************************************/
|
||||
/* Mednafen - Multi-system Emulator */
|
||||
/******************************************************************************/
|
||||
/* sound.cpp - WonderSwan Sound Emulation
|
||||
** Copyright (C) 2007-2017 Mednafen Team
|
||||
** Copyright (C) 2016 Alex 'trap15' Marshall - http://daifukkat.su/
|
||||
**
|
||||
** 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 "swan.h"
|
||||
#include <string.h>
|
||||
|
||||
#define MK_SAMPLE_CACHE \
|
||||
{ \
|
||||
int sample; \
|
||||
sample = (((wsRAM[(/*(SampleRAMPos << 6) + */(sample_pos[ch] >> 1) + (ch << 4)) ] >> ((sample_pos[ch] & 1) ? 4 : 0)) & 0x0F)); \
|
||||
sample_cache[ch][0] = sample * ((volume[ch] >> 4) & 0x0F); \
|
||||
sample_cache[ch][1] = sample * ((volume[ch] >> 0) & 0x0F); \
|
||||
}
|
||||
|
||||
#define MK_SAMPLE_CACHE_NOISE \
|
||||
{ \
|
||||
int sample; \
|
||||
sample = ((nreg & 1) ? 0xF : 0x0); \
|
||||
sample_cache[ch][0] = sample * ((volume[ch] >> 4) & 0x0F); \
|
||||
sample_cache[ch][1] = sample * ((volume[ch] >> 0) & 0x0F); \
|
||||
}
|
||||
|
||||
#define MK_SAMPLE_CACHE_VOICE \
|
||||
{ \
|
||||
int sample, half; \
|
||||
sample = volume[ch]; \
|
||||
half = sample >> 1; \
|
||||
sample_cache[ch][0] = (voice_volume & 4) ? sample : (voice_volume & 8) ? half : 0; \
|
||||
sample_cache[ch][1] = (voice_volume & 1) ? sample : (voice_volume & 2) ? half : 0; \
|
||||
}
|
||||
|
||||
|
||||
#define SYNCSAMPLE(wt) /* \
|
||||
{ \
|
||||
int32_t left = sample_cache[ch][0], right = sample_cache[ch][1]; \
|
||||
WaveSynth.offset_inline(wt, left - last_val[ch][0], sbuf[0]); \
|
||||
WaveSynth.offset_inline(wt, right - last_val[ch][1], sbuf[1]); \
|
||||
last_val[ch][0] = left; \
|
||||
last_val[ch][1] = right; \
|
||||
} */
|
||||
|
||||
#define SYNCSAMPLE_NOISE(wt) SYNCSAMPLE(wt)
|
||||
|
||||
void WSwan::SoundUpdate(uint32_t v30mz_timestamp)
|
||||
{
|
||||
int32_t run_time;
|
||||
|
||||
//printf("%d\n", v30mz_timestamp);
|
||||
//printf("%02x %02x\n", control, noise_control);
|
||||
run_time = v30mz_timestamp - last_ts;
|
||||
|
||||
for(int y = 0; y < 2; y++)
|
||||
sbuf[y] = 0;
|
||||
|
||||
for(unsigned int ch = 0; ch < 4; ch++)
|
||||
{
|
||||
// Channel is disabled?
|
||||
if(!(control & (1 << ch)))
|
||||
continue;
|
||||
|
||||
if(ch == 1 && (control & 0x20)) // Direct D/A mode?
|
||||
{
|
||||
MK_SAMPLE_CACHE_VOICE;
|
||||
SYNCSAMPLE(v30mz_timestamp);
|
||||
}
|
||||
else if(ch == 2 && (control & 0x40) && sweep_value) // Sweep
|
||||
{
|
||||
uint32_t tmp_pt = 2048 - period[ch];
|
||||
uint32_t tmp_run_time = run_time;
|
||||
|
||||
while(tmp_run_time)
|
||||
{
|
||||
int32_t sub_run_time = tmp_run_time;
|
||||
|
||||
if(sub_run_time > sweep_8192_divider)
|
||||
sub_run_time = sweep_8192_divider;
|
||||
|
||||
sweep_8192_divider -= sub_run_time;
|
||||
if(sweep_8192_divider <= 0)
|
||||
{
|
||||
sweep_8192_divider += 8192;
|
||||
sweep_counter--;
|
||||
if(sweep_counter <= 0)
|
||||
{
|
||||
sweep_counter = sweep_step + 1;
|
||||
period[ch] = (period[ch] + (int8_t)sweep_value) & 0x7FF;
|
||||
}
|
||||
}
|
||||
|
||||
if(tmp_pt > 4)
|
||||
{
|
||||
period_counter[ch] -= sub_run_time;
|
||||
while(period_counter[ch] <= 0)
|
||||
{
|
||||
sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F;
|
||||
|
||||
MK_SAMPLE_CACHE;
|
||||
period_counter[ch] += tmp_pt;
|
||||
}
|
||||
}
|
||||
tmp_run_time -= sub_run_time;
|
||||
}
|
||||
}
|
||||
else if(ch == 3 && (control & 0x80) && (noise_control & 0x10)) // Noise
|
||||
{
|
||||
uint32_t tmp_pt = 2048 - period[ch];
|
||||
|
||||
period_counter[ch] -= run_time;
|
||||
while(period_counter[ch] <= 0)
|
||||
{
|
||||
static const uint8_t stab[8] = { 14, 10, 13, 4, 8, 6, 9, 11 };
|
||||
|
||||
nreg = ((nreg << 1) | ((1 ^ (nreg >> 7) ^ (nreg >> stab[noise_control & 0x7])) & 1)) & 0x7FFF;
|
||||
|
||||
if(control & 0x80)
|
||||
{
|
||||
MK_SAMPLE_CACHE_NOISE;
|
||||
SYNCSAMPLE_NOISE(v30mz_timestamp + period_counter[ch]);
|
||||
}
|
||||
else if(tmp_pt > 4)
|
||||
{
|
||||
sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F;
|
||||
MK_SAMPLE_CACHE;
|
||||
SYNCSAMPLE(v30mz_timestamp + period_counter[ch]);
|
||||
}
|
||||
period_counter[ch] += tmp_pt;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t tmp_pt = 2048 - period[ch];
|
||||
|
||||
if(tmp_pt > 4)
|
||||
{
|
||||
period_counter[ch] -= run_time;
|
||||
while(period_counter[ch] <= 0)
|
||||
{
|
||||
sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F;
|
||||
|
||||
MK_SAMPLE_CACHE;
|
||||
SYNCSAMPLE(v30mz_timestamp + period_counter[ch]); // - period_counter[ch]);
|
||||
period_counter[ch] += tmp_pt;
|
||||
}
|
||||
}
|
||||
}
|
||||
sbuf[0] += sample_cache[ch][0];
|
||||
sbuf[1] += sample_cache[ch][1];
|
||||
}
|
||||
|
||||
if(HVoiceCtrl & 0x80)
|
||||
{
|
||||
int16_t sample = (uint8_t)HyperVoice;
|
||||
|
||||
switch(HVoiceCtrl & 0xC)
|
||||
{
|
||||
case 0x0: sample = (uint16_t)sample << (8 - (HVoiceCtrl & 3)); break;
|
||||
case 0x4: sample = (uint16_t)(sample | -0x100) << (8 - (HVoiceCtrl & 3)); break;
|
||||
case 0x8: sample = (uint16_t)((int8_t)sample) << (8 - (HVoiceCtrl & 3)); break;
|
||||
case 0xC: sample = (uint16_t)sample << 8; break;
|
||||
}
|
||||
// bring back to 11bit, keeping signedness
|
||||
sample >>= 5;
|
||||
|
||||
int32_t left, right;
|
||||
left = (HVoiceChanCtrl & 0x40) ? sample : 0;
|
||||
right = (HVoiceChanCtrl & 0x20) ? sample : 0;
|
||||
|
||||
// WaveSynth.offset_inline(v30mz_timestamp, left - last_hv_val[0], sbuf[0]);
|
||||
// WaveSynth.offset_inline(v30mz_timestamp, right - last_hv_val[1], sbuf[1]);
|
||||
// last_hv_val[0] = left;
|
||||
// last_hv_val[1] = right;
|
||||
sbuf[0] += left;
|
||||
sbuf[1] += right;
|
||||
}
|
||||
last_ts = v30mz_timestamp;
|
||||
}
|
||||
|
||||
void WSwan::SoundWrite(uint32_t A, uint8_t V)
|
||||
{
|
||||
if(A >= 0x80 && A <= 0x87)
|
||||
{
|
||||
int ch = (A - 0x80) >> 1;
|
||||
|
||||
if(A & 1)
|
||||
period[ch] = (period[ch] & 0x00FF) | ((V & 0x07) << 8);
|
||||
else
|
||||
period[ch] = (period[ch] & 0x0700) | ((V & 0xFF) << 0);
|
||||
|
||||
//printf("Period %d: 0x%04x --- %f\n", ch, period[ch], 3072000.0 / (2048 - period[ch]));
|
||||
}
|
||||
else if(A >= 0x88 && A <= 0x8B)
|
||||
{
|
||||
volume[A - 0x88] = V;
|
||||
}
|
||||
else if(A == 0x8C)
|
||||
sweep_value = V;
|
||||
else if(A == 0x8D)
|
||||
{
|
||||
sweep_step = V;
|
||||
sweep_counter = sweep_step + 1;
|
||||
sweep_8192_divider = 8192;
|
||||
}
|
||||
else if(A == 0x8E)
|
||||
{
|
||||
//printf("NOISECONTROL: %02x\n", V);
|
||||
if(V & 0x8)
|
||||
nreg = 0;
|
||||
|
||||
noise_control = V & 0x17;
|
||||
}
|
||||
else if(A == 0x90)
|
||||
{
|
||||
for(int n = 0; n < 4; n++)
|
||||
{
|
||||
if(!(control & (1 << n)) && (V & (1 << n)))
|
||||
{
|
||||
period_counter[n] = 1;
|
||||
sample_pos[n] = 0x1F;
|
||||
}
|
||||
}
|
||||
control = V;
|
||||
//printf("Sound Control: %02x\n", V);
|
||||
}
|
||||
else if(A == 0x91)
|
||||
{
|
||||
output_control = V & 0xF;
|
||||
//printf("%02x, %02x\n", V, (V >> 1) & 3);
|
||||
}
|
||||
else if(A == 0x92)
|
||||
nreg = (nreg & 0xFF00) | (V << 0);
|
||||
else if(A == 0x93)
|
||||
nreg = (nreg & 0x00FF) | ((V & 0x7F) << 8);
|
||||
else if(A == 0x94)
|
||||
{
|
||||
voice_volume = V & 0xF;
|
||||
//printf("%02x\n", V);
|
||||
}
|
||||
else switch(A)
|
||||
{
|
||||
case 0x6A: HVoiceCtrl = V; break;
|
||||
case 0x6B: HVoiceChanCtrl = V & 0x6F; break;
|
||||
case 0x8F: SampleRAMPos = V; break;
|
||||
case 0x95: HyperVoice = V; break; // Pick a port, any port?!
|
||||
//default: printf("%04x:%02x\n", A, V); break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t WSwan::SoundRead(uint32_t A)
|
||||
{
|
||||
if(A >= 0x80 && A <= 0x87)
|
||||
{
|
||||
int ch = (A - 0x80) >> 1;
|
||||
|
||||
if(A & 1)
|
||||
return(period[ch] >> 8);
|
||||
else
|
||||
return(period[ch]);
|
||||
}
|
||||
else if(A >= 0x88 && A <= 0x8B)
|
||||
return(volume[A - 0x88]);
|
||||
else switch(A)
|
||||
{
|
||||
default: /*printf("SoundRead: %04x\n", A);*/ return(0);
|
||||
case 0x6A: return(HVoiceCtrl);
|
||||
case 0x6B: return(HVoiceChanCtrl);
|
||||
case 0x8C: return(sweep_value);
|
||||
case 0x8D: return(sweep_step);
|
||||
case 0x8E: return(noise_control);
|
||||
case 0x8F: return(SampleRAMPos);
|
||||
case 0x90: return(control);
|
||||
case 0x91: return(output_control | 0x80);
|
||||
case 0x92: return((nreg >> 0) & 0xFF);
|
||||
case 0x93: return((nreg >> 8) & 0xFF);
|
||||
case 0x94: return(voice_volume);
|
||||
}
|
||||
}
|
||||
|
||||
void WSwan::RAMWrite(uint32_t A, uint8_t V)
|
||||
{
|
||||
wsRAM[A & 0x3F] = V;
|
||||
}
|
||||
|
||||
int32_t WSwan::SoundFlush(int16_t *SoundBuf, const int32_t MaxSoundFrames)
|
||||
{
|
||||
int32_t FrameCount = 0;
|
||||
|
||||
if(SoundBuf)
|
||||
{
|
||||
for(int y = 0; y < 2; y++)
|
||||
{
|
||||
// sbuf[y]->end_frame(v30mz_timestamp);
|
||||
// FrameCount = sbuf[y]->read_samples(SoundBuf + y, MaxSoundFrames, true);
|
||||
int32_t left = sbuf[0];
|
||||
int32_t right = sbuf[1];
|
||||
if (left >= 0x400) left = 0x3FF;
|
||||
else if (left < -0x400) left = -0x400;
|
||||
if (right >= 0x400) left = 0x3FF;
|
||||
else if (right < -0x400) left = -0x400;
|
||||
SoundBuf[0] = (int16_t)left << 5;
|
||||
SoundBuf[1] = (int16_t)right << 5;
|
||||
}
|
||||
}
|
||||
|
||||
last_ts = 0;
|
||||
|
||||
return(FrameCount);
|
||||
}
|
||||
|
||||
// Call before wsRAM is updated
|
||||
// void WSwan::SoundCheckRAMWrite(uint32_t A)
|
||||
// {
|
||||
// if((A >> 6) == SampleRAMPos)
|
||||
// SoundUpdate();
|
||||
// }
|
||||
|
||||
// static void RedoVolume(void)
|
||||
// {
|
||||
// WaveSynth.volume(2.5);
|
||||
// }
|
||||
|
||||
// void WSwan::SoundInit(void)
|
||||
// {
|
||||
// for(int i = 0; i < 2; i++)
|
||||
// {
|
||||
// sbuf[i] = new Blip_Buffer();
|
||||
|
||||
// sbuf[i]->set_sample_rate(0 ? 0 : 44100, 60);
|
||||
// sbuf[i]->clock_rate((long)(3072000));
|
||||
// sbuf[i]->bass_freq(20);
|
||||
// }
|
||||
|
||||
// RedoVolume();
|
||||
// }
|
||||
|
||||
// void WSwan::SoundKill(void)
|
||||
// {
|
||||
// for(int i = 0; i < 2; i++)
|
||||
// {
|
||||
// if(sbuf[i])
|
||||
// {
|
||||
// delete sbuf[i];
|
||||
// sbuf[i] = NULL;
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// bool WSwan::SetSoundRate(uint32_t rate)
|
||||
// {
|
||||
// for(int i = 0; i < 2; i++)
|
||||
// sbuf[i]->set_sample_rate(rate?rate:44100, 60);
|
||||
|
||||
// return(true);
|
||||
// }
|
||||
|
||||
void WSwan::SoundReset(void)
|
||||
{
|
||||
memset(period, 0, sizeof(period));
|
||||
memset(volume, 0, sizeof(volume));
|
||||
voice_volume = 0;
|
||||
sweep_step = 0;
|
||||
sweep_value = 0;
|
||||
noise_control = 0;
|
||||
control = 0;
|
||||
output_control = 0;
|
||||
|
||||
sweep_8192_divider = 8192;
|
||||
sweep_counter = 1;
|
||||
SampleRAMPos = 0;
|
||||
|
||||
for(unsigned ch = 0; ch < 4; ch++)
|
||||
period_counter[ch] = 1;
|
||||
|
||||
memset(sample_pos, 0, sizeof(sample_pos));
|
||||
nreg = 0;
|
||||
|
||||
memset(sample_cache, 0, sizeof(sample_cache));
|
||||
// memset(last_val, 0, sizeof(last_val));
|
||||
last_v_val = 0;
|
||||
|
||||
HyperVoice = 0;
|
||||
last_hv_val[0] = last_hv_val[1] = 0;
|
||||
HVoiceCtrl = 0;
|
||||
HVoiceChanCtrl = 0;
|
||||
|
||||
for(int y = 0; y < 2; y++)
|
||||
// sbuf[y]->clear();
|
||||
sbuf[y] = 0;
|
||||
last_ts = 0;
|
||||
}
|
81
src/engine/platform/sound/swan.h
Normal file
81
src/engine/platform/sound/swan.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/******************************************************************************/
|
||||
/* Mednafen - Multi-system Emulator */
|
||||
/******************************************************************************/
|
||||
/* sound.h - WonderSwan Sound Emulation
|
||||
** Copyright (C) 2007-2016 Mednafen Team
|
||||
**
|
||||
** 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 __WSWAN_SOUND_H
|
||||
#define __WSWAN_SOUND_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class WSwan
|
||||
{
|
||||
public:
|
||||
int32_t SoundFlush(int16_t *SoundBuf, const int32_t MaxSoundFrames);
|
||||
|
||||
// void SoundInit(void);
|
||||
// void SoundKill(void);
|
||||
// void SetSoundMultiplier(double multiplier);
|
||||
// bool SetSoundRate(uint32_t rate);
|
||||
|
||||
void SoundWrite(uint32_t, uint8_t);
|
||||
uint8_t SoundRead(uint32_t);
|
||||
void SoundReset(void);
|
||||
// void SoundCheckRAMWrite(uint32_t A);
|
||||
|
||||
void SoundUpdate(uint32_t);
|
||||
void RAMWrite(uint32_t, uint8_t);
|
||||
|
||||
private:
|
||||
// Blip_Synth<blip_good_quality, 4096> WaveSynth;
|
||||
|
||||
// Blip_Buffer *sbuf[2] = { NULL };
|
||||
int32_t sbuf[2];
|
||||
|
||||
uint16_t period[4];
|
||||
uint8_t volume[4]; // left volume in upper 4 bits, right in lower 4 bits
|
||||
uint8_t voice_volume;
|
||||
|
||||
uint8_t sweep_step, sweep_value;
|
||||
uint8_t noise_control;
|
||||
uint8_t control;
|
||||
uint8_t output_control;
|
||||
|
||||
int32_t sweep_8192_divider;
|
||||
uint8_t sweep_counter;
|
||||
uint8_t SampleRAMPos;
|
||||
|
||||
int32_t sample_cache[4][2];
|
||||
|
||||
int32_t last_v_val;
|
||||
|
||||
uint8_t HyperVoice;
|
||||
int32_t last_hv_val[2];
|
||||
uint8_t HVoiceCtrl, HVoiceChanCtrl;
|
||||
|
||||
int32_t period_counter[4];
|
||||
// int32_t last_val[4][2]; // Last outputted value, l&r
|
||||
uint8_t sample_pos[4];
|
||||
uint16_t nreg;
|
||||
uint32_t last_ts;
|
||||
|
||||
uint8_t wsRAM[64];
|
||||
};
|
||||
|
||||
#endif
|
521
src/engine/platform/swan.cpp
Normal file
521
src/engine/platform/swan.cpp
Normal file
|
@ -0,0 +1,521 @@
|
|||
/**
|
||||
* 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 "swan.h"
|
||||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);}}
|
||||
|
||||
#define CHIP_DIVIDER 32
|
||||
|
||||
const char* regCheatSheetWS[]={
|
||||
"CH1_Pitch", "00",
|
||||
"CH2_Pitch", "02",
|
||||
"CH3_Pitch", "04",
|
||||
"CH4_Pitch", "06",
|
||||
"CH1_Vol", "08",
|
||||
"CH2_Vol", "09",
|
||||
"CH3_Vol", "0A",
|
||||
"CH4_Vol", "0B",
|
||||
"Sweep_Value", "0C",
|
||||
"Sweep_Time", "0D",
|
||||
"Noise", "0E",
|
||||
"Wave_Base", "0F",
|
||||
"Ctrl", "10",
|
||||
"Output", "11",
|
||||
"Random", "12",
|
||||
"Voice_Ctrl", "14",
|
||||
"Wave_Mem", "40",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformSwan::getRegisterSheet() {
|
||||
return regCheatSheetWS;
|
||||
}
|
||||
|
||||
const char* DivPlatformSwan::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Setup noise mode (0: disabled; 1-8: enabled/tap)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Setup sweep period (0: disabled; 1-20: enabled/period)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set sweep amount";
|
||||
break;
|
||||
case 0x17:
|
||||
return "17xx: Toggle PCM mode";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
// PCM part
|
||||
if (pcm && dacSample!=-1) {
|
||||
dacPeriod+=dacRate;
|
||||
while (dacPeriod>rate) {
|
||||
DivSample* s=parent->getSample(dacSample);
|
||||
if (s->samples<=0) {
|
||||
dacSample=-1;
|
||||
continue;
|
||||
}
|
||||
rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80);
|
||||
if (dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<=(int)s->samples) {
|
||||
dacPos=s->loopStart;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
}
|
||||
}
|
||||
dacPeriod-=rate;
|
||||
}
|
||||
}
|
||||
|
||||
// the rest
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
regPool[w.addr]=w.val;
|
||||
if (w.addr<0x40) ws->SoundWrite(w.addr|0x80,w.val);
|
||||
else ws->RAMWrite(w.addr&0x3f,w.val);
|
||||
writes.pop();
|
||||
}
|
||||
int16_t samp[2]{0, 0};
|
||||
ws->SoundUpdate(16);
|
||||
ws->SoundFlush(samp, 1);
|
||||
bufL[h]=samp[0];
|
||||
bufR[h]=samp[1];
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformSwan::updateWave(int ch) {
|
||||
DivWavetable* wt=parent->getWave(chan[ch].wave);
|
||||
unsigned char addr=0x40+ch*16;
|
||||
if (wt->max<1 || wt->len<1) {
|
||||
for (int i=0; i<16; i++) {
|
||||
rWrite(addr+i,0);
|
||||
}
|
||||
} else {
|
||||
for (int i=0; i<16; i++) {
|
||||
unsigned char nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max;
|
||||
unsigned char nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max;
|
||||
rWrite(addr+i,nibble1|(nibble2<<4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformSwan::calcAndWriteOutVol(int ch, int env) {
|
||||
int vl=chan[ch].vol*((chan[ch].pan>>4)&0x0f)*env/225;
|
||||
int vr=chan[ch].vol*(chan[ch].pan&0x0f)*env/225;
|
||||
if (ch==1&&pcm) {
|
||||
vl=(vl>0)?((vl>7)?3:2):0;
|
||||
vr=(vr>0)?((vr>7)?3:2):0;
|
||||
chan[1].outVol=vr|(vl<<2);
|
||||
} else {
|
||||
chan[ch].outVol=vr|(vl<<4);
|
||||
}
|
||||
writeOutVol(ch);
|
||||
}
|
||||
|
||||
void DivPlatformSwan::writeOutVol(int ch) {
|
||||
unsigned char val=isMuted[ch]?0:chan[ch].outVol;
|
||||
if (ch==1&&pcm) {
|
||||
rWrite(0x14,val)
|
||||
} else {
|
||||
rWrite(0x08+ch,val);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformSwan::tick() {
|
||||
unsigned char sndCtrl=(pcm?0x20:0)|(sweep?0x40:0)|((noise>0)?0x80:0);
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.hadVol) {
|
||||
int env=chan[i].std.vol;
|
||||
if(parent->getIns(chan[i].ins)->type==DIV_INS_AMIGA) {
|
||||
env=MIN(env/4,15);
|
||||
}
|
||||
calcAndWriteOutVol(i,env);
|
||||
}
|
||||
if (chan[i].std.hadArp) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arpMode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadWave && !(i==1 && pcm)) {
|
||||
if (chan[i].wave!=chan[i].std.wave) {
|
||||
chan[i].wave=chan[i].std.wave;
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
if (chan[i].active) {
|
||||
sndCtrl|=(1<<i);
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
|
||||
if (i==1 && pcm && furnaceDac) {
|
||||
double off=1.0;
|
||||
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(dacSample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=8363.0/(double)s->centerRate;
|
||||
}
|
||||
}
|
||||
dacRate=((double)chipClock/2)/MAX(1,off*chan[i].freq);
|
||||
if (dumpWrites) addWrite(0xffff0001,dacRate);
|
||||
}
|
||||
if (chan[i].freq>2048) chan[i].freq=2048;
|
||||
if (chan[i].freq<1) chan[i].freq=1;
|
||||
int rVal=2048-chan[i].freq;
|
||||
rWrite(i*2,rVal&0xff);
|
||||
rWrite(i*2+1,rVal>>8);
|
||||
if (chan[i].keyOn) {
|
||||
if (!chan[i].std.willVol) {
|
||||
calcAndWriteOutVol(i,15);
|
||||
}
|
||||
if (chan[i].wave<0) {
|
||||
chan[i].wave=0;
|
||||
updateWave(i);
|
||||
}
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
chan[i].keyOff=false;
|
||||
}
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
if (chan[3].std.hadDuty) {
|
||||
noise=chan[3].std.duty;
|
||||
if (noise>0) {
|
||||
rWrite(0x0e,((noise-1)&0x07)|0x18);
|
||||
sndCtrl|=0x80;
|
||||
} else {
|
||||
sndCtrl&=~0x80;
|
||||
}
|
||||
}
|
||||
rWrite(0x10,sndCtrl);
|
||||
}
|
||||
|
||||
int DivPlatformSwan::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
if (c.chan==1) {
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
pcm=true;
|
||||
} else if (furnaceDac) {
|
||||
pcm=false;
|
||||
}
|
||||
if (pcm) {
|
||||
if (skipRegisterWrites) break;
|
||||
dacPos=0;
|
||||
dacPeriod=0;
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
dacSample=ins->amiga.initSample;
|
||||
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
|
||||
dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002,0);
|
||||
break;
|
||||
} else {
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffff0000,dacSample);
|
||||
}
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[1].baseFreq=NOTE_PERIODIC(c.value);
|
||||
chan[1].freqChanged=true;
|
||||
chan[1].note=c.value;
|
||||
}
|
||||
chan[1].active=true;
|
||||
chan[1].keyOn=true;
|
||||
chan[1].std.init(ins);
|
||||
furnaceDac=true;
|
||||
} else {
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[1].note=c.value;
|
||||
}
|
||||
dacSample=12*sampleBank+chan[1].note%12;
|
||||
if (dacSample>=parent->song.sampleLen) {
|
||||
dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002,0);
|
||||
break;
|
||||
} else {
|
||||
if (dumpWrites) addWrite(0xffff0000,dacSample);
|
||||
}
|
||||
dacRate=parent->getSample(dacSample)->rate;
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffff0001,dacRate);
|
||||
}
|
||||
chan[1].active=true;
|
||||
chan[1].keyOn=true;
|
||||
furnaceDac=false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
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].keyOn=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
if (c.chan==1&&pcm) {
|
||||
dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002,0);
|
||||
pcm=false;
|
||||
}
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
chan[c.chan].ins=c.value;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.hadVol) {
|
||||
calcAndWriteOutVol(c.chan,15);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
return chan[c.chan].vol;
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_WAVE:
|
||||
chan[c.chan].wave=c.value;
|
||||
updateWave(c.chan);
|
||||
chan[c.chan].keyOn=true;
|
||||
break;
|
||||
case DIV_CMD_WS_SWEEP_TIME:
|
||||
if (c.chan==2) {
|
||||
if (c.value==0) {
|
||||
sweep=false;
|
||||
} else {
|
||||
sweep=true;
|
||||
rWrite(0x0d,(c.value-1)&0xff);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_WS_SWEEP_AMOUNT:
|
||||
if (c.chan==2) {
|
||||
rWrite(0x0c,c.value&0xff);
|
||||
}
|
||||
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_STD_NOISE_MODE:
|
||||
if (c.chan==3) {
|
||||
noise=c.value&0xff;
|
||||
if (noise>0) rWrite(0x0e,((noise-1)&0x07)|0x18);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
if (c.chan==1) pcm=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
sampleBank=c.value;
|
||||
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
chan[c.chan].pan=c.value;
|
||||
calcAndWriteOutVol(c.chan,chan[c.chan].std.willVol?chan[c.chan].std.vol:15);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(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].std.init(parent->getIns(chan[c.chan].ins));
|
||||
}
|
||||
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 DivPlatformSwan::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
writeOutVol(ch);
|
||||
}
|
||||
|
||||
void DivPlatformSwan::forceIns() {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
updateWave(i);
|
||||
writeOutVol(i);
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformSwan::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformSwan::getRegisterPool() {
|
||||
// get Random from emulator
|
||||
regPool[0x12]=ws->SoundRead(0x92);
|
||||
regPool[0x13]=ws->SoundRead(0x93);
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformSwan::getRegisterPoolSize() {
|
||||
return 128;
|
||||
}
|
||||
|
||||
void DivPlatformSwan::reset() {
|
||||
while (!writes.empty()) writes.pop();
|
||||
memset(regPool,0,128);
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i]=Channel();
|
||||
chan[i].vol=15;
|
||||
chan[i].pan=0xff;
|
||||
rWrite(0x08+i,0xff);
|
||||
}
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
}
|
||||
ws->SoundReset();
|
||||
pcm=false;
|
||||
sweep=false;
|
||||
furnaceDac=false;
|
||||
noise=0;
|
||||
dacPeriod=0;
|
||||
dacRate=0;
|
||||
dacPos=0;
|
||||
dacSample=-1;
|
||||
sampleBank=0;
|
||||
rWrite(0x0f,0x00); // wave table at 0x0000
|
||||
rWrite(0x11,0x09); // enable speakers
|
||||
}
|
||||
|
||||
bool DivPlatformSwan::isStereo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformSwan::notifyWaveChange(int wave) {
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].wave==wave) {
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformSwan::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformSwan::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformSwan::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
int DivPlatformSwan::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
chipClock=3072000;
|
||||
rate=chipClock/16; // = 192000kHz, should be enough
|
||||
for (int i=0; i<4; i++) {
|
||||
isMuted[i]=false;
|
||||
}
|
||||
ws=new WSwan();
|
||||
reset();
|
||||
return 4;
|
||||
}
|
||||
|
||||
void DivPlatformSwan::quit() {
|
||||
delete ws;
|
||||
}
|
||||
|
||||
DivPlatformSwan::~DivPlatformSwan() {
|
||||
}
|
95
src/engine/platform/swan.h
Normal file
95
src/engine/platform/swan.h
Normal file
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* 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 _SWAN_H
|
||||
#define _SWAN_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include "../macroInt.h"
|
||||
#include "sound/swan.h"
|
||||
#include <queue>
|
||||
|
||||
class DivPlatformSwan: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins, pan;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
|
||||
int vol, outVol, wave;
|
||||
DivMacroInt std;
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
pan(255),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
vol(15),
|
||||
outVol(15),
|
||||
wave(-1) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
bool isMuted[4];
|
||||
bool pcm, sweep, furnaceDac;
|
||||
unsigned char sampleBank, noise;
|
||||
int dacPeriod, dacRate;
|
||||
unsigned int dacPos;
|
||||
int dacSample;
|
||||
|
||||
unsigned char regPool[0x80];
|
||||
struct QueuedWrite {
|
||||
unsigned char addr;
|
||||
unsigned char val;
|
||||
QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
WSwan* ws;
|
||||
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);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void muteChannel(int ch, bool mute);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
bool isStereo();
|
||||
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();
|
||||
~DivPlatformSwan();
|
||||
private:
|
||||
void calcAndWriteOutVol(int ch, int env);
|
||||
void writeOutVol(int ch);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -32,7 +32,7 @@ static bool isOutput[8][4]={
|
|||
{true ,true ,true ,true},
|
||||
};
|
||||
static unsigned char dtTable[8]={
|
||||
7,6,5,0,1,2,3,0
|
||||
7,6,5,0,1,2,3,4
|
||||
};
|
||||
|
||||
static int orderedOps[4]={
|
||||
|
|
|
@ -251,6 +251,27 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_SWAN:
|
||||
switch (effect) {
|
||||
case 0x10: // select waveform
|
||||
dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal));
|
||||
break;
|
||||
case 0x11: // noise mode
|
||||
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
|
||||
break;
|
||||
case 0x12: // sweep period
|
||||
dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_TIME,ch,effectVal));
|
||||
break;
|
||||
case 0x13: // sweep amount
|
||||
dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_AMOUNT,ch,effectVal));
|
||||
break;
|
||||
case 0x17: // PCM enable
|
||||
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0)));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_VERA:
|
||||
switch (effect) {
|
||||
case 0x20: // select waveform
|
||||
|
|
|
@ -80,6 +80,10 @@ int SafeWriter::writeC(signed char val) {
|
|||
int SafeWriter::writeS(short val) {
|
||||
return write(&val,2);
|
||||
}
|
||||
int SafeWriter::writeS_BE(short val) {
|
||||
unsigned char bytes[2]{(unsigned char)((val>>8)&0xff), (unsigned char)(val&0xff)};
|
||||
return write(bytes,2);
|
||||
}
|
||||
|
||||
int SafeWriter::writeI(int val) {
|
||||
return write(&val,4);
|
||||
|
|
|
@ -1644,6 +1644,7 @@ bool DivEngine::isVGMExportable(DivSystem which) {
|
|||
case DIV_SYSTEM_OPLL:
|
||||
case DIV_SYSTEM_OPLL_DRUMS:
|
||||
case DIV_SYSTEM_VRC7:
|
||||
case DIV_SYSTEM_SWAN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
|
|
@ -25,119 +25,123 @@
|
|||
constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
|
||||
|
||||
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool isSecond) {
|
||||
unsigned char baseAddr1=isSecond?0xa0:0x50;
|
||||
unsigned char baseAddr2=isSecond?0x80:0;
|
||||
unsigned short baseAddr2S=isSecond?0x8000:0;
|
||||
unsigned char smsAddr=isSecond?0x30:0x50;
|
||||
if (write.addr==0xffffffff) { // Furnace fake reset
|
||||
switch (sys) {
|
||||
case DIV_SYSTEM_YM2612:
|
||||
case DIV_SYSTEM_YM2612_EXT:
|
||||
for (int i=0; i<3; i++) { // set SL and RR to highest
|
||||
w->writeC(isSecond?0xa2:0x52);
|
||||
w->writeC(2|baseAddr1);
|
||||
w->writeC(0x80+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa2:0x52);
|
||||
w->writeC(2|baseAddr1);
|
||||
w->writeC(0x84+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa2:0x52);
|
||||
w->writeC(2|baseAddr1);
|
||||
w->writeC(0x88+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa2:0x52);
|
||||
w->writeC(2|baseAddr1);
|
||||
w->writeC(0x8c+i);
|
||||
w->writeC(0xff);
|
||||
|
||||
w->writeC(isSecond?0xa3:0x53);
|
||||
w->writeC(3|baseAddr1);
|
||||
w->writeC(0x80+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa3:0x53);
|
||||
w->writeC(3|baseAddr1);
|
||||
w->writeC(0x84+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa3:0x53);
|
||||
w->writeC(3|baseAddr1);
|
||||
w->writeC(0x88+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa3:0x53);
|
||||
w->writeC(3|baseAddr1);
|
||||
w->writeC(0x8c+i);
|
||||
w->writeC(0xff);
|
||||
}
|
||||
for (int i=0; i<3; i++) { // note off
|
||||
w->writeC(isSecond?0xa2:0x52);
|
||||
w->writeC(2|baseAddr1);
|
||||
w->writeC(0x28);
|
||||
w->writeC(i);
|
||||
w->writeC(isSecond?0xa2:0x52);
|
||||
w->writeC(2|baseAddr1);
|
||||
w->writeC(0x28);
|
||||
w->writeC(4+i);
|
||||
}
|
||||
w->writeC(isSecond?0xa2:0x52); // disable DAC
|
||||
w->writeC(2|baseAddr1); // disable DAC
|
||||
w->writeC(0x2b);
|
||||
w->writeC(0);
|
||||
break;
|
||||
case DIV_SYSTEM_SMS:
|
||||
for (int i=0; i<4; i++) {
|
||||
w->writeC(isSecond?0x30:0x50);
|
||||
w->writeC(smsAddr);
|
||||
w->writeC(0x90|(i<<5)|15);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_GB:
|
||||
// square 1
|
||||
w->writeC(0xb3);
|
||||
w->writeC(isSecond?0x82:2);
|
||||
w->writeC(2|baseAddr2);
|
||||
w->writeC(0);
|
||||
w->writeC(0xb3);
|
||||
w->writeC(isSecond?0x84:4);
|
||||
w->writeC(4|baseAddr2);
|
||||
w->writeC(0x80);
|
||||
|
||||
// square 2
|
||||
w->writeC(0xb3);
|
||||
w->writeC(isSecond?0x87:7);
|
||||
w->writeC(7|baseAddr2);
|
||||
w->writeC(0);
|
||||
w->writeC(0xb3);
|
||||
w->writeC(isSecond?0x89:9);
|
||||
w->writeC(9|baseAddr2);
|
||||
w->writeC(0x80);
|
||||
|
||||
// wave
|
||||
w->writeC(0xb3);
|
||||
w->writeC(isSecond?0x8c:0x0c);
|
||||
w->writeC(0x0c|baseAddr2);
|
||||
w->writeC(0);
|
||||
w->writeC(0xb3);
|
||||
w->writeC(isSecond?0x8e:0x0e);
|
||||
w->writeC(0x0e|baseAddr2);
|
||||
w->writeC(0x80);
|
||||
|
||||
// noise
|
||||
w->writeC(0xb3);
|
||||
w->writeC(isSecond?0x91:0x11);
|
||||
w->writeC(0x11|baseAddr2);
|
||||
w->writeC(0);
|
||||
w->writeC(0xb3);
|
||||
w->writeC(isSecond?0x93:0x13);
|
||||
w->writeC(0x13|baseAddr2);
|
||||
w->writeC(0x80);
|
||||
break;
|
||||
case DIV_SYSTEM_PCE:
|
||||
for (int i=0; i<6; i++) {
|
||||
w->writeC(0xb9);
|
||||
w->writeC(isSecond?0x80:0);
|
||||
w->writeC(0|baseAddr2);
|
||||
w->writeC(i);
|
||||
w->writeC(0xb9);
|
||||
w->writeC(isSecond?0x84:4);
|
||||
w->writeC(4|baseAddr2);
|
||||
w->writeC(0);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_NES:
|
||||
w->writeC(0xb4);
|
||||
w->writeC(isSecond?0x95:0x15);
|
||||
w->writeC(0x15|baseAddr2);
|
||||
w->writeC(0);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2151:
|
||||
for (int i=0; i<8; i++) {
|
||||
w->writeC(isSecond?0xa4:0x54);
|
||||
w->writeC(4|baseAddr1);
|
||||
w->writeC(0xe0+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa4:0x54);
|
||||
w->writeC(4|baseAddr1);
|
||||
w->writeC(0xe8+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa4:0x54);
|
||||
w->writeC(4|baseAddr1);
|
||||
w->writeC(0xf0+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa4:0x54);
|
||||
w->writeC(4|baseAddr1);
|
||||
w->writeC(0xf8+i);
|
||||
w->writeC(0xff);
|
||||
|
||||
w->writeC(isSecond?0xa4:0x54);
|
||||
w->writeC(4|baseAddr1);
|
||||
w->writeC(0x08);
|
||||
w->writeC(i);
|
||||
}
|
||||
|
@ -146,7 +150,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
case DIV_SYSTEM_SEGAPCM_COMPAT:
|
||||
for (int i=0; i<16; i++) {
|
||||
w->writeC(0xc0);
|
||||
w->writeS((isSecond?0x8086:0x86)+(i<<3));
|
||||
w->writeS((0x86|baseAddr2S)+(i<<3));
|
||||
w->writeC(3);
|
||||
}
|
||||
break;
|
||||
|
@ -157,60 +161,60 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
case DIV_SYSTEM_YM2610_FULL_EXT:
|
||||
case DIV_SYSTEM_YM2610B_EXT:
|
||||
for (int i=0; i<2; i++) { // set SL and RR to highest
|
||||
w->writeC(isSecond?0xa8:0x58);
|
||||
w->writeC(8|baseAddr1);
|
||||
w->writeC(0x81+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa8:0x58);
|
||||
w->writeC(8|baseAddr1);
|
||||
w->writeC(0x85+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa8:0x58);
|
||||
w->writeC(8|baseAddr1);
|
||||
w->writeC(0x89+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa8:0x58);
|
||||
w->writeC(8|baseAddr1);
|
||||
w->writeC(0x8d+i);
|
||||
w->writeC(0xff);
|
||||
|
||||
w->writeC(isSecond?0xa9:0x59);
|
||||
w->writeC(9|baseAddr1);
|
||||
w->writeC(0x81+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa9:0x59);
|
||||
w->writeC(9|baseAddr1);
|
||||
w->writeC(0x85+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa9:0x59);
|
||||
w->writeC(9|baseAddr1);
|
||||
w->writeC(0x89+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(isSecond?0xa9:0x59);
|
||||
w->writeC(9|baseAddr1);
|
||||
w->writeC(0x8d+i);
|
||||
w->writeC(0xff);
|
||||
}
|
||||
for (int i=0; i<2; i++) { // note off
|
||||
w->writeC(isSecond?0xa8:0x58);
|
||||
w->writeC(8|baseAddr1);
|
||||
w->writeC(0x28);
|
||||
w->writeC(1+i);
|
||||
w->writeC(isSecond?0xa8:0x58);
|
||||
w->writeC(8|baseAddr1);
|
||||
w->writeC(0x28);
|
||||
w->writeC(5+i);
|
||||
}
|
||||
|
||||
// reset AY
|
||||
w->writeC(isSecond?0xa8:0x58);
|
||||
w->writeC(8|baseAddr1);
|
||||
w->writeC(7);
|
||||
w->writeC(0x3f);
|
||||
|
||||
w->writeC(isSecond?0xa8:0x58);
|
||||
w->writeC(8|baseAddr1);
|
||||
w->writeC(8);
|
||||
w->writeC(0);
|
||||
|
||||
w->writeC(isSecond?0xa8:0x58);
|
||||
w->writeC(8|baseAddr1);
|
||||
w->writeC(9);
|
||||
w->writeC(0);
|
||||
|
||||
w->writeC(isSecond?0xa8:0x58);
|
||||
w->writeC(8|baseAddr1);
|
||||
w->writeC(10);
|
||||
w->writeC(0);
|
||||
|
||||
// reset sample
|
||||
w->writeC(isSecond?0xa9:0x59);
|
||||
w->writeC(9|baseAddr1);
|
||||
w->writeC(0);
|
||||
w->writeC(0xbf);
|
||||
break;
|
||||
|
@ -218,56 +222,56 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
case DIV_SYSTEM_OPLL_DRUMS:
|
||||
case DIV_SYSTEM_VRC7:
|
||||
for (int i=0; i<9; i++) {
|
||||
w->writeC(isSecond?0xa1:0x51);
|
||||
w->writeC(1|baseAddr1);
|
||||
w->writeC(0x20+i);
|
||||
w->writeC(0);
|
||||
w->writeC(isSecond?0xa1:0x51);
|
||||
w->writeC(1|baseAddr1);
|
||||
w->writeC(0x30+i);
|
||||
w->writeC(0);
|
||||
w->writeC(isSecond?0xa1:0x51);
|
||||
w->writeC(1|baseAddr1);
|
||||
w->writeC(0x10+i);
|
||||
w->writeC(0);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_AY8910:
|
||||
w->writeC(0xa0);
|
||||
w->writeC(isSecond?0x87:7);
|
||||
w->writeC(7|baseAddr2);
|
||||
w->writeC(0x3f);
|
||||
|
||||
w->writeC(0xa0);
|
||||
w->writeC(isSecond?0x88:8);
|
||||
w->writeC(8|baseAddr2);
|
||||
w->writeC(0);
|
||||
|
||||
w->writeC(0xa0);
|
||||
w->writeC(isSecond?0x89:9);
|
||||
w->writeC(9|baseAddr2);
|
||||
w->writeC(0);
|
||||
|
||||
w->writeC(0xa0);
|
||||
w->writeC(isSecond?0x8a:10);
|
||||
w->writeC(10|baseAddr2);
|
||||
w->writeC(0);
|
||||
break;
|
||||
case DIV_SYSTEM_AY8930:
|
||||
w->writeC(0xa0);
|
||||
w->writeC(isSecond?0x8d:0x0d);
|
||||
w->writeC(0x0d|baseAddr2);
|
||||
w->writeC(0);
|
||||
w->writeC(0xa0);
|
||||
w->writeC(isSecond?0x8d:0x0d);
|
||||
w->writeC(0x0d|baseAddr2);
|
||||
w->writeC(0xa0);
|
||||
break;
|
||||
case DIV_SYSTEM_SAA1099:
|
||||
w->writeC(0xbd);
|
||||
w->writeC(isSecond?0x9c:0x1c);
|
||||
w->writeC(0x1c|baseAddr2);
|
||||
w->writeC(0x02);
|
||||
w->writeC(0xbd);
|
||||
w->writeC(isSecond?0x94:0x14);
|
||||
w->writeC(0x14|baseAddr2);
|
||||
w->writeC(0);
|
||||
w->writeC(0xbd);
|
||||
w->writeC(isSecond?0x95:0x15);
|
||||
w->writeC(0x15|baseAddr2);
|
||||
w->writeC(0);
|
||||
|
||||
for (int i=0; i<6; i++) {
|
||||
w->writeC(0xbd);
|
||||
w->writeC((isSecond?0x80:0)+i);
|
||||
w->writeC((0|baseAddr2)+i);
|
||||
w->writeC(0);
|
||||
}
|
||||
break;
|
||||
|
@ -346,49 +350,49 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
case DIV_SYSTEM_YM2612_EXT:
|
||||
switch (write.addr>>8) {
|
||||
case 0: // port 0
|
||||
w->writeC(isSecond?0xa2:0x52);
|
||||
w->writeC(2|baseAddr1);
|
||||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case 1: // port 1
|
||||
w->writeC(isSecond?0xa3:0x53);
|
||||
w->writeC(3|baseAddr1);
|
||||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case 2: // PSG
|
||||
w->writeC(isSecond?0x30:0x50);
|
||||
w->writeC(smsAddr);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_SMS:
|
||||
w->writeC(isSecond?0x30:0x50);
|
||||
w->writeC(smsAddr);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_GB:
|
||||
w->writeC(0xb3);
|
||||
w->writeC((isSecond?0x80:0)|((write.addr-16)&0xff));
|
||||
w->writeC(baseAddr2|((write.addr-16)&0xff));
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_PCE:
|
||||
w->writeC(0xb9);
|
||||
w->writeC((isSecond?0x80:0)|(write.addr&0xff));
|
||||
w->writeC(baseAddr2|(write.addr&0xff));
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_NES:
|
||||
w->writeC(0xb4);
|
||||
w->writeC((isSecond?0x80:0)|(write.addr&0xff));
|
||||
w->writeC(baseAddr2|(write.addr&0xff));
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2151:
|
||||
w->writeC(isSecond?0xa4:0x54);
|
||||
w->writeC(4|baseAddr1);
|
||||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_SEGAPCM:
|
||||
case DIV_SYSTEM_SEGAPCM_COMPAT:
|
||||
w->writeC(0xc0);
|
||||
w->writeS((isSecond?0x8000:0)|(write.addr&0xffff));
|
||||
w->writeS(baseAddr2S|(write.addr&0xffff));
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2610:
|
||||
|
@ -399,12 +403,12 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
case DIV_SYSTEM_YM2610B_EXT:
|
||||
switch (write.addr>>8) {
|
||||
case 0: // port 0
|
||||
w->writeC(isSecond?0xa8:0x58);
|
||||
w->writeC(8|baseAddr1);
|
||||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case 1: // port 1
|
||||
w->writeC(isSecond?0xa9:0x59);
|
||||
w->writeC(9|baseAddr1);
|
||||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
|
@ -413,19 +417,19 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
case DIV_SYSTEM_OPLL:
|
||||
case DIV_SYSTEM_OPLL_DRUMS:
|
||||
case DIV_SYSTEM_VRC7:
|
||||
w->writeC(isSecond?0xa1:0x51);
|
||||
w->writeC(1|baseAddr1);
|
||||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_AY8910:
|
||||
case DIV_SYSTEM_AY8930:
|
||||
w->writeC(0xa0);
|
||||
w->writeC((isSecond?0x80:0)|(write.addr&0xff));
|
||||
w->writeC(baseAddr2|(write.addr&0xff));
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_SAA1099:
|
||||
w->writeC(0xbd);
|
||||
w->writeC((isSecond?0x80:0)|(write.addr&0xff));
|
||||
w->writeC(baseAddr2|(write.addr&0xff));
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_LYNX:
|
||||
|
@ -439,6 +443,18 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
w->writeC(write.val&0xff);
|
||||
w->writeC(write.addr&0xff);
|
||||
break;
|
||||
case DIV_SYSTEM_SWAN:
|
||||
if ((write.addr&0x7f)<0x40) {
|
||||
w->writeC(0xbc);
|
||||
w->writeC(baseAddr2|(write.addr&0x3f));
|
||||
w->writeC(write.val&0xff);
|
||||
} else {
|
||||
// (Wave) RAM write
|
||||
w->writeC(0xc6);
|
||||
w->writeS_BE(baseAddr2S|(write.addr&0x3f));
|
||||
w->writeC(write.val&0xff);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logW("write not handled!\n");
|
||||
break;
|
||||
|
@ -742,6 +758,21 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
|
|||
addWarning("dual QSound is not supported by the VGM format");
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_SWAN:
|
||||
if (!hasSwan) {
|
||||
hasSwan=disCont[i].dispatch->chipClock;
|
||||
willExport[i]=true;
|
||||
// funny enough, VGM doesn't have support for WSC's sound DMA by design
|
||||
// so DAC stream it goes
|
||||
// since WS has the same PCM format as YM2612 DAC, I can just reuse this flag
|
||||
writeDACSamples=true;
|
||||
} else if (!(hasSwan&0x40000000)) {
|
||||
isSecond[i]=true;
|
||||
willExport[i]=true;
|
||||
hasSwan|=0x40000000;
|
||||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -1027,6 +1058,24 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
|
|||
streamID++;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_SWAN:
|
||||
w->writeC(0x90);
|
||||
w->writeC(streamID);
|
||||
w->writeC(isSecond[i]?0xa1:0x21);
|
||||
w->writeC(0); // port
|
||||
w->writeC(0x09); // DAC
|
||||
|
||||
w->writeC(0x91);
|
||||
w->writeC(streamID);
|
||||
w->writeC(0);
|
||||
w->writeC(1);
|
||||
w->writeC(0);
|
||||
|
||||
w->writeC(0x92);
|
||||
w->writeC(streamID);
|
||||
w->writeI(24000); // default
|
||||
streamID++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1551,6 +1551,7 @@ const char* aboutLine[]={
|
|||
"",
|
||||
"-- program --",
|
||||
"tildearrow",
|
||||
"akumanatt",
|
||||
"cam900",
|
||||
"laoo",
|
||||
"superctr",
|
||||
|
@ -4629,11 +4630,18 @@ bool FurnaceGUI::loop() {
|
|||
sysAddOption(DIV_SYSTEM_OPLL);
|
||||
sysAddOption(DIV_SYSTEM_OPLL_DRUMS);
|
||||
sysAddOption(DIV_SYSTEM_VRC7);
|
||||
sysAddOption(DIV_SYSTEM_OPL);
|
||||
sysAddOption(DIV_SYSTEM_OPL_DRUMS);
|
||||
sysAddOption(DIV_SYSTEM_OPL2);
|
||||
sysAddOption(DIV_SYSTEM_OPL2_DRUMS);
|
||||
sysAddOption(DIV_SYSTEM_OPL3);
|
||||
sysAddOption(DIV_SYSTEM_OPL3_DRUMS);
|
||||
sysAddOption(DIV_SYSTEM_TIA);
|
||||
sysAddOption(DIV_SYSTEM_SAA1099);
|
||||
sysAddOption(DIV_SYSTEM_AY8930);
|
||||
sysAddOption(DIV_SYSTEM_LYNX);
|
||||
sysAddOption(DIV_SYSTEM_QSOUND);
|
||||
sysAddOption(DIV_SYSTEM_SWAN);
|
||||
sysAddOption(DIV_SYSTEM_VERA);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
@ -4928,6 +4936,7 @@ bool FurnaceGUI::loop() {
|
|||
break;
|
||||
}
|
||||
case DIV_SYSTEM_GB:
|
||||
case DIV_SYSTEM_SWAN:
|
||||
case DIV_SYSTEM_VERA:
|
||||
case DIV_SYSTEM_YM2610:
|
||||
case DIV_SYSTEM_YM2610_EXT:
|
||||
|
@ -4976,11 +4985,18 @@ bool FurnaceGUI::loop() {
|
|||
sysChangeOption(i,DIV_SYSTEM_OPLL);
|
||||
sysChangeOption(i,DIV_SYSTEM_OPLL_DRUMS);
|
||||
sysChangeOption(i,DIV_SYSTEM_VRC7);
|
||||
sysChangeOption(i,DIV_SYSTEM_OPL);
|
||||
sysChangeOption(i,DIV_SYSTEM_OPL_DRUMS);
|
||||
sysChangeOption(i,DIV_SYSTEM_OPL2);
|
||||
sysChangeOption(i,DIV_SYSTEM_OPL2_DRUMS);
|
||||
sysChangeOption(i,DIV_SYSTEM_OPL3);
|
||||
sysChangeOption(i,DIV_SYSTEM_OPL3_DRUMS);
|
||||
sysChangeOption(i,DIV_SYSTEM_TIA);
|
||||
sysChangeOption(i,DIV_SYSTEM_SAA1099);
|
||||
sysChangeOption(i,DIV_SYSTEM_AY8930);
|
||||
sysChangeOption(i,DIV_SYSTEM_LYNX);
|
||||
sysChangeOption(i,DIV_SYSTEM_QSOUND);
|
||||
sysChangeOption(i,DIV_SYSTEM_SWAN);
|
||||
sysChangeOption(i,DIV_SYSTEM_VERA);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
@ -6399,6 +6415,12 @@ FurnaceGUI::FurnaceGUI():
|
|||
0
|
||||
}
|
||||
));
|
||||
cat.systems.push_back(FurnaceGUISysDef(
|
||||
"WonderSwan", {
|
||||
DIV_SYSTEM_SWAN, 64, 0, 0,
|
||||
0
|
||||
}
|
||||
));
|
||||
sysCategories.push_back(cat);
|
||||
|
||||
cat=FurnaceGUISysCategory("Computers");
|
||||
|
|
|
@ -797,7 +797,8 @@ void FurnaceGUI::drawInsEdit() {
|
|||
int asInt[256];
|
||||
float loopIndicator[256];
|
||||
int opCount=4;
|
||||
if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL) opCount=2;
|
||||
if (ins->type==DIV_INS_OPLL) opCount=2;
|
||||
if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2;
|
||||
|
||||
if (ImGui::BeginTabItem("FM")) {
|
||||
if (ImGui::BeginTable("fmDetails",3,ImGuiTableFlags_SizingStretchSame)) {
|
||||
|
@ -817,7 +818,19 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::TableNextColumn();
|
||||
drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
|
||||
break;
|
||||
case DIV_INS_OPL:
|
||||
case DIV_INS_OPL: {
|
||||
bool fourOp=(ins->fm.ops==4);
|
||||
ImGui::TableNextColumn();
|
||||
P(ImGui::SliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable
|
||||
if (ImGui::Checkbox("4-op",&fourOp)) { PARAMETER
|
||||
ins->fm.ops=fourOp?4:2;
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
P(ImGui::SliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); rightClickable
|
||||
ImGui::TableNextColumn();
|
||||
drawAlgorithm(ins->fm.alg&1,FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
|
||||
break;
|
||||
}
|
||||
case DIV_INS_OPLL: {
|
||||
bool dc=ins->fm.fms;
|
||||
bool dm=ins->fm.ams;
|
||||
|
@ -1065,7 +1078,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
if (ImGui::SliderInt("##DT",&detune,-3,3)) { PARAMETER
|
||||
if (ImGui::SliderInt("##DT",&detune,-3,4)) { PARAMETER
|
||||
op.dt=detune+3;
|
||||
} rightClickable
|
||||
ImGui::TableNextColumn();
|
||||
|
@ -1368,6 +1381,10 @@ void FurnaceGUI::drawInsEdit() {
|
|||
if (ins->type==DIV_INS_PCE) {
|
||||
dutyMax=1;
|
||||
}
|
||||
if (ins->type==DIV_INS_SWAN) {
|
||||
dutyLabel="Noise";
|
||||
dutyMax=8;
|
||||
}
|
||||
if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) {
|
||||
dutyMax=0;
|
||||
}
|
||||
|
@ -1791,7 +1808,7 @@ void FurnaceGUI::drawWaveEdit() {
|
|||
DivWavetable* wave=e->song.wave[curWave];
|
||||
ImGui::Text("Width");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("use a width of 32 on Game Boy and PC Engine.\nany other widths will be scaled during playback.");
|
||||
ImGui::SetTooltip("use a width of 32 on Game Boy, PC Engine and WonderSwan.\nany other widths will be scaled during playback.");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(128.0f*dpiScale);
|
||||
|
@ -1805,7 +1822,7 @@ void FurnaceGUI::drawWaveEdit() {
|
|||
ImGui::SameLine();
|
||||
ImGui::Text("Height");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("use a height of:\n- 15 for Game Boy\n- 31 for PC Engine\nany other heights will be scaled during playback.");
|
||||
ImGui::SetTooltip("use a height of:\n- 15 for Game Boy and WonderSwan\n- 31 for PC Engine\nany other heights will be scaled during playback.");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(128.0f*dpiScale);
|
||||
|
|
Loading…
Reference in a new issue