dev82 - amiga wavetables

closes #16 (yay!)
This commit is contained in:
tildearrow 2022-04-12 01:19:00 -05:00
parent 014e86d3d1
commit 24209c7853
8 changed files with 119 additions and 47 deletions

View File

@ -6,8 +6,10 @@ in this very computer music trackers were born...
# effects # effects
- `10xx`: toggle low-pass filter. `0` turns it off and `1` turns it on. - `10xx`: change wave.
- only works when "Mode" is set to "Wavetable" in the instrument.
- `11xx`: toggle amplitude modulation with the next channel. - `11xx`: toggle amplitude modulation with the next channel.
- does not work on the last channel. - does not work on the last channel.
- `12xx`: toggle period (frequency) modulation with the next channel. - `12xx`: toggle period (frequency) modulation with the next channel.
- does not work on the last channel. - does not work on the last channel.
- `13xx`: toggle low-pass filter. `0` turns it off and `1` turns it on.

View File

@ -29,6 +29,8 @@ furthermore, an `or reserved` indicates this field is always present, but is res
the format versions are: the format versions are:
- 82: Furnace dev82
- 81: Furnace dev81
- 80: Furnace dev80 - 80: Furnace dev80
- 79: Furnace dev79 - 79: Furnace dev79
- 78: Furnace dev78 - 78: Furnace dev78
@ -342,7 +344,11 @@ size | description
1 | filter macro is absolute 1 | filter macro is absolute
--- | **Amiga instrument data** --- | **Amiga instrument data**
2 | initial sample 2 | initial sample
14 | reserved 1 | mode (>=82) or reserved
| - 0: sample
| - 1: wavetable
1 | wavetable length (-1) (>=82) or reserved
12 | reserved
--- | **standard instrument data** --- | **standard instrument data**
4 | volume macro length 4 | volume macro length
4 | arp macro length 4 | arp macro length

View File

@ -42,8 +42,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false; #define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev81" #define DIV_VERSION "dev82"
#define DIV_ENGINE_VERSION 81 #define DIV_ENGINE_VERSION 82
// for imports // for imports
#define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_MOD 0xff01

View File

@ -106,7 +106,9 @@ void DivInstrument::putInsData(SafeWriter* w) {
// Amiga // Amiga
w->writeS(amiga.initSample); w->writeS(amiga.initSample);
for (int j=0; j<14; j++) { // reserved w->writeC(amiga.useWave);
w->writeC(amiga.waveLen);
for (int j=0; j<12; j++) { // reserved
w->writeC(0); w->writeC(0);
} }
@ -571,8 +573,15 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
// Amiga // Amiga
amiga.initSample=reader.readS(); amiga.initSample=reader.readS();
if (version>=82) {
amiga.useWave=reader.readC();
amiga.waveLen=(unsigned char)reader.readC();
} else {
reader.readC();
reader.readC();
}
// reserved // reserved
for (int k=0; k<14; k++) reader.readC(); for (int k=0; k<12; k++) reader.readC();
// standard // standard
std.volMacro.len=reader.readI(); std.volMacro.len=reader.readI();

View File

@ -293,12 +293,16 @@ struct DivInstrumentC64 {
struct DivInstrumentAmiga { struct DivInstrumentAmiga {
short initSample; short initSample;
bool useNoteMap; bool useNoteMap;
bool useWave;
unsigned char waveLen;
int noteFreq[120]; int noteFreq[120];
short noteMap[120]; short noteMap[120];
DivInstrumentAmiga(): DivInstrumentAmiga():
initSample(0), initSample(0),
useNoteMap(false) { useNoteMap(false),
useWave(false),
waveLen(31) {
memset(noteMap,-1,120*sizeof(short)); memset(noteMap,-1,120*sizeof(short));
memset(noteFreq,0,120*sizeof(int)); memset(noteFreq,0,120*sizeof(int));
} }

View File

@ -79,35 +79,45 @@ const char* DivPlatformAmiga::getEffectName(unsigned char effect) {
return NULL; return NULL;
} }
#define writeAudDat(x) \
chan[i].audDat=x; \
if (i<3 && chan[i].useV) { \
chan[i+1].outVol=(unsigned char)chan[i].audDat^0x80; \
if (chan[i+1].outVol>64) chan[i+1].outVol=64; \
} \
if (i<3 && chan[i].useP) { \
chan[i+1].freq=(unsigned char)chan[i].audDat^0x80; \
if (chan[i+1].freq<AMIGA_DIVIDER) chan[i+1].freq=AMIGA_DIVIDER; \
}
void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t len) { void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t len) {
static int outL, outR; static int outL, outR;
for (size_t h=start; h<start+len; h++) { for (size_t h=start; h<start+len; h++) {
outL=0; outL=0;
outR=0; outR=0;
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) { if (chan[i].useWave || (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen)) {
chan[i].audSub-=AMIGA_DIVIDER; chan[i].audSub-=AMIGA_DIVIDER;
if (chan[i].audSub<0) { if (chan[i].audSub<0) {
DivSample* s=parent->getSample(chan[i].sample); if (chan[i].useWave) {
if (s->samples>0) { writeAudDat(chan[i].ws.output[chan[i].audPos++]^0x80);
chan[i].audDat=s->data8[chan[i].audPos++]; if (chan[i].audPos>=(unsigned int)(chan[i].audLen<<1)) {
if (i<3 && chan[i].useV) { chan[i].audPos=0;
chan[i+1].outVol=(unsigned char)chan[i].audDat^0x80;
if (chan[i+1].outVol>64) chan[i+1].outVol=64;
}
if (i<3 && chan[i].useP) {
chan[i+1].freq=(unsigned char)chan[i].audDat^0x80;
if (chan[i+1].freq<AMIGA_DIVIDER) chan[i+1].freq=AMIGA_DIVIDER;
}
if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
chan[i].audPos=s->loopStart;
} else {
chan[i].sample=-1;
}
} }
} else { } else {
chan[i].sample=-1; DivSample* s=parent->getSample(chan[i].sample);
if (s->samples>0) {
writeAudDat(s->data8[chan[i].audPos++]);
if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
chan[i].audPos=s->loopStart;
} else {
chan[i].sample=-1;
}
}
} else {
chan[i].sample=-1;
}
} }
/*if (chan[i].freq<124) { /*if (chan[i].freq<124) {
if (++chan[i].busClock>=512) { if (++chan[i].busClock>=512) {
@ -151,7 +161,7 @@ void DivPlatformAmiga::tick() {
chan[i].outVol=((chan[i].vol%65)*MIN(64,chan[i].std.vol.val))>>6; chan[i].outVol=((chan[i].vol%65)*MIN(64,chan[i].std.vol.val))>>6;
} }
double off=1.0; double off=1.0;
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) { if (!chan[i].useWave && chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].sample); DivSample* s=parent->getSample(chan[i].sample);
if (s->centerRate<1) { if (s->centerRate<1) {
off=1.0; off=1.0;
@ -174,21 +184,21 @@ void DivPlatformAmiga::tick() {
chan[i].freqChanged=true; chan[i].freqChanged=true;
} }
} }
if (chan[i].std.wave.had) { if (chan[i].useWave && chan[i].std.wave.had) {
if (chan[i].wave!=chan[i].std.wave.val) { if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave.val; chan[i].wave=chan[i].std.wave.val;
chan[i].ws.changeWave1(chan[i].wave);
if (!chan[i].keyOff) chan[i].keyOn=true; if (!chan[i].keyOff) chan[i].keyOn=true;
} }
} }
if (chan[i].useWave && chan[i].active) {
chan[i].ws.tick();
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins); //DivInstrument* ins=parent->getIns(chan[i].ins);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].freq>4095) chan[i].freq=4095;
if (chan[i].note>0x5d) chan[i].freq=0x01;
if (chan[i].keyOn) { if (chan[i].keyOn) {
if (chan[i].wave<0) {
chan[i].wave=0;
}
} }
if (chan[i].keyOff) { if (chan[i].keyOff) {
} }
@ -203,20 +213,33 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
switch (c.cmd) { switch (c.cmd) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins); DivInstrument* ins=parent->getIns(chan[c.chan].ins);
chan[c.chan].sample=ins->amiga.initSample;
double off=1.0; double off=1.0;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) { if (ins->amiga.useWave) {
DivSample* s=parent->getSample(chan[c.chan].sample); chan[c.chan].useWave=true;
if (s->centerRate<1) { chan[c.chan].audLen=(ins->amiga.waveLen+1)>>1;
off=1.0; if (chan[c.chan].insChanged) {
} else { if (chan[c.chan].wave<0) {
off=8363.0/(double)s->centerRate; chan[c.chan].wave=0;
chan[c.chan].ws.setWidth(chan[c.chan].audLen<<1);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
}
} else {
chan[c.chan].sample=ins->amiga.initSample;
chan[c.chan].useWave=false;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=8363.0/(double)s->centerRate;
}
} }
} }
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value)); chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value));
} }
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1; chan[c.chan].sample=-1;
} }
if (chan[c.chan].setPos) { if (chan[c.chan].setPos) {
@ -232,6 +255,10 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
chan[c.chan].active=true; chan[c.chan].active=true;
chan[c.chan].keyOn=true; chan[c.chan].keyOn=true;
chan[c.chan].std.init(ins); chan[c.chan].std.init(ins);
if (chan[c.chan].useWave) {
chan[c.chan].ws.init(ins,chan[c.chan].audLen<<1,255,chan[c.chan].insChanged);
}
chan[c.chan].insChanged=false;
break; break;
} }
case DIV_CMD_NOTE_OFF: case DIV_CMD_NOTE_OFF:
@ -247,6 +274,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
case DIV_CMD_INSTRUMENT: case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) { if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value; chan[c.chan].ins=c.value;
chan[c.chan].insChanged=true;
} }
break; break;
case DIV_CMD_VOLUME: case DIV_CMD_VOLUME:
@ -268,14 +296,16 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
break; break;
case DIV_CMD_WAVE: case DIV_CMD_WAVE:
if (!chan[c.chan].useWave) break;
chan[c.chan].wave=c.value; chan[c.chan].wave=c.value;
chan[c.chan].keyOn=true; chan[c.chan].keyOn=true;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
break; break;
case DIV_CMD_NOTE_PORTA: { case DIV_CMD_NOTE_PORTA: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins); DivInstrument* ins=parent->getIns(chan[c.chan].ins);
chan[c.chan].sample=ins->amiga.initSample; chan[c.chan].sample=ins->amiga.initSample;
double off=1.0; double off=1.0;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) { if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample); DivSample* s=parent->getSample(chan[c.chan].sample);
if (s->centerRate<1) { if (s->centerRate<1) {
off=1.0; off=1.0;
@ -307,7 +337,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
} }
case DIV_CMD_LEGATO: { case DIV_CMD_LEGATO: {
double off=1.0; double off=1.0;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) { if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample); DivSample* s=parent->getSample(chan[c.chan].sample);
if (s->centerRate<1) { if (s->centerRate<1) {
off=1.0; off=1.0;
@ -327,6 +357,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
chan[c.chan].inPorta=c.value; chan[c.chan].inPorta=c.value;
break; break;
case DIV_CMD_SAMPLE_POS: case DIV_CMD_SAMPLE_POS:
if (chan[c.chan].useWave) break;
chan[c.chan].audPos=c.value; chan[c.chan].audPos=c.value;
chan[c.chan].setPos=true; chan[c.chan].setPos=true;
break; break;
@ -373,6 +404,8 @@ void* DivPlatformAmiga::getChanState(int ch) {
void DivPlatformAmiga::reset() { void DivPlatformAmiga::reset() {
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
chan[i]=DivPlatformAmiga::Channel(); chan[i]=DivPlatformAmiga::Channel();
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,32,255);
filter[0][i]=0; filter[0][i]=0;
filter[1][i]=0; filter[1][i]=0;
} }
@ -397,7 +430,11 @@ void DivPlatformAmiga::notifyInsChange(int ins) {
} }
void DivPlatformAmiga::notifyWaveChange(int wave) { void DivPlatformAmiga::notifyWaveChange(int wave) {
// TODO when wavetables are added for (int i=0; i<4; i++) {
if (chan[i].useWave && chan[i].wave==wave) {
chan[i].ws.changeWave1(wave);
}
}
} }
void DivPlatformAmiga::notifyInsDeletion(void* ins) { void DivPlatformAmiga::notifyInsDeletion(void* ins) {

View File

@ -23,6 +23,7 @@
#include "../dispatch.h" #include "../dispatch.h"
#include <queue> #include <queue>
#include "../macroInt.h" #include "../macroInt.h"
#include "../waveSynth.h"
class DivPlatformAmiga: public DivDispatch { class DivPlatformAmiga: public DivDispatch {
struct Channel { struct Channel {
@ -39,6 +40,7 @@ class DivPlatformAmiga: public DivDispatch {
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos, useV, useP; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos, useV, useP;
signed char vol, outVol; signed char vol, outVol;
DivMacroInt std; DivMacroInt std;
DivWaveSynth ws;
Channel(): Channel():
freq(0), freq(0),
baseFreq(0), baseFreq(0),
@ -49,7 +51,7 @@ class DivPlatformAmiga: public DivDispatch {
audSub(0), audSub(0),
audDat(0), audDat(0),
sample(-1), sample(-1),
wave(0), wave(-1),
ins(-1), ins(-1),
busClock(0), busClock(0),
note(0), note(0),

View File

@ -2264,6 +2264,17 @@ void FurnaceGUI::drawInsEdit() {
} }
ImGui::EndCombo(); ImGui::EndCombo();
} }
P(ImGui::Checkbox("Use wavetable (Amiga only)",&ins->amiga.useWave));
if (ins->amiga.useWave) {
int len=ins->amiga.waveLen+1;
if (ImGui::InputInt("Width",&len,2,16)) {
if (len<2) len=2;
if (len>256) len=256;
ins->amiga.waveLen=(len&(~1))-1;
PARAMETER
}
}
ImGui::BeginDisabled(ins->amiga.useWave);
P(ImGui::Checkbox("Use sample map (does not work yet!)",&ins->amiga.useNoteMap)); P(ImGui::Checkbox("Use sample map (does not work yet!)",&ins->amiga.useNoteMap));
if (ins->amiga.useNoteMap) { if (ins->amiga.useNoteMap) {
if (ImGui::BeginTable("NoteMap",3,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { if (ImGui::BeginTable("NoteMap",3,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) {
@ -2317,6 +2328,7 @@ void FurnaceGUI::drawInsEdit() {
ImGui::EndTable(); ImGui::EndTable();
} }
} }
ImGui::EndDisabled();
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) {
@ -2383,7 +2395,7 @@ void FurnaceGUI::drawInsEdit() {
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
if (ins->type==DIV_INS_GB || if (ins->type==DIV_INS_GB ||
ins->type==DIV_INS_AMIGA || (ins->type==DIV_INS_AMIGA && ins->amiga.useWave) ||
ins->type==DIV_INS_X1_010 || ins->type==DIV_INS_X1_010 ||
ins->type==DIV_INS_N163 || ins->type==DIV_INS_N163 ||
ins->type==DIV_INS_FDS || ins->type==DIV_INS_FDS ||