diff --git a/papers/doc/7-systems/amiga.md b/papers/doc/7-systems/amiga.md index 97c1151f..cbc73f7a 100644 --- a/papers/doc/7-systems/amiga.md +++ b/papers/doc/7-systems/amiga.md @@ -6,8 +6,10 @@ in this very computer music trackers were born... # 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. - does not work on the last channel. - `12xx`: toggle period (frequency) modulation with the next channel. - - does not work on the last channel. \ No newline at end of file + - does not work on the last channel. +- `13xx`: toggle low-pass filter. `0` turns it off and `1` turns it on. \ No newline at end of file diff --git a/papers/format.md b/papers/format.md index ce1685c6..87e53ebf 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,8 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 82: Furnace dev82 +- 81: Furnace dev81 - 80: Furnace dev80 - 79: Furnace dev79 - 78: Furnace dev78 @@ -342,7 +344,11 @@ size | description 1 | filter macro is absolute --- | **Amiga instrument data** 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** 4 | volume macro length 4 | arp macro length diff --git a/src/engine/engine.h b/src/engine/engine.h index 89f6ff73..32e89fd4 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev81" -#define DIV_ENGINE_VERSION 81 +#define DIV_VERSION "dev82" +#define DIV_ENGINE_VERSION 82 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 7dbf5b3f..460fee9e 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -106,7 +106,9 @@ void DivInstrument::putInsData(SafeWriter* w) { // Amiga 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); } @@ -571,8 +573,15 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { // Amiga amiga.initSample=reader.readS(); + if (version>=82) { + amiga.useWave=reader.readC(); + amiga.waveLen=(unsigned char)reader.readC(); + } else { + reader.readC(); + reader.readC(); + } // reserved - for (int k=0; k<14; k++) reader.readC(); + for (int k=0; k<12; k++) reader.readC(); // standard std.volMacro.len=reader.readI(); diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 605241c7..80df6ab4 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -293,12 +293,16 @@ struct DivInstrumentC64 { struct DivInstrumentAmiga { short initSample; bool useNoteMap; + bool useWave; + unsigned char waveLen; int noteFreq[120]; short noteMap[120]; DivInstrumentAmiga(): initSample(0), - useNoteMap(false) { + useNoteMap(false), + useWave(false), + waveLen(31) { memset(noteMap,-1,120*sizeof(short)); memset(noteFreq,0,120*sizeof(int)); } diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 458c9329..e7b57457 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -79,35 +79,45 @@ const char* DivPlatformAmiga::getEffectName(unsigned char effect) { 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=0 && chan[i].samplesong.sampleLen) { + if (chan[i].useWave || (chan[i].sample>=0 && chan[i].samplesong.sampleLen)) { chan[i].audSub-=AMIGA_DIVIDER; if (chan[i].audSub<0) { - DivSample* s=parent->getSample(chan[i].sample); - if (s->samples>0) { - chan[i].audDat=s->data8[chan[i].audPos++]; - 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=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; - } + if (chan[i].useWave) { + writeAudDat(chan[i].ws.output[chan[i].audPos++]^0x80); + if (chan[i].audPos>=(unsigned int)(chan[i].audLen<<1)) { + chan[i].audPos=0; } } 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].busClock>=512) { @@ -151,7 +161,7 @@ void DivPlatformAmiga::tick() { chan[i].outVol=((chan[i].vol%65)*MIN(64,chan[i].std.vol.val))>>6; } double off=1.0; - if (chan[i].sample>=0 && chan[i].samplesong.sampleLen) { + if (!chan[i].useWave && chan[i].sample>=0 && chan[i].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[i].sample); if (s->centerRate<1) { off=1.0; @@ -174,21 +184,21 @@ void DivPlatformAmiga::tick() { chan[i].freqChanged=true; } } - if (chan[i].std.wave.had) { - if (chan[i].wave!=chan[i].std.wave.val) { + if (chan[i].useWave && chan[i].std.wave.had) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { chan[i].wave=chan[i].std.wave.val; + chan[i].ws.changeWave1(chan[i].wave); if (!chan[i].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) { //DivInstrument* ins=parent->getIns(chan[i].ins); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); 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].wave<0) { - chan[i].wave=0; - } } if (chan[i].keyOff) { } @@ -203,20 +213,33 @@ int DivPlatformAmiga::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); - chan[c.chan].sample=ins->amiga.initSample; double off=1.0; - if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[c.chan].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=8363.0/(double)s->centerRate; + if (ins->amiga.useWave) { + chan[c.chan].useWave=true; + chan[c.chan].audLen=(ins->amiga.waveLen+1)>>1; + if (chan[c.chan].insChanged) { + if (chan[c.chan].wave<0) { + 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].samplesong.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) { 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; } if (chan[c.chan].setPos) { @@ -232,6 +255,10 @@ int DivPlatformAmiga::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; 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; } case DIV_CMD_NOTE_OFF: @@ -247,6 +274,7 @@ int DivPlatformAmiga::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: @@ -268,14 +296,16 @@ int DivPlatformAmiga::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; case DIV_CMD_WAVE: + if (!chan[c.chan].useWave) break; chan[c.chan].wave=c.value; chan[c.chan].keyOn=true; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); break; case DIV_CMD_NOTE_PORTA: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); chan[c.chan].sample=ins->amiga.initSample; double off=1.0; - if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); if (s->centerRate<1) { off=1.0; @@ -307,7 +337,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { } case DIV_CMD_LEGATO: { double off=1.0; - if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); if (s->centerRate<1) { off=1.0; @@ -327,6 +357,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { chan[c.chan].inPorta=c.value; break; case DIV_CMD_SAMPLE_POS: + if (chan[c.chan].useWave) break; chan[c.chan].audPos=c.value; chan[c.chan].setPos=true; break; @@ -373,6 +404,8 @@ void* DivPlatformAmiga::getChanState(int ch) { void DivPlatformAmiga::reset() { for (int i=0; i<4; i++) { chan[i]=DivPlatformAmiga::Channel(); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,255); filter[0][i]=0; filter[1][i]=0; } @@ -397,7 +430,11 @@ void DivPlatformAmiga::notifyInsChange(int ins) { } 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) { diff --git a/src/engine/platform/amiga.h b/src/engine/platform/amiga.h index b5f13701..8fbe8fec 100644 --- a/src/engine/platform/amiga.h +++ b/src/engine/platform/amiga.h @@ -23,6 +23,7 @@ #include "../dispatch.h" #include #include "../macroInt.h" +#include "../waveSynth.h" class DivPlatformAmiga: public DivDispatch { struct Channel { @@ -39,6 +40,7 @@ class DivPlatformAmiga: public DivDispatch { bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos, useV, useP; signed char vol, outVol; DivMacroInt std; + DivWaveSynth ws; Channel(): freq(0), baseFreq(0), @@ -49,7 +51,7 @@ class DivPlatformAmiga: public DivDispatch { audSub(0), audDat(0), sample(-1), - wave(0), + wave(-1), ins(-1), busClock(0), note(0), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index e9e69084..4380764d 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2264,6 +2264,17 @@ void FurnaceGUI::drawInsEdit() { } 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)); if (ins->amiga.useNoteMap) { if (ImGui::BeginTable("NoteMap",3,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { @@ -2317,6 +2328,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTable(); } } + ImGui::EndDisabled(); ImGui::EndTabItem(); } if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { @@ -2383,7 +2395,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTabItem(); } 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_N163 || ins->type==DIV_INS_FDS ||