mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-23 04:55:13 +00:00
parent
014e86d3d1
commit
24209c7853
8 changed files with 119 additions and 47 deletions
|
@ -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.
|
||||
- `13xx`: toggle low-pass filter. `0` turns it off and `1` turns it on.
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -79,26 +79,35 @@ 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<AMIGA_DIVIDER) chan[i+1].freq=AMIGA_DIVIDER; \
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
static int outL, outR;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
outL=0;
|
||||
outR=0;
|
||||
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;
|
||||
if (chan[i].audSub<0) {
|
||||
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 {
|
||||
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<AMIGA_DIVIDER) chan[i+1].freq=AMIGA_DIVIDER;
|
||||
}
|
||||
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;
|
||||
|
@ -109,6 +118,7 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le
|
|||
} else {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
}
|
||||
/*if (chan[i].freq<124) {
|
||||
if (++chan[i].busClock>=512) {
|
||||
unsigned int rAmount=(124-chan[i].freq)*2;
|
||||
|
@ -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].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);
|
||||
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,8 +213,20 @@ 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 (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].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
if (s->centerRate<1) {
|
||||
|
@ -213,10 +235,11 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
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].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);
|
||||
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].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);
|
||||
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) {
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "../dispatch.h"
|
||||
#include <queue>
|
||||
#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),
|
||||
|
|
|
@ -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 ||
|
||||
|
|
Loading…
Reference in a new issue