Merge branch 'tildearrow:master' into master

This commit is contained in:
BlastBrothers 2022-04-08 09:44:53 -04:00 committed by GitHub
commit 7850e892d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 512 additions and 140 deletions

View file

@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res
the format versions are:
- 79: Furnace dev79
- 78: Furnace dev78
- 77: Furnace dev77
- 76: Furnace dev76
@ -579,6 +580,19 @@ size | description
--- | **OPZ instrument extra data** (>=77)
1 | fms2
1 | ams2
--- | **wavetable synth data** (>=79)
4 | first wave
4 | second wave
1 | rate divider
1 | effect
| - bit 7: single or dual effect
1 | enabled
1 | global
1 | speed (+1)
1 | parameter 1
1 | parameter 2
1 | parameter 3
1 | parameter 4
```
# wavetable

View file

@ -42,8 +42,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev78"
#define DIV_ENGINE_VERSION 78
#define DIV_VERSION "dev79"
#define DIV_ENGINE_VERSION 79
// for imports
#define DIV_VERSION_MOD 0xff01

View file

@ -69,7 +69,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
}
ds.version=(unsigned char)reader.readC();
logI("module version %d (0x%.2x)\n",ds.version,ds.version);
if (ds.version>0x19) {
if (ds.version>0x1a) {
logE("this version is not supported by Furnace yet!\n");
lastError="this version is not supported by Furnace yet";
delete[] file;
@ -579,6 +579,13 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
} else {
wave->data[j]=reader.readI();
}
wave->data[j]&=wave->max;
}
// #FDS4Bit
if (ds.system[0]==DIV_SYSTEM_NES_FDS && ds.version<0x1a) {
for (int j=0; j<wave->len; j++) {
wave->data[j]*=4;
}
}
ds.wave.push_back(wave);
}
@ -2222,7 +2229,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
SafeWriter* DivEngine::saveDMF(unsigned char version) {
// fail if version is not supported
if (version<24 || version>25) {
if (version<24 || version>26) {
logE("cannot save in this version!\n");
lastError="invalid version to save in! this is a bug!";
return NULL;
@ -2273,6 +2280,12 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
lastError="NES + VRC7 not supported in 1.0/legacy .dmf!";
return NULL;
}
// fail if the system is FDS and version<25
if (version<25 && song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) {
logE("FDS not supported in 1.0/legacy .dmf!\n");
lastError="FDS not supported in 1.0/legacy .dmf!";
return NULL;
}
// fail if the system is Furnace-exclusive
if (!isFlat && systemToFileDMF(song.system[0])==0) {
logE("cannot save Furnace-exclusive system song!\n");
@ -2308,7 +2321,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
sys=DIV_SYSTEM_NES_VRC7;
} else if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) {
w->writeC(systemToFileDMF(DIV_SYSTEM_NES_FDS));
sys=DIV_SYSTEM_NES_VRC7;
sys=DIV_SYSTEM_NES_FDS;
} else {
w->writeC(systemToFileDMF(song.system[0]));
sys=song.system[0];
@ -2481,8 +2494,14 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
w->writeC(song.wave.size());
for (DivWavetable* i: song.wave) {
w->writeI(i->len);
if (sys==DIV_SYSTEM_NES_FDS && version<26) {
for (int j=0; j<i->len; j++) {
w->writeI(i->data[j]>>2);
}
} else {
w->write(i->data,4*i->len);
}
}
for (int i=0; i<getChannelCount(sys); i++) {
w->writeC(song.pat[i].effectRows);

View file

@ -467,6 +467,19 @@ void DivInstrument::putInsData(SafeWriter* w) {
// OPZ
w->writeC(fm.fms2);
w->writeC(fm.ams2);
// wave synth
w->writeI(ws.wave1);
w->writeI(ws.wave2);
w->writeC(ws.rateDivider);
w->writeC(ws.effect);
w->writeC(ws.enabled);
w->writeC(ws.global);
w->writeC(ws.speed);
w->writeC(ws.param1);
w->writeC(ws.param2);
w->writeC(ws.param3);
w->writeC(ws.param4);
}
DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
@ -895,6 +908,21 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
fm.fms2=reader.readC();
fm.ams2=reader.readC();
}
// wave synth
if (version>=79) {
ws.wave1=reader.readI();
ws.wave2=reader.readI();
ws.rateDivider=reader.readC();
ws.effect=reader.readC();
ws.enabled=reader.readC();
ws.global=reader.readC();
ws.speed=reader.readC();
ws.param1=reader.readC();
ws.param2=reader.readC();
ws.param3=reader.readC();
ws.param4=reader.readC();
}
return DIV_DATA_SUCCESS;
}

View file

@ -485,6 +485,9 @@ enum DivWaveSynthEffects {
DIV_WS_SUBTRACT,
DIV_WS_AVERAGE,
DIV_WS_PHASE,
DIV_WS_SINGLE_MAX,
// two waveform effects
DIV_WS_NONE_DUAL=128,
DIV_WS_WIPE,
@ -493,25 +496,25 @@ enum DivWaveSynthEffects {
DIV_WS_OVERLAY,
DIV_WS_NEGATIVE_OVERLAY,
DIV_WS_PHASE_DUAL,
DIV_WS_DUAL_MAX
};
struct DivInstrumentWaveSynth {
int wave1, wave2;
unsigned char rateDivider, width, height;
DivWaveSynthEffects effect;
unsigned char rateDivider;
unsigned char effect;
bool oneShot, enabled, global;
unsigned char speed, param1, param2, param3, param4;
DivInstrumentWaveSynth():
wave1(0),
wave2(0),
rateDivider(1),
width(32),
height(32),
effect(DIV_WS_NONE),
oneShot(false),
enabled(false),
global(false),
speed(1),
speed(0),
param1(0),
param2(0),
param3(0),

View file

@ -71,17 +71,13 @@ void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_
}
void DivPlatformBubSysWSG::updateWave(int ch) {
DivWavetable* wt=parent->getWave(chan[ch].wave);
//DivWavetable* wt=parent->getWave(chan[ch].wave);
for (int i=0; i<32; i++) {
if (wt->max>0 && wt->len>0) {
int data=wt->data[i*wt->len/32]*15/wt->max; // 4 bit PROM at bubble system
if (data<0) data=0;
if (data>15) data=15;
chan[ch].waveROM[i]=data-8; // convert to signed
}
// convert to signed
chan[ch].waveROM[i]=chan[ch].ws.output[i]-8;
}
if (chan[ch].active) {
rWrite(2+ch,(chan[ch].wave<<5)|chan[ch].outVol);
rWrite(2+ch,(ch<<5)|chan[ch].outVol);
}
}
@ -108,12 +104,17 @@ void DivPlatformBubSysWSG::tick() {
}
}
if (chan[i].std.hadWave) {
if (chan[i].wave!=chan[i].std.wave) {
if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave;
updateWave(i);
chan[i].ws.changeWave1(chan[i].wave);
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
if (chan[i].active) {
if (chan[i].ws.tick()) {
updateWave(i);
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins);
chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
@ -123,10 +124,7 @@ void DivPlatformBubSysWSG::tick() {
rWrite(i,chan[i].freq);
k005289->update(i);
if (chan[i].keyOn) {
if (chan[i].wave<0) {
chan[i].wave=0;
updateWave(i);
}
// ???
}
if (chan[i].keyOff) {
rWrite(2+i,(chan[i].wave<<5)|0);
@ -151,6 +149,12 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) {
chan[c.chan].keyOn=true;
rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].vol);
chan[c.chan].std.init(ins);
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,15,chan[c.chan].insChanged);
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
@ -188,7 +192,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) {
break;
case DIV_CMD_WAVE:
chan[c.chan].wave=c.value;
updateWave(c.chan);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
chan[c.chan].keyOn=true;
break;
case DIV_CMD_NOTE_PORTA: {
@ -270,6 +274,8 @@ void DivPlatformBubSysWSG::reset() {
memset(regPool,0,4*2);
for (int i=0; i<2; i++) {
chan[i]=DivPlatformBubSysWSG::Channel();
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,32,15,false);
}
if (dumpWrites) {
addWrite(0xffffffff,0);
@ -288,6 +294,7 @@ bool DivPlatformBubSysWSG::keyOffAffectsArp(int ch) {
void DivPlatformBubSysWSG::notifyWaveChange(int wave) {
for (int i=0; i<2; i++) {
if (chan[i].wave==wave) {
chan[i].ws.changeWave1(chan[i].wave);
updateWave(i);
}
}

View file

@ -23,6 +23,7 @@
#include "../dispatch.h"
#include <queue>
#include "../macroInt.h"
#include "../waveSynth.h"
#include "sound/k005289/k005289.hpp"
class DivPlatformBubSysWSG: public DivDispatch {
@ -33,6 +34,7 @@ class DivPlatformBubSysWSG: public DivDispatch {
signed char vol, outVol, wave;
signed char waveROM[32] = {0}; // 4 bit PROM per channel on bubble system
DivMacroInt std;
DivWaveSynth ws;
Channel():
freq(0),
baseFreq(0),

View file

@ -89,18 +89,10 @@ void DivPlatformFDS::acquire(short* bufL, short* bufR, size_t start, size_t len)
}
void DivPlatformFDS::updateWave() {
DivWavetable* wt=parent->getWave(chan[0].wave);
// TODO: master volume
rWrite(0x4089,0x80);
for (int i=0; i<64; i++) {
if (wt->max<1 || wt->len<1) {
rWrite(0x4040+i,0);
} else {
int data=wt->data[i*wt->len/64]*63/wt->max;
if (data<0) data=0;
if (data>63) data=63;
rWrite(0x4040+i,data);
}
rWrite(0x4040+i,ws.output[i]);
}
rWrite(0x4089,0);
}
@ -157,12 +149,18 @@ void DivPlatformFDS::tick() {
}
}*/
if (chan[i].std.hadWave) {
if (chan[i].wave!=chan[i].std.wave) {
if (chan[i].wave!=chan[i].std.wave || ws.activeChanged()) {
chan[i].wave=chan[i].std.wave;
updateWave();
ws.changeWave1(chan[i].wave);
//if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
if (chan[i].active) {
if (ws.tick()) {
updateWave();
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
if (chan[i].std.hadEx1) { // mod depth
chan[i].modOn=chan[i].std.ex1;
chan[i].modDepth=chan[i].std.ex1;
@ -190,10 +188,7 @@ void DivPlatformFDS::tick() {
if (chan[i].freq>4095) chan[i].freq=4095;
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].keyOn) {
if (chan[i].wave<0) {
chan[i].wave=0;
updateWave();
}
// ???
}
if (chan[i].keyOff) {
rWrite(0x4080,0x80);
@ -209,20 +204,20 @@ void DivPlatformFDS::tick() {
int DivPlatformFDS::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON:
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
if (chan[c.chan].insChanged) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
if (ins->fds.initModTableWithFirstWave) { // compatible
if (chan[c.chan].wave==-1) {
DivWavetable* wt=parent->getWave(0);
for (int i=0; i<32; i++) {
if (wt->max<1 || wt->len<1) {
rWrite(0x4040+i,0);
chan[c.chan].modTable[i]=0;
} else {
int data=wt->data[i*MIN(32,wt->len)/32]*7/wt->max;
if (data<0) data=0;
@ -253,9 +248,16 @@ int DivPlatformFDS::dispatch(DivCommand c) {
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
chan[c.chan].std.init(ins);
if (chan[c.chan].wave<0) {
chan[c.chan].wave=0;
ws.changeWave1(chan[c.chan].wave);
}
ws.init(ins,64,63,chan[c.chan].insChanged);
rWrite(0x4080,0x80|chan[c.chan].vol);
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
@ -268,6 +270,7 @@ int DivPlatformFDS::dispatch(DivCommand c) {
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
chan[c.chan].insChanged=true;
}
break;
case DIV_CMD_VOLUME:
@ -289,7 +292,7 @@ int DivPlatformFDS::dispatch(DivCommand c) {
case DIV_CMD_WAVE:
if (chan[c.chan].wave!=c.value) {
chan[c.chan].wave=c.value;
updateWave();
ws.changeWave1(chan[c.chan].wave);
}
break;
case DIV_CMD_FDS_MOD_DEPTH:
@ -406,6 +409,8 @@ void DivPlatformFDS::reset() {
for (int i=0; i<1; i++) {
chan[i]=DivPlatformFDS::Channel();
}
ws.setEngine(parent);
ws.init(NULL,64,63,false);
if (dumpWrites) {
addWrite(0xffffffff,0);
}

View file

@ -22,6 +22,7 @@
#include "../dispatch.h"
#include "../macroInt.h"
#include "../waveSynth.h"
class DivPlatformFDS: public DivDispatch {
struct Channel {
@ -59,6 +60,7 @@ class DivPlatformFDS: public DivDispatch {
};
Channel chan[1];
bool isMuted[1];
DivWaveSynth ws;
unsigned char apuType;
struct _fds* fds;
unsigned char regPool[128];

View file

@ -91,21 +91,12 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len)
}
void DivPlatformGB::updateWave() {
DivWavetable* wt=parent->getWave(chan[2].wave);
rWrite(0x1a,0);
for (int i=0; i<16; i++) {
if (wt->max<1 || wt->len<1) {
rWrite(0x30+i,0);
} else {
int nibble1=15-((wt->data[(i*2)*wt->len/32]*15)/wt->max);
int nibble2=15-((wt->data[(1+i*2)*wt->len/32]*15)/wt->max);
if (nibble1<0) nibble1=0;
if (nibble1>15) nibble1=15;
if (nibble2<0) nibble2=0;
if (nibble2>15) nibble2=15;
int nibble1=15-ws.output[i<<1];
int nibble2=15-ws.output[1+(i<<1)];
rWrite(0x30+i,(nibble1<<4)|nibble2);
}
}
}
static unsigned char chanMuteMask[4]={
@ -194,10 +185,16 @@ void DivPlatformGB::tick() {
}
}
}
if (chan[i].std.hadWave) {
if (chan[i].wave!=chan[i].std.wave) {
if (i==2 && chan[i].std.hadWave) {
if (chan[i].wave!=chan[i].std.wave || ws.activeChanged()) {
chan[i].wave=chan[i].std.wave;
ws.changeWave1(chan[i].wave);
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
if (i==2) {
if (chan[i].active) {
if (ws.tick()) {
updateWave();
if (!chan[i].keyOff) chan[i].keyOn=true;
}
@ -222,10 +219,6 @@ void DivPlatformGB::tick() {
}
if (chan[i].keyOn) {
if (i==2) { // wave
if (chan[i].wave<0) {
chan[i].wave=0;
updateWave();
}
rWrite(16+i*5,0x80);
rWrite(16+i*5+2,gbVolMap[chan[i].vol]);
} else {
@ -261,7 +254,8 @@ void DivPlatformGB::muteChannel(int ch, bool mute) {
int DivPlatformGB::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON:
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
if (c.value!=DIV_NOTE_NULL) {
if (c.chan==3) { // noise
chan[c.chan].baseFreq=c.value;
@ -273,8 +267,17 @@ int DivPlatformGB::dispatch(DivCommand c) {
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
chan[c.chan].std.init(ins);
if (c.chan==2) {
if (chan[c.chan].wave<0) {
chan[c.chan].wave=0;
ws.changeWave1(chan[c.chan].wave);
}
ws.init(ins,32,15,chan[c.chan].insChanged);
}
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
@ -287,6 +290,7 @@ int DivPlatformGB::dispatch(DivCommand c) {
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
chan[c.chan].insChanged=true;
if (c.chan!=2) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
chan[c.chan].vol=ins->gb.envVol;
@ -312,7 +316,7 @@ int DivPlatformGB::dispatch(DivCommand c) {
case DIV_CMD_WAVE:
if (c.chan!=2) break;
chan[c.chan].wave=c.value;
updateWave();
ws.changeWave1(chan[c.chan].wave);
chan[c.chan].keyOn=true;
break;
case DIV_CMD_NOTE_PORTA: {
@ -415,6 +419,8 @@ void DivPlatformGB::reset() {
for (int i=0; i<4; i++) {
chan[i]=DivPlatformGB::Channel();
}
ws.setEngine(parent);
ws.init(NULL,32,15,false);
if (dumpWrites) {
addWrite(0xffffffff,0);
}
@ -445,6 +451,7 @@ void DivPlatformGB::notifyInsChange(int ins) {
void DivPlatformGB::notifyWaveChange(int wave) {
if (chan[2].wave==wave) {
ws.changeWave1(wave);
updateWave();
if (!chan[2].keyOff) chan[2].keyOn=true;
}

View file

@ -22,6 +22,7 @@
#include "../dispatch.h"
#include "../macroInt.h"
#include "../waveSynth.h"
#include "sound/gb/gb.h"
class DivPlatformGB: public DivDispatch {
@ -53,6 +54,7 @@ class DivPlatformGB: public DivDispatch {
Channel chan[4];
bool isMuted[4];
unsigned char lastPan;
DivWaveSynth ws;
GB_gameboy_t* gb;
unsigned char regPool[128];

View file

@ -947,7 +947,7 @@ void DivPlatformOPL::reset() {
drumVol[3]=0;
drumVol[4]=0;
if (oplType==1) { // disable waveforms
if (oplType==2) { // enable OPL2 waveforms
immWrite(0x01,0x20);
}

View file

@ -131,18 +131,10 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
}
void DivPlatformPCE::updateWave(int ch) {
DivWavetable* wt=parent->getWave(chan[ch].wave);
chWrite(ch,0x04,0x5f);
chWrite(ch,0x04,0x1f);
for (int i=0; i<32; i++) {
if (wt->max<1 || wt->len<1) {
chWrite(ch,0x06,0);
} else {
int data=wt->data[i*wt->len/32]*31/wt->max;
if (data<0) data=0;
if (data>31) data=31;
chWrite(ch,0x06,data);
}
chWrite(ch,0x06,chan[ch].ws.output[i]);
}
if (chan[ch].active) {
chWrite(ch,0x04,0x80|chan[ch].outVol);
@ -198,12 +190,17 @@ void DivPlatformPCE::tick() {
}
}
if (chan[i].std.hadWave && !chan[i].pcm) {
if (chan[i].wave!=chan[i].std.wave) {
if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave;
updateWave(i);
chan[i].ws.changeWave1(chan[i].wave);
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
if (chan[i].active) {
if (chan[i].ws.tick()) {
updateWave(i);
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
@ -224,10 +221,6 @@ void DivPlatformPCE::tick() {
chWrite(i,0x02,chan[i].freq&0xff);
chWrite(i,0x03,chan[i].freq>>8);
if (chan[i].keyOn) {
if (chan[i].wave<0) {
chan[i].wave=0;
updateWave(i);
}
//rWrite(16+i*5,0x80);
//chWrite(i,0x04,0x80|chan[i].vol);
}
@ -310,6 +303,12 @@ int DivPlatformPCE::dispatch(DivCommand c) {
chan[c.chan].keyOn=true;
chWrite(c.chan,0x04,0x80|chan[c.chan].vol);
chan[c.chan].std.init(ins);
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,31,chan[c.chan].insChanged);
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
@ -327,6 +326,7 @@ int DivPlatformPCE::dispatch(DivCommand c) {
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
chan[c.chan].insChanged=true;
}
break;
case DIV_CMD_VOLUME:
@ -350,7 +350,7 @@ int DivPlatformPCE::dispatch(DivCommand c) {
break;
case DIV_CMD_WAVE:
chan[c.chan].wave=c.value;
updateWave(c.chan);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
chan[c.chan].keyOn=true;
break;
case DIV_CMD_PCE_LFO_MODE:
@ -462,6 +462,8 @@ void DivPlatformPCE::reset() {
memset(regPool,0,128);
for (int i=0; i<6; i++) {
chan[i]=DivPlatformPCE::Channel();
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,32,31,false);
}
if (dumpWrites) {
addWrite(0xffffffff,0);
@ -499,6 +501,7 @@ bool DivPlatformPCE::keyOffAffectsArp(int ch) {
void DivPlatformPCE::notifyWaveChange(int wave) {
for (int i=0; i<6; i++) {
if (chan[i].wave==wave) {
chan[i].ws.changeWave1(wave);
updateWave(i);
}
}

View file

@ -23,6 +23,7 @@
#include "../dispatch.h"
#include <queue>
#include "../macroInt.h"
#include "../waveSynth.h"
#include "sound/pce_psg.h"
class DivPlatformPCE: public DivDispatch {
@ -35,6 +36,7 @@ class DivPlatformPCE: public DivDispatch {
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac;
signed char vol, outVol, wave;
DivMacroInt std;
DivWaveSynth ws;
Channel():
freq(0),
baseFreq(0),

View file

@ -111,23 +111,12 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len
}
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++) {
int nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max;
int nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max;
if (nibble1<0) nibble1=0;
if (nibble1>15) nibble1=15;
if (nibble2<0) nibble2=0;
if (nibble2>15) nibble2=15;
int nibble1=chan[ch].ws.output[i<<1];
int nibble2=chan[ch].ws.output[1+(i<<1)];
rWrite(addr+i,nibble1|(nibble2<<4));
}
}
}
void DivPlatformSwan::calcAndWriteOutVol(int ch, int env) {
@ -179,13 +168,16 @@ void DivPlatformSwan::tick() {
}
}
if (chan[i].std.hadWave && !(i==1 && pcm)) {
if (chan[i].wave!=chan[i].std.wave) {
if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave;
updateWave(i);
chan[i].ws.changeWave1(chan[i].wave);
}
}
if (chan[i].active) {
sndCtrl|=(1<<i);
if (chan[i].ws.tick()) {
updateWave(i);
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
@ -211,10 +203,6 @@ void DivPlatformSwan::tick() {
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) {
@ -300,6 +288,12 @@ int DivPlatformSwan::dispatch(DivCommand c) {
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].std.init(ins);
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,15,chan[c.chan].insChanged);
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
@ -319,6 +313,7 @@ int DivPlatformSwan::dispatch(DivCommand c) {
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
chan[c.chan].insChanged=true;
}
break;
case DIV_CMD_VOLUME:
@ -338,7 +333,7 @@ int DivPlatformSwan::dispatch(DivCommand c) {
break;
case DIV_CMD_WAVE:
chan[c.chan].wave=c.value;
updateWave(c.chan);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
chan[c.chan].keyOn=true;
break;
case DIV_CMD_WS_SWEEP_TIME:
@ -458,6 +453,8 @@ void DivPlatformSwan::reset() {
chan[i]=Channel();
chan[i].vol=15;
chan[i].pan=0xff;
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,32,15,false);
rWrite(0x08+i,0xff);
}
if (dumpWrites) {
@ -484,6 +481,7 @@ bool DivPlatformSwan::isStereo() {
void DivPlatformSwan::notifyWaveChange(int wave) {
for (int i=0; i<4; i++) {
if (chan[i].wave==wave) {
chan[i].ws.changeWave1(wave);
updateWave(i);
}
}

View file

@ -22,6 +22,7 @@
#include "../dispatch.h"
#include "../macroInt.h"
#include "../waveSynth.h"
#include "sound/swan.h"
#include <queue>
@ -32,6 +33,7 @@ class DivPlatformSwan: public DivDispatch {
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
int vol, outVol, wave;
DivMacroInt std;
DivWaveSynth ws;
Channel():
freq(0),
baseFreq(0),

View file

@ -274,20 +274,13 @@ double DivPlatformX1_010::NoteX1_010(int ch, int note) {
}
void DivPlatformX1_010::updateWave(int ch) {
DivWavetable* wt=parent->getWave(chan[ch].wave);
if (chan[ch].active) {
chan[ch].waveBank ^= 1;
chan[ch].waveBank^=1;
}
for (int i=0; i<128; i++) {
if (wt->max<1 || wt->len<1) {
waveWrite(ch,i,0);
} else {
int data=wt->data[i*wt->len/128]*255/wt->max;
if (data<0) data=0;
if (data>255) data=255;
int data=chan[ch].ws.output[i];
waveWrite(ch,i,data);
}
}
if (!chan[ch].pcm) {
chWrite(ch,1,(chan[ch].waveBank<<4)|(ch&0xf));
}
@ -371,10 +364,10 @@ void DivPlatformX1_010::tick() {
}
}
if (chan[i].std.hadWave && !chan[i].pcm) {
if (chan[i].wave!=chan[i].std.wave) {
if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave;
if (!chan[i].pcm) {
updateWave(i);
chan[i].ws.changeWave1(chan[i].wave);
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
@ -458,6 +451,11 @@ void DivPlatformX1_010::tick() {
if (!chan[i].std.willEx3) chan[i].autoEnvNum=1;
}
}
if (chan[i].active) {
if (chan[i].ws.tick()) {
updateWave(i);
}
}
if (chan[i].envChanged) {
chan[i].lvol=isMuted[i]?0:(((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15);
chan[i].rvol=isMuted[i]?0:(((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15);
@ -575,6 +573,12 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
chan[c.chan].keyOn=true;
chan[c.chan].envChanged=true;
chan[c.chan].std.init(ins);
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,128,255,chan[c.chan].insChanged);
chan[c.chan].insChanged=false;
refreshControl(c.chan);
break;
}
@ -591,6 +595,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
chan[c.chan].insChanged=true;
}
break;
case DIV_CMD_VOLUME:
@ -618,7 +623,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
break;
case DIV_CMD_WAVE:
chan[c.chan].wave=c.value;
updateWave(c.chan);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
chan[c.chan].keyOn=true;
break;
case DIV_CMD_X1_010_ENVELOPE_SHAPE:
@ -813,6 +818,8 @@ void DivPlatformX1_010::reset() {
for (int i=0; i<16; i++) {
chan[i]=DivPlatformX1_010::Channel();
chan[i].reset();
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,128,255,false);
}
x1_010->reset();
sampleBank=0;
@ -833,6 +840,7 @@ bool DivPlatformX1_010::keyOffAffectsArp(int ch) {
void DivPlatformX1_010::notifyWaveChange(int wave) {
for (int i=0; i<16; i++) {
if (chan[i].wave==wave) {
chan[i].ws.changeWave1(wave);
updateWave(i);
}
}

View file

@ -23,6 +23,7 @@
#include "../dispatch.h"
#include "../engine.h"
#include "../macroInt.h"
#include "../waveSynth.h"
#include "sound/x1_010/x1_010.hpp"
class DivX1_010Interface: public x1_010_mem_intf {
@ -86,6 +87,7 @@ class DivPlatformX1_010: public DivDispatch {
unsigned char waveBank;
Envelope env;
DivMacroInt std;
DivWaveSynth ws;
void reset() {
freq = baseFreq = pitch = note = 0;
wave = sample = ins = -1;

View file

@ -102,6 +102,8 @@ struct DivSong {
// version number used for saving the song.
// Furnace will save using the latest possible version,
// known version numbers:
// - 26: v1.1.3
// - changes height of FDS wave to 6-bit (it was 4-bit before)
// - 25: v1.1
// - adds pattern names (in a rather odd way)
// - introduces SMS+OPLL system

View file

@ -1,5 +1,99 @@
#include "waveSynth.h"
#include "engine.h"
#include "instrument.h"
bool DivWaveSynth::tick() {
bool DivWaveSynth::activeChanged() {
if (activeChangedB) {
activeChangedB=false;
return true;
}
return false;
}
bool DivWaveSynth::tick() {
bool updated=first;
first=false;
if (!state.enabled) return updated;
if (--divCounter<=0) {
// run effect
switch (state.effect) {
case DIV_WS_INVERT:
for (int i=0; i<=state.speed; i++) {
output[pos]=height-output[pos];
if (++pos>=width) pos=0;
}
updated=true;
break;
}
divCounter=state.rateDivider;
}
return updated;
}
void DivWaveSynth::changeWave1(int num) {
DivWavetable* w1=e->getWave(num);
for (int i=0; i<width; i++) {
if (w1->max<1 || w1->len<1) {
wave1[i]=0;
output[i]=0;
} else {
int data=w1->data[i*w1->len/width]*height/w1->max;
if (data<0) data=0;
if (data>height) data=height;
wave1[i]=data;
output[i]=data;
}
}
first=true;
}
void DivWaveSynth::changeWave2(int num) {
DivWavetable* w2=e->getWave(num);
for (int i=0; i<width; i++) {
if (w2->max<1 || w2->len<1) {
wave2[i]=0;
} else {
int data=w2->data[i*w2->len/width]*height/w2->max;
if (data<0) data=0;
if (data>height) data=height;
wave2[i]=data;
}
}
first=true;
}
void DivWaveSynth::setEngine(DivEngine* engine) {
e=engine;
}
void DivWaveSynth::init(DivInstrument* which, int w, int h, bool insChanged) {
width=w;
height=h;
if (width<0) width=0;
if (width>256) width=256;
if (e==NULL) return;
if (which==NULL) {
if (state.enabled) activeChangedB=true;
state=DivInstrumentWaveSynth();
return;
}
if (!which->ws.enabled) {
if (state.enabled) activeChangedB=true;
state=DivInstrumentWaveSynth();
return;
} else {
if (!state.enabled) activeChangedB=true;
}
state=which->ws;
if (insChanged || !state.global) {
pos=0;
stage=0;
divCounter=1+state.rateDivider;
first=true;
changeWave1(state.wave1);
changeWave2(state.wave2);
}
}

View file

@ -23,22 +23,60 @@
#include "instrument.h"
#include "wavetable.h"
class DivEngine;
class DivWaveSynth {
DivInstrument* ins;
int pos, stage, divCounter;
int output[256];
DivEngine* e;
DivInstrumentWaveSynth state;
int pos, stage, divCounter, width, height;
bool first, activeChangedB;
unsigned char wave1[256];
unsigned char wave2[256];
public:
/**
* the output.
*/
int output[256];
/**
* check whether the "active" status has changed.
* @return truth.
*/
bool activeChanged();
/**
* tick this DivWaveSynth.
* @return whether the wave has changed.
*/
bool tick();
void init(DivInstrument* ins);
/**
* change the first wave.
* @param num wavetable number.
*/
void changeWave1(int num);
/**
* change the second wave.
* @param num wavetable number.
*/
void changeWave2(int num);
/**
* initialize this DivWaveSynth.
* @param which the instrument.
* @param width the system's wave width.
* @param height the system's wave height.
* @param insChanged whether the instrument has changed.
*/
void init(DivInstrument* which, int width, int height, bool insChanged=false);
void setEngine(DivEngine* engine);
DivWaveSynth():
ins(NULL),
e(NULL),
pos(0),
stage(0),
divCounter(0) {
divCounter(0),
width(32),
height(31),
first(false),
activeChangedB(false) {
memset(wave1,0,sizeof(int)*256);
memset(wave2,0,sizeof(int)*256);
memset(output,0,sizeof(int)*256);
}
};

View file

@ -1015,6 +1015,11 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) {
if (!curNibble) {
if (!settings.effectCursorDir) {
editAdvance();
} else {
if (settings.effectCursorDir==2) {
if (++cursor.xFine>=(3+(e->song.pat[cursor.xCoarse].effectRows*2))) {
cursor.xFine=3;
}
} else {
if (cursor.xFine&1) {
cursor.xFine++;
@ -1026,6 +1031,7 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) {
}
}
}
}
}
void FurnaceGUI::keyDown(SDL_Event& ev) {
@ -1300,8 +1306,8 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Save File",
{"Furnace song", "*.fur",
"DefleMask 1.1 module", "*.dmf"},
"Furnace song{.fur},DefleMask 1.1 module{.dmf}",
"DefleMask 1.1.3 module", "*.dmf"},
"Furnace song{.fur},DefleMask 1.1.3 module{.dmf}",
workingDirSong,
dpiScale
);
@ -2714,7 +2720,7 @@ bool FurnaceGUI::loop() {
showError(fmt::sprintf("Error while saving file! (%s)",lastError));
}
} else {
if (save(copyOfName,25)>0) {
if (save(copyOfName,26)>0) {
showError(fmt::sprintf("Error while saving file! (%s)",lastError));
}
}

View file

@ -157,6 +157,25 @@ const int orderedOps[4]={
0, 2, 1, 3
};
const char* singleWSEffects[6]={
"None",
"Invert",
"Add",
"Subtract",
"Average",
"Phase",
};
const char* dualWSEffects[7]={
"None (dual)",
"Wipe",
"Fade",
"Wipe (ping-pong)",
"Overlay",
"Negative Overlay",
"Phase (dual)",
};
String macroHoverNote(int id, float val) {
if (val<-60 || val>=120) return "???";
return fmt::sprintf("%d: %s",id,noteNames[(int)val+60]);
@ -2301,6 +2320,109 @@ void FurnaceGUI::drawInsEdit() {
}
ImGui::EndTabItem();
}
if (ins->type==DIV_INS_GB ||
ins->type==DIV_INS_AMIGA ||
ins->type==DIV_INS_X1_010 ||
ins->type==DIV_INS_N163 ||
ins->type==DIV_INS_FDS ||
ins->type==DIV_INS_SWAN ||
ins->type==DIV_INS_PCE ||
ins->type==DIV_INS_SCC) {
if (ImGui::BeginTabItem("Wavetable")) {
ImGui::Checkbox("Enable synthesizer",&ins->ws.enabled);
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ins->ws.effect&0x80) {
if ((ins->ws.effect&0x7f)>=DIV_WS_DUAL_MAX) {
ins->ws.effect=0;
}
} else {
if ((ins->ws.effect&0x7f)>=DIV_WS_SINGLE_MAX) {
ins->ws.effect=0;
}
}
if (ImGui::BeginCombo("##WSEffect",(ins->ws.effect&0x80)?dualWSEffects[ins->ws.effect&0x7f]:singleWSEffects[ins->ws.effect&0x7f])) {
ImGui::Text("Single-waveform");
ImGui::Indent();
for (int i=0; i<DIV_WS_SINGLE_MAX; i++) {
if (ImGui::Selectable(singleWSEffects[i])) {
ins->ws.effect=i;
}
}
ImGui::Unindent();
ImGui::Text("Dual-waveform");
ImGui::Indent();
for (int i=129; i<DIV_WS_DUAL_MAX; i++) {
if (ImGui::Selectable(dualWSEffects[i-128])) {
ins->ws.effect=i;
}
}
ImGui::Unindent();
ImGui::EndCombo();
}
if (ImGui::BeginTable("WSPreview",2)) {
DivWavetable* wave1=e->getWave(ins->ws.wave1);
DivWavetable* wave2=e->getWave(ins->ws.wave2);
float wavePreview1[256];
float wavePreview2[256];
for (int i=0; i<wave1->len; i++) {
if (wave1->data[i]>wave1->max) {
wavePreview1[i]=wave1->max;
} else {
wavePreview1[i]=wave1->data[i];
}
}
for (int i=0; i<wave2->len; i++) {
if (wave2->data[i]>wave2->max) {
wavePreview2[i]=wave2->max;
} else {
wavePreview2[i]=wave2->data[i];
}
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImVec2 size1=ImVec2(ImGui::GetContentRegionAvail().x,64.0f*dpiScale);
PlotNoLerp("##WaveformP1",wavePreview1,wave1->len+1,0,NULL,0,wave1->max,size1);
ImGui::TableNextColumn();
ImVec2 size2=ImVec2(ImGui::GetContentRegionAvail().x,64.0f*dpiScale);
PlotNoLerp("##WaveformP2",wavePreview2,wave2->len+1,0,NULL,0,wave2->max,size2);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Wave 1");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##SelWave1",&ins->ws.wave1,1,4)) {
if (ins->ws.wave1<0) ins->ws.wave1=0;
if (ins->ws.wave1>=(int)e->song.wave.size()) ins->ws.wave1=e->song.wave.size()-1;
}
ImGui::TableNextColumn();
ImGui::Text("Wave 2");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##SelWave2",&ins->ws.wave2,1,4)) {
if (ins->ws.wave2<0) ins->ws.wave2=0;
if (ins->ws.wave2>=(int)e->song.wave.size()) ins->ws.wave2=e->song.wave.size()-1;
}
ImGui::EndTable();
}
ImGui::InputScalar("Update Rate",ImGuiDataType_U8,&ins->ws.rateDivider,&_ONE,&_SEVEN);
int speed=ins->ws.speed+1;
if (ImGui::InputInt("Speed",&speed,1,16)) {
if (speed<1) speed=1;
if (speed>256) speed=256;
ins->ws.speed=speed-1;
}
ImGui::InputScalar("Amount",ImGuiDataType_U8,&ins->ws.param1,&_ONE,&_SEVEN);
ImGui::Checkbox("Global",&ins->ws.global);
ImGui::EndTabItem();
}
}
if (ImGui::BeginTabItem("Macros")) {
float asFloat[256];
int asInt[256];

View file

@ -250,11 +250,6 @@ void FurnaceGUI::drawSettings() {
settings.stepOnInsert=stepOnInsertB;
}
bool effectCursorDirB=settings.effectCursorDir;
if (ImGui::Checkbox("Move cursor to effect value on effect input",&effectCursorDirB)) {
settings.effectCursorDir=effectCursorDirB;
}
bool cursorPastePosB=settings.cursorPastePos;
if (ImGui::Checkbox("Move cursor to end of clipboard content when pasting",&cursorPastePosB)) {
settings.cursorPastePos=cursorPastePosB;
@ -315,6 +310,17 @@ void FurnaceGUI::drawSettings() {
settings.scrollStep=1;
}
ImGui::Text("Effect input cursor behavior:");
if (ImGui::RadioButton("Move down##eicb0",settings.effectCursorDir==0)) {
settings.effectCursorDir=0;
}
if (ImGui::RadioButton("Move to effect value (otherwise move down)##eicb1",settings.effectCursorDir==1)) {
settings.effectCursorDir=1;
}
if (ImGui::RadioButton("Move to effect value/next effect and wrap around##eicb2",settings.effectCursorDir==2)) {
settings.effectCursorDir=2;
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Audio/MIDI")) {
@ -1503,7 +1509,7 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.loadJapanese,0,1);
clampSetting(settings.fmLayout,0,3);
clampSetting(settings.susPosition,0,1);
clampSetting(settings.effectCursorDir,0,1);
clampSetting(settings.effectCursorDir,0,2);
clampSetting(settings.cursorPastePos,0,1);
clampSetting(settings.titleBarInfo,0,3);
clampSetting(settings.titleBarSys,0,1);