Merge branch 'master' of https://github.com/tildearrow/furnace into es5506_alt

# Conflicts:
#	src/gui/insEdit.cpp
#	src/gui/sampleEdit.cpp
This commit is contained in:
cam900 2022-04-26 12:54:11 +09:00
commit 97d2bddf1f
66 changed files with 1118 additions and 312 deletions

View file

@ -436,6 +436,8 @@ class DivDispatch {
#define NOTE_PERIODIC_NOROUND(x) parent->calcBaseFreq(chipClock,CHIP_DIVIDER,x,true)
#define NOTE_FREQUENCY(x) parent->calcBaseFreq(chipClock,CHIP_FREQBASE,x,false)
#define NOTE_FNUM_BLOCK(x,bits) parent->calcBaseFreqFNumBlock(chipClock,CHIP_FREQBASE,x,bits)
#define COLOR_NTSC (315000000.0/88.0)
#define COLOR_PAL (283.75*15625.0+25.0)

View file

@ -770,6 +770,9 @@ String DivEngine::getWarnings() {
}
DivInstrument* DivEngine::getIns(int index, DivInstrumentType fallbackType) {
if (index==-2 && tempIns!=NULL) {
return tempIns;
}
if (index<0 || index>=song.insLen) {
switch (fallbackType) {
case DIV_INS_OPLL:
@ -918,6 +921,30 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri
base*(divider/clock);
}
unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) {
int bf=calcBaseFreq(clock,divider,note,false);
int block=note/12;
if (block<0) block=0;
if (block>7) block=7;
bf>>=block;
if (bf<0) bf=0;
// octave boundaries
while (bf>0 && bf<644 && block>0) {
bf<<=1;
block--;
}
if (bf>1288) {
while (block<7) {
bf>>=1;
block++;
}
if (bf>((1<<bits)-1)) {
bf=(1<<bits)-1;
}
}
return bf|(block<<bits);
}
int DivEngine::calcFreq(int base, int pitch, bool period, int octave) {
if (song.linearPitch) {
// global pitch multiplier
@ -1381,6 +1408,15 @@ int DivEngine::addInstrumentPtr(DivInstrument* which) {
return song.insLen;
}
void DivEngine::loadTempIns(DivInstrument* which) {
BUSY_BEGIN;
if (tempIns==NULL) {
tempIns=new DivInstrument;
}
*tempIns=*which;
BUSY_END;
}
void DivEngine::delInstrument(int index) {
BUSY_BEGIN;
saveLock.lock();
@ -2037,7 +2073,7 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
}
do {
if ((ins<0 || ins>=song.insLen || getChannelType(finalChan)==4 || getPreferInsType(finalChan)==getIns(ins)->type || getIns(ins)->type==DIV_INS_AMIGA) && chan[finalChan].midiNote==-1) {
if ((ins==-1 || ins>=song.insLen || getChannelType(finalChan)==4 || getPreferInsType(finalChan)==getIns(ins)->type || getIns(ins)->type==DIV_INS_AMIGA) && chan[finalChan].midiNote==-1) {
chan[finalChan].midiNote=note;
pendingNotes.push(DivNoteEvent(finalChan,ins,note,vol,true));
break;

View file

@ -43,8 +43,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev84"
#define DIV_ENGINE_VERSION 84
#define DIV_VERSION "dev87"
#define DIV_ENGINE_VERSION 87
// for imports
#define DIV_VERSION_MOD 0xff01
@ -290,6 +290,9 @@ class DivEngine {
void loadVGI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadS3I(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadOPLI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadOPNI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadY12(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
@ -301,6 +304,7 @@ class DivEngine {
public:
DivSong song;
DivInstrument* tempIns;
DivSystem sysOfChan[DIV_MAX_CHANS];
int dispatchOfChan[DIV_MAX_CHANS];
int dispatchChanOfChan[DIV_MAX_CHANS];
@ -366,6 +370,9 @@ class DivEngine {
// calculate base frequency/period
double calcBaseFreq(double clock, double divider, int note, bool period);
// calculate base frequency in f-num/block format
unsigned short calcBaseFreqFNumBlock(double clock, double divider, int note, int bits);
// calculate frequency/period
int calcFreq(int base, int pitch, bool period=false, int octave=0);
@ -521,6 +528,9 @@ class DivEngine {
// if the returned vector is empty then there was an error.
std::vector<DivInstrument*> instrumentFromFile(const char* path);
// load temporary instrument
void loadTempIns(DivInstrument* which);
// delete instrument
void delInstrument(int index);
@ -796,6 +806,7 @@ class DivEngine {
metroAmp(0.0f),
metroVol(1.0f),
totalProcessed(0),
tempIns(NULL),
oscBuf{NULL,NULL},
oscSize(1),
oscReadPos(0),

View file

@ -162,6 +162,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.gbInsAffectsEnvelope=true;
ds.ignoreDACModeOutsideIntendedChannel=false;
ds.e1e2AlsoTakePriority=true;
ds.fbPortaPause=true;
ds.snDutyReset=true;
// 1.1 compat flags
if (ds.version>24) {
@ -499,9 +501,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
} else {
ins->std.dutyMacro.val[j]=reader.readI();
}
if ((ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) && ins->std.dutyMacro.val[j]>24) {
/*if ((ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) && ins->std.dutyMacro.val[j]>24) {
ins->std.dutyMacro.val[j]=24;
}
}*/
}
if (ins->std.dutyMacro.len>0) {
ins->std.dutyMacro.open=true;
@ -554,6 +556,16 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ins->c64.bp=reader.readC();
ins->c64.lp=reader.readC();
ins->c64.ch3off=reader.readC();
// weird storage
if (ins->c64.volIsCutoff) {
for (int j=0; j<ins->std.volMacro.len; j++) {
ins->std.volMacro.val[j]-=18;
}
}
for (int j=0; j<ins->std.dutyMacro.len; j++) {
ins->std.dutyMacro.val[j]-=12;
}
}
if (ds.system[0]==DIV_SYSTEM_GB && ds.version>0x11) {
@ -993,6 +1005,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<84) {
ds.newSegaPCM=false;
}
if (ds.version<85) {
ds.fbPortaPause=true;
}
if (ds.version<86) {
ds.snDutyReset=true;
}
ds.isDMF=false;
reader.readS(); // reserved
@ -1342,7 +1360,17 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
} else {
reader.readC();
}
for (int i=0; i<22; i++) {
if (ds.version>=85) {
ds.fbPortaPause=reader.readC();
} else {
reader.readC();
}
if (ds.version>=86) {
ds.snDutyReset=reader.readC();
} else {
reader.readC();
}
for (int i=0; i<20; i++) {
reader.readC();
}
}
@ -2296,7 +2324,9 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeC(song.ignoreDACModeOutsideIntendedChannel);
w->writeC(song.e1e2AlsoTakePriority);
w->writeC(song.newSegaPCM);
for (int i=0; i<22; i++) {
w->writeC(song.fbPortaPause);
w->writeC(song.snDutyReset);
for (int i=0; i<20; i++) {
w->writeC(0);
}
@ -2602,7 +2632,13 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
} else { // STD
if (sys!=DIV_SYSTEM_GB) {
w->writeC(i->std.volMacro.len);
w->write(i->std.volMacro.val,4*i->std.volMacro.len);
if ((sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) && i->c64.volIsCutoff) {
for (int j=0; j<i->std.volMacro.len; j++) {
w->writeI(i->std.volMacro.val[j]+18);
}
} else {
w->write(i->std.volMacro.val,4*i->std.volMacro.len);
}
if (i->std.volMacro.len>0) {
w->writeC(i->std.volMacro.loop);
}
@ -2622,7 +2658,13 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
w->writeC(i->std.arpMacro.mode);
w->writeC(i->std.dutyMacro.len);
w->write(i->std.dutyMacro.val,4*i->std.dutyMacro.len);
if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) {
for (int j=0; j<i->std.dutyMacro.len; j++) {
w->writeI(i->std.dutyMacro.val[j]+12);
}
} else {
w->write(i->std.dutyMacro.val,4*i->std.dutyMacro.len);
}
if (i->std.dutyMacro.len>0) {
w->writeC(i->std.dutyMacro.loop);
}

View file

@ -30,6 +30,9 @@ enum DivInsFormats {
DIV_INSFORMAT_BTI,
DIV_INSFORMAT_S3I,
DIV_INSFORMAT_SBI,
DIV_INSFORMAT_Y12,
DIV_INSFORMAT_OPLI,
DIV_INSFORMAT_OPNI,
DIV_INSFORMAT_BNK,
DIV_INSFORMAT_OPM,
DIV_INSFORMAT_FF,
@ -342,6 +345,16 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
ins->c64.bp=reader.readC();
ins->c64.lp=reader.readC();
ins->c64.ch3off=reader.readC();
// weird storage
if (ins->c64.volIsCutoff) {
for (int j=0; j<ins->std.volMacro.len; j++) {
ins->std.volMacro.val[j]-=18;
}
}
for (int j=0; j<ins->std.dutyMacro.len; j++) {
ins->std.dutyMacro.val[j]-=12;
}
}
if (ins->type==DIV_INS_GB) {
ins->gb.envVol=reader.readC();
@ -495,11 +508,11 @@ void DivEngine::loadS3I(SafeReader& reader, std::vector<DivInstrument*>& ret, St
// Skip more stuff we don't need
reader.seek(21, SEEK_CUR);
} else {
lastError = "S3I PCM samples currently not supported.";
lastError="S3I PCM samples currently not supported.";
logE("S3I PCM samples currently not supported.");
}
ins->name = reader.readString(28);
ins->name = (ins->name.length() == 0) ? stripPath : ins->name;
ins->name = (ins->name.size() == 0) ? stripPath : ins->name;
int s3i_signature = reader.readI();
@ -508,7 +521,7 @@ void DivEngine::loadS3I(SafeReader& reader, std::vector<DivInstrument*>& ret, St
logW("S3I signature invalid.");
};
} catch (EndOfFileException& e) {
lastError = "premature end of file";
lastError="premature end of file";
logE("premature end of file");
delete ins;
return;
@ -531,7 +544,7 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, St
// 32-byte null terminated instrument name
String patchName = reader.readString(32);
patchName = (patchName.length() == 0) ? stripPath : patchName;
patchName = (patchName.size() == 0) ? stripPath : patchName;
auto writeOp = [](sbi_t& sbi, DivInstrumentFM::Operator& opM, DivInstrumentFM::Operator& opC) {
opM.mult = sbi.Mcharacteristics & 0xF;
@ -605,7 +618,7 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, St
if (is_6op) {
// Freq Monster 801 6op SBIs use a 4+2op layout
// Save the 4op portion before reading the 2op part
ins->name = fmt::format("{0} (4op)", ins->name);
ins->name = fmt::sprintf("%s (4op)", ins->name);
ret.push_back(ins);
readSbiOpData(sbi_op12, reader);
@ -615,7 +628,7 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, St
DivInstrumentFM::Operator& opC6 = ins->fm.op[1];
ins->type = DIV_INS_OPL;
ins->fm.ops = 2;
ins->name = fmt::format("{0} (2op)", patchName);
ins->name = fmt::sprintf("%s (2op)", patchName);
writeOp(sbi_op12, opM6, opC6);
ins->fm.alg = (sbi_op12.FeedConnect & 0x1);
ins->fm.fb = ((sbi_op12.FeedConnect >> 1) & 0x7);
@ -629,11 +642,209 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, St
}
} catch (EndOfFileException& e) {
lastError = "premature end of file";
lastError="premature end of file";
logE("premature end of file");
delete ins;
}
}
void DivEngine::loadOPLI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
DivInstrument* ins = new DivInstrument;
try {
reader.seek(0, SEEK_SET);
String header = reader.readString(11);
if (header == "WOPL3-INST") {
uint16_t version = reader.readS();
if (version > 3) {
logW("Unknown OPLI version.");
}
reader.readC(); // skip isPerc field
ins->type = DIV_INS_OPL;
String insName = reader.readString(32);
insName = (insName.size() > 0) ? insName : stripPath;
ins->name = insName;
reader.seek(7, SEEK_CUR); // skip MIDI params
uint8_t instTypeFlags = reader.readC(); // [0EEEDCBA] - see WOPL/OPLI spec
bool is_4op = ((instTypeFlags & 0x1) == 1);
bool is_2x2op = (((instTypeFlags>>1) & 0x1) == 1);
bool is_rhythm = (((instTypeFlags>>4) & 0x7) > 0);
auto readOpliOp = [](SafeReader& reader, DivInstrumentFM::Operator& op) {
uint8_t characteristics = reader.readC();
uint8_t keyScaleLevel = reader.readC();
uint8_t attackDecay = reader.readC();
uint8_t sustainRelease = reader.readC();
uint8_t waveSelect = reader.readC();
op.mult = characteristics & 0xF;
op.ksr = ((characteristics >> 4) & 0x1);
op.sus = ((characteristics >> 5) & 0x1);
op.vib = ((characteristics >> 6) & 0x1);
op.am = ((characteristics >> 7) & 0x1);
op.tl = keyScaleLevel & 0x3F;
op.ksl = ((keyScaleLevel >> 6) & 0x3);
op.ar = ((attackDecay >> 4) & 0xF);
op.dr = attackDecay & 0xF;
op.rr = sustainRelease & 0xF;
op.sl = ((sustainRelease >> 4) & 0xF);
op.ws = waveSelect;
};
uint8_t feedConnect = reader.readC();
uint8_t feedConnect2nd = reader.readC();
ins->fm.alg = (feedConnect & 0x1);
ins->fm.fb = ((feedConnect >> 1) & 0xF);
if (is_4op && !is_2x2op) {
ins->fm.ops = 4;
ins->fm.alg = (feedConnect & 0x1) | ((feedConnect2nd & 0x1) << 1);
for (int i : {2,0,3,1}) { // omfg >_<
readOpliOp(reader, ins->fm.op[i]);
}
} else {
ins->fm.ops = 2;
for (int i : {1,0}) {
readOpliOp(reader, ins->fm.op[i]);
}
if (is_rhythm) {
ins->fm.opllPreset = (uint8_t)(1<<4);
} else if (is_2x2op) {
// Note: Pair detuning offset not mappable. Use E5xx effect :P
ins->name = fmt::sprintf("%s (1)", insName);
ret.push_back(ins);
ins = new DivInstrument;
ins->type = DIV_INS_OPL;
ins->name = fmt::sprintf("%s (2)", insName);
for (int i : {1,0}) {
readOpliOp(reader, ins->fm.op[i]);
}
}
}
// Skip rest of file
reader.seek(0, SEEK_END);
ret.push_back(ins);
}
} catch (EndOfFileException& e) {
lastError="premature end of file";
logE("premature end of file");
delete ins;
}
}
void DivEngine::loadOPNI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
DivInstrument* ins = new DivInstrument;
try {
reader.seek(0, SEEK_SET);
String header = reader.readString(11);
if (header == "WOPN2-INST" || header == "WOPN2-IN2T") { // omfg >_<
uint16_t version = reader.readS();
if (!(version >= 2) || version > 0xF) {
// version 1 doesn't have a version field........
reader.seek(-2, SEEK_CUR);
}
reader.readC(); // skip isPerc
ins->type = DIV_INS_FM;
ins->fm.ops = 4;
String insName = reader.readString(32);
ins->name = (insName.size() > 0) ? insName : stripPath;
reader.seek(3, SEEK_CUR); // skip MIDI params
uint8_t feedAlgo = reader.readC();
ins->fm.alg = (feedAlgo & 0x7);
ins->fm.fb = ((feedAlgo>>3) & 0x7);
reader.readC(); // Skip global bank flags - see WOPN/OPNI spec
auto readOpniOp = [](SafeReader& reader, DivInstrumentFM::Operator& op) {
uint8_t dtMul = reader.readC();
uint8_t totalLevel = reader.readC();
uint8_t arRateScale = reader.readC();
uint8_t drAmpEnable = reader.readC();
uint8_t d2r = reader.readC();
uint8_t susRelease = reader.readC();
uint8_t ssgEg = reader.readC();
op.mult = dtMul & 0xF;
op.dt = ((dtMul >> 4) & 0x7);
op.tl = totalLevel & 0x3F;
op.rs = ((arRateScale >> 6) & 0x3);
op.ar = arRateScale & 0x1F;
op.dr = drAmpEnable & 0x1F;
op.am = ((drAmpEnable >> 7) & 0x1);
op.d2r = d2r & 0x1F;
op.rr = susRelease & 0xF;
op.sl = ((susRelease >> 4) & 0xF);
op.ssgEnv = ssgEg;
};
for (int i = 0; i < 4; ++i) {
readOpniOp(reader, ins->fm.op[i]);
}
// Skip rest of file
reader.seek(0, SEEK_END);
ret.push_back(ins);
}
} catch (EndOfFileException& e) {
lastError="premature end of file";
logE("premature end of file");
delete ins;
}
}
void DivEngine::loadY12(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
DivInstrument *ins = new DivInstrument;
try {
reader.seek(0, SEEK_SET);
ins->type = DIV_INS_FM;
ins->fm.ops = 4;
ins->name = stripPath;
for (int i = 0; i < 4; ++i) {
DivInstrumentFM::Operator& insOp = ins->fm.op[i];
uint8_t tmp = reader.readC();
insOp.mult = tmp & 0xF;
insOp.dt = ((tmp >> 4) & 0x7);
insOp.tl = (reader.readC() & 0x3F);
tmp = reader.readC();
insOp.rs = ((tmp >> 6) & 0x3);
insOp.ar = tmp & 0x1F;
tmp = reader.readC();
insOp.dr = tmp & 0x1F;
insOp.am = ((tmp >> 7) & 0x1);
insOp.d2r = (reader.readC() & 0x1F);
tmp = reader.readC();
insOp.rr = tmp & 0xF;
insOp.sl = ((tmp >> 4) & 0xF);
insOp.ssgEnv = reader.readC();
if (!reader.seek(9, SEEK_CUR)) {
throw EndOfFileException(&reader, reader.tell() + 9);
}
}
ins->fm.alg = reader.readC();
ins->fm.fb = reader.readC();
if (!reader.seek(62, SEEK_CUR)) {
throw EndOfFileException(&reader, reader.tell() + 62);
}
ret.push_back(ins);
} catch (EndOfFileException& e) {
lastError="premature end of file";
logE("premature end of file");
delete ins;
}
}
void DivEngine::loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
std::vector<DivInstrument*> insList;
std::vector<String*> instNames;
@ -661,7 +872,9 @@ void DivEngine::loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, St
}
// Seek to BNK data
reader.seek(data_offset, SEEK_SET);
if (!reader.seek(data_offset, SEEK_SET)) {
throw EndOfFileException(&reader, data_offset);
};
// Read until EOF
for (int i = 0; i < readCount; ++i) {
@ -670,6 +883,7 @@ void DivEngine::loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, St
auto& ins = insList[i];
ins->type = DIV_INS_OPL;
ins->fm.ops = 2;
timbre.mode = reader.readC();
timbre.percVoice = reader.readC();
@ -706,14 +920,14 @@ void DivEngine::loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, St
ins->fm.op[0].ws = reader.readC();
ins->fm.op[1].ws = reader.readC();
ins->name = instNames[i]->length() > 0 ? (*instNames[i]) : fmt::format("{0}[{1}]", stripPath, i);
ins->name = instNames[i]->length() > 0 ? (*instNames[i]) : fmt::sprintf("%s[%d]", stripPath, i);
}
reader.seek(0, SEEK_END);
} catch (EndOfFileException& e) {
lastError = "premature end of file";
lastError="premature end of file";
logE("premature end of file");
for (int i = readCount; i >= 0; --i) {
for (int i = readCount - 1; i >= 0; --i) {
delete insList[i];
}
is_failed = true;
@ -721,7 +935,7 @@ void DivEngine::loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, St
} else {
// assume GEMS BNK for now.
lastError = "GEMS BNK currently not supported.";
lastError="GEMS BNK currently not supported.";
logE("GEMS BNK currently not supported.");
}
@ -796,9 +1010,9 @@ void DivEngine::loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, Str
++readCount;
}
} catch (EndOfFileException& e) {
lastError = "premature end of file";
lastError="premature end of file";
logE("premature end of file");
for (int i = readCount; i >= 0; --i) {
for (int i = readCount - 1; i >= 0; --i) {
delete insList[i];
}
return;
@ -810,16 +1024,161 @@ void DivEngine::loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, Str
}
void DivEngine::loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
DivInstrument* ins[128];
memset(ins,0,128*sizeof(void*));
std::vector<DivInstrument*> insList;
int readCount = 0;
bool is_failed = false;
bool patchNameRead = false,
lfoRead = false,
characteristicRead = false,
m1Read = false,
c1Read = false,
m2Read = false,
c2Read = false;
DivInstrument* newPatch = NULL;
auto completePatchRead = [&]() {
return patchNameRead && lfoRead && characteristicRead && m1Read && c1Read && m2Read && c2Read;
};
auto resetPatchRead = [&]() {
patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false;
newPatch = NULL;
};
auto readIntStrWithinRange = [](String&& input, int limitLow, int limitHigh) {
int x = std::stoi(input.c_str());
if (x > limitHigh || x < limitLow) {
throw std::invalid_argument(fmt::sprintf("%s is out of bounds of range [%d..%d]", input, limitLow, limitHigh));
}
return (x>limitHigh) ? limitHigh :
(x<limitLow) ? limitLow : x;
};
auto readOpmOperator = [&](SafeReader& reader, DivInstrumentFM::Operator& op) {
op.ar = readIntStrWithinRange(reader.readStringToken(), 0, 31);
op.dr = readIntStrWithinRange(reader.readStringToken(), 0, 31);
op.d2r = readIntStrWithinRange(reader.readStringToken(), 0, 31);
op.rr = readIntStrWithinRange(reader.readStringToken(), 0, 31);
op.sl = readIntStrWithinRange(reader.readStringToken(), 0, 15);
op.tl = readIntStrWithinRange(reader.readStringToken(), 0, 127);
op.rs = readIntStrWithinRange(reader.readStringToken(), 0, 3);;
op.mult = readIntStrWithinRange(reader.readStringToken(), 0, 15);
op.dt = readIntStrWithinRange(reader.readStringToken(), 0, 7);
op.dt = (op.dt >= 4) ? (7 - op.dt) : (op.dt + 3);
op.dt2 = readIntStrWithinRange(reader.readStringToken(), 0, 3);
op.am = readIntStrWithinRange(reader.readStringToken(), 0, 1);
};
try {
String line;
reader.seek(0, SEEK_SET);
while (!reader.isEOF()) {
String token = reader.readStringToken();
if (token.size() == 0) {
continue;
}
if (token.compare(0,2,"//") == 0) {
if (!reader.isEOF()) {
reader.readStringLine();
}
continue;
}
// At this point we know any other line would be associated with patch params
if (newPatch == NULL) {
newPatch = new DivInstrument;
newPatch->type = DIV_INS_FM;
newPatch->fm.ops = 4;
}
// Read each line for their respective params. They may not be written in the same LINE order but they
// must absolutely be properly grouped per patch! Line prefixes must be separated by a space! (see inline comments)
if (token.size() >= 2) {
if (token[0] == '@') {
// @:123 Name of patch
// Note: Fallback to bank filename and current patch number in _file_ order (not @n order)
newPatch->name = reader.readStringLine();
newPatch->name = newPatch->name.size() > 0 ? newPatch->name : fmt::sprintf("%s[%d]", stripPath, readCount);
patchNameRead = true;
} else if (token.compare(0,3,"CH:") == 0) {
// CH: PAN FL CON AMS PMS SLOT NE
reader.readStringToken(); // skip PAN
newPatch->fm.fb = readIntStrWithinRange(reader.readStringToken(), 0, 7);
newPatch->fm.alg = readIntStrWithinRange(reader.readStringToken(), 0, 7);
newPatch->fm.ams = readIntStrWithinRange(reader.readStringToken(), 0, 4);
newPatch->fm.fms = readIntStrWithinRange(reader.readStringToken(), 0, 7);
reader.readStringToken(); // skip SLOT
reader.readStringToken(); // skip NE
characteristicRead = true;
} else if (token.compare(0,3,"C1:") == 0) {
// C1: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN
readOpmOperator(reader, newPatch->fm.op[2]);
c1Read = true;
} else if (token.compare(0,3,"C2:") == 0) {
// C2: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN
readOpmOperator(reader, newPatch->fm.op[3]);
c2Read = true;
} else if (token.compare(0,3,"M1:") == 0) {
// M1: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN
readOpmOperator(reader, newPatch->fm.op[0]);
m1Read = true;
} else if (token.compare(0,3,"M2:") == 0) {
// M2: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN
readOpmOperator(reader, newPatch->fm.op[1]);
m2Read = true;
} else if (token.compare(0,4,"LFO:") == 0) {
// LFO: LFRQ AMD PMD WF NFRQ
// Furnace patches do not store these as they are chip-global.
reader.readStringLine();
lfoRead = true;
} else {
// other unsupported lines ignored.
reader.readStringLine();
}
}
if (completePatchRead()) {
insList.push_back(newPatch);
resetPatchRead();
++readCount;
}
}
if (newPatch != NULL) {
addWarning("Last OPM patch read was incomplete and therefore not imported.");
logW("Last OPM patch read was incomplete and therefore not imported.");
delete newPatch;
}
for (int i = 0; i < readCount; ++i) {
ret.push_back(insList[i]);
}
} catch (EndOfFileException& e) {
lastError="premature end of file";
logE("premature end of file");
return;
is_failed = true;
} catch (std::invalid_argument& e) {
lastError=fmt::sprintf("Invalid value found in patch file. %s", e.what());
logE("Invalid value found in patch file.");
logE(e.what());
is_failed = true;
}
if (is_failed) {
for (int i = readCount - 1; i >= 0; --i) {
delete insList[i];
}
if (newPatch != NULL) {
delete newPatch;
}
}
}
@ -947,6 +1306,12 @@ std::vector<DivInstrument*> DivEngine::instrumentFromFile(const char* path) {
format=DIV_INSFORMAT_S3I;
} else if (extS==String(".sbi")) {
format=DIV_INSFORMAT_SBI;
} else if (extS==String(".opli")) {
format=DIV_INSFORMAT_OPLI;
} else if (extS==String(".opni")) {
format=DIV_INSFORMAT_OPNI;;
} else if (extS==String(".y12")) {
format=DIV_INSFORMAT_Y12;
} else if (extS==String(".bnk")) {
format=DIV_INSFORMAT_BNK;
} else if (extS==String(".opm")) {
@ -971,20 +1336,30 @@ std::vector<DivInstrument*> DivEngine::instrumentFromFile(const char* path) {
break;
case DIV_INSFORMAT_BTI: // TODO
break;
case DIV_INSFORMAT_OPM: // TODO
break;
case DIV_INSFORMAT_S3I:
loadS3I(reader,ret,stripPath);
break;
case DIV_INSFORMAT_SBI:
loadSBI(reader,ret,stripPath);
break;
case DIV_INSFORMAT_OPLI:
loadOPLI(reader,ret,stripPath);
break;
case DIV_INSFORMAT_OPNI:
loadOPNI(reader, ret, stripPath);
break;
case DIV_INSFORMAT_Y12:
loadY12(reader,ret,stripPath);
break;
case DIV_INSFORMAT_BNK:
loadBNK(reader, ret, stripPath);
break;
case DIV_INSFORMAT_FF:
loadFF(reader,ret,stripPath);
break;
case DIV_INSFORMAT_OPM:
loadOPM(reader, ret, stripPath);
break;
}
if (reader.tell()<reader.size()) {

View file

@ -643,6 +643,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
std.arpMacro.val[j]-=12;
}
}
if (type==DIV_INS_C64 && version<87) {
if (c64.volIsCutoff && !c64.filterIsAbs) for (int j=0; j<std.volMacro.len; j++) {
std.volMacro.val[j]-=18;
}
if (!c64.dutyIsAbs) for (int j=0; j<std.dutyMacro.len; j++) {
std.dutyMacro.val[j]-=12;
}
}
if (version>=17) {
reader.read(std.pitchMacro.val,4*std.pitchMacro.len);
reader.read(std.ex1Macro.val,4*std.ex1Macro.len);

View file

@ -34,7 +34,7 @@ class DivPlatformAmiga: public DivDispatch {
int audSub;
signed char audDat;
int sample, wave;
unsigned char ins;
int ins;
int busClock;
int note;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos, useV, useP;

View file

@ -37,7 +37,7 @@ class DivPlatformArcade: public DivDispatch {
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, note;
unsigned char ins;
int ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset;
int vol, outVol;

View file

@ -33,7 +33,8 @@ class DivPlatformAY8910: public DivDispatch {
struct Channel {
unsigned char freqH, freqL;
int freq, baseFreq, note, pitch;
unsigned char ins, psgMode, autoEnvNum, autoEnvDen;
int ins;
unsigned char psgMode, autoEnvNum, autoEnvDen;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
int vol, outVol;

View file

@ -29,7 +29,8 @@ class DivPlatformAY8930: public DivDispatch {
struct Channel {
unsigned char freqH, freqL;
int freq, baseFreq, note, pitch;
unsigned char ins, psgMode, autoEnvNum, autoEnvDen, duty;
int ins;
unsigned char psgMode, autoEnvNum, autoEnvDen, duty;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
int vol, outVol;

View file

@ -28,8 +28,7 @@
class DivPlatformBubSysWSG: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, note;
unsigned char ins;
int freq, baseFreq, pitch, note, ins;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
signed char vol, outVol, wave;
signed char waveROM[32] = {0}; // 4 bit PROM per channel on bubble system

View file

@ -131,7 +131,7 @@ void DivPlatformC64::tick(bool sysTick) {
if (ins->c64.filterIsAbs) {
filtCut=MIN(2047,chan[i].std.vol.val);
} else {
filtCut-=((signed char)chan[i].std.vol.val-18)*7;
filtCut-=((signed char)chan[i].std.vol.val)*7;
if (filtCut>2047) filtCut=2047;
if (filtCut<0) filtCut=0;
}
@ -161,7 +161,7 @@ void DivPlatformC64::tick(bool sysTick) {
if (ins->c64.dutyIsAbs) {
chan[i].duty=chan[i].std.duty.val;
} else {
chan[i].duty-=((signed char)chan[i].std.duty.val-12)*4;
chan[i].duty-=((signed char)chan[i].std.duty.val)*4;
}
rWrite(i*7+2,chan[i].duty&0xff);
rWrite(i*7+3,chan[i].duty>>8);

View file

@ -26,8 +26,8 @@
class DivPlatformC64: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, prevFreq, testWhen, note;
unsigned char ins, sweep, wave, attack, decay, sustain, release;
int freq, baseFreq, pitch, prevFreq, testWhen, note, ins;
unsigned char sweep, wave, attack, decay, sustain, release;
short duty;
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, filter;
bool resetMask, resetFilter, resetDuty, ring, sync;

View file

@ -443,7 +443,7 @@ void DivPlatformES5506::tick(bool sysTick) {
pageWrite(0x00|i,0x06,0); // Clear ECOUNT
pageWrite(0x20|i,0x03,chan[i].pcm.reversed?chan[i].pcm.end:chan[i].pcm.start); // Set ACCUM to start address
pageWrite(0x00|i,0x07,0xffff); // Set K1 and K2 to 0xffff
pageWrite(0x00|i,0x09,0xffff);
pageWrite(0x00|i,0x09,0xffff,~0,(chanMax+1)*4*2); // needs to 4 sample period delay
pageWrite(0x00|i,0x01,chan[i].freq);
pageWrite(0x20|i,0x01,(chan[i].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT)?chan[i].pcm.start:chan[i].pcm.loopStart);
pageWrite(0x20|i,0x02,(chan[i].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT)?chan[i].pcm.end:chan[i].pcm.loopEnd);
@ -451,7 +451,7 @@ void DivPlatformES5506::tick(bool sysTick) {
pageWrite(0x00|i,0x03,((unsigned char)chan[i].envelope.lVRamp)<<8);
pageWrite(0x00|i,0x05,((unsigned char)chan[i].envelope.rVRamp)<<8);
pageWrite(0x00|i,0x0a,(((unsigned char)chan[i].envelope.k1Ramp)<<8)|(chan[i].envelope.k1Slow?1:0));
pageWrite(0x00|i,0x08,(((unsigned char)chan[i].envelope.k2Ramp)<<8)|(chan[i].envelope.k2Slow?1:0),~0,(chanMax+1)*4*2); // needs to 4 sample period delay
pageWrite(0x00|i,0x08,(((unsigned char)chan[i].envelope.k2Ramp)<<8)|(chan[i].envelope.k2Slow?1:0));
// initialize filter
pageWriteMask(0x00|i,0x5f,0x00,(chan[i].pcm.bank<<14)|(chan[i].filter.mode<<8),0xc300);
if ((chan[i].std.ex2.mode==0) && (chan[i].std.ex2.had)) {

View file

@ -54,10 +54,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
loopEnd(0),
loopMode(DIV_SAMPLE_LOOPMODE_ONESHOT) {}
} pcm;
int freq, baseFreq, pitch;
int sample, wave;
unsigned char ins;
int note;
int freq, baseFreq, pitch, note, ins, sample, wave;
bool active, insChanged, freqChanged, volChanged, keyOn, keyOff, inPorta, useWave, isReverseLoop;
struct FilterChanged { // Filter changed flags
@ -103,9 +100,10 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
freq(0),
baseFreq(0),
pitch(0),
sample(-1),
ins(-1),
note(0),
ins(-1),
sample(-1),
wave(-1),
active(false),
insChanged(true),
freqChanged(false),

View file

@ -26,8 +26,8 @@
class DivPlatformFDS: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, prevFreq, note, modFreq;
unsigned char ins, duty, sweep, modDepth, modPos;
int freq, baseFreq, pitch, prevFreq, note, modFreq, ins;
unsigned char duty, sweep, modDepth, modPos;
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, modOn;
signed char vol, outVol, wave;
signed char modTable[32];

View file

@ -27,8 +27,8 @@
class DivPlatformGB: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, note;
unsigned char ins, duty, sweep;
int freq, baseFreq, pitch, note, ins;
unsigned char duty, sweep;
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta;
signed char vol, outVol, wave;
DivMacroInt std;

View file

@ -86,14 +86,22 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
static int os[2];
for (size_t h=start; h<start+len; h++) {
if (!dacReady) {
dacDelay+=32000;
if (dacDelay>=rate) {
dacDelay-=rate;
dacReady=true;
}
}
if (dacMode && dacSample!=-1) {
dacPeriod-=6;
if (dacPeriod<1) {
dacPeriod+=dacRate;
if (dacPeriod>=rate) {
DivSample* s=parent->getSample(dacSample);
if (s->samples>0) {
if (!isMuted[5]) {
if (writes.size()<16) {
if (dacReady && writes.size()<16) {
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
dacReady=false;
}
}
dacPos++;
@ -107,7 +115,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
}
}
}
dacPeriod+=MAX(40,dacRate);
while (dacPeriod>=rate) dacPeriod-=rate;
} else {
dacSample=-1;
}
@ -156,14 +164,22 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
static int os[2];
for (size_t h=start; h<start+len; h++) {
if (!dacReady) {
dacDelay+=32000;
if (dacDelay>=rate) {
dacDelay-=rate;
dacReady=true;
}
}
if (dacMode && dacSample!=-1) {
dacPeriod-=24;
if (dacPeriod<1) {
dacPeriod+=dacRate;
if (dacPeriod>=rate) {
DivSample* s=parent->getSample(dacSample);
if (s->samples>0) {
if (!isMuted[5]) {
if (writes.size()<16) {
if (dacReady && writes.size()<16) {
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
dacReady=false;
}
}
dacPos++;
@ -177,7 +193,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
}
}
}
dacPeriod+=MAX(40,dacRate);
while (dacPeriod>=rate) dacPeriod-=rate;
} else {
dacSample=-1;
}
@ -247,15 +263,15 @@ void DivPlatformGenesis::tick(bool sysTick) {
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
} else {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
chan[i].freqChanged=true;
}
}
@ -396,11 +412,20 @@ void DivPlatformGenesis::tick(bool sysTick) {
for (int i=0; i<6; i++) {
if (i==2 && extMode) continue;
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;
int freqt=toFreq(chan[i].freq)+chan[i].std.pitch.val;
immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8);
immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff);
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4)+chan[i].std.pitch.val;
int block=(chan[i].baseFreq&0xf800)>>11;
if (fNum<0) fNum=0;
if (fNum>2047) {
while (block<7) {
fNum>>=1;
block++;
}
if (fNum>2047) fNum=2047;
}
chan[i].freq=(block<<11)|fNum;
if (chan[i].freq>0x3fff) chan[i].freq=0x3fff;
immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8);
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
if (chan[i].furnaceDac && dacMode) {
double off=1.0;
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
@ -408,12 +433,13 @@ void DivPlatformGenesis::tick(bool sysTick) {
if (s->centerRate<1) {
off=1.0;
} else {
off=8363.0/(double)s->centerRate;
off=(double)s->centerRate/8363.0;
}
}
dacRate=(1280000*1.25*off)/MAX(1,chan[i].baseFreq);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4)+chan[i].std.pitch.val;
dacRate=chan[i].freq*off;
if (dacRate<1) dacRate=1;
if (dumpWrites) addWrite(0xffff0001,1280000/dacRate);
if (dumpWrites) addWrite(0xffff0001,dacRate);
}
chan[i].freqChanged=false;
}
@ -424,47 +450,6 @@ void DivPlatformGenesis::tick(bool sysTick) {
}
}
int DivPlatformGenesis::octave(int freq) {
if (freq>=82432) {
return 128;
} else if (freq>=41216) {
return 64;
} else if (freq>=20608) {
return 32;
} else if (freq>=10304) {
return 16;
} else if (freq>=5152) {
return 8;
} else if (freq>=2576) {
return 4;
} else if (freq>=1288) {
return 2;
} else {
return 1;
}
return 1;
}
int DivPlatformGenesis::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);
} else {
return freq&0x7ff;
}
}
void DivPlatformGenesis::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
for (int j=0; j<4; j++) {
@ -511,7 +496,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
dacPos=0;
dacPeriod=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
chan[c.chan].freqChanged=true;
}
chan[c.chan].furnaceDac=true;
@ -530,7 +515,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
}
dacPos=0;
dacPeriod=0;
dacRate=1280000/MAX(1,parent->getSample(dacSample)->rate);
dacRate=MAX(1,parent->getSample(dacSample)->rate);
if (dumpWrites) addWrite(0xffff0001,parent->getSample(dacSample)->rate);
chan[c.chan].furnaceDac=false;
}
@ -578,7 +563,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
chan[c.chan].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
chan[c.chan].portaPause=false;
chan[c.chan].note=c.value;
chan[c.chan].freqChanged=true;
@ -658,31 +643,65 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
if (c.chan==5 && chan[c.chan].furnaceDac) {
int destFreq=parent->calcBaseFreq(1,1,c.value2,false);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value*16;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value*16;
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;
}
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq;
bool return2=false;
if (chan[c.chan].portaPause) {
chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq;
}
if (destFreq>chan[c.chan].baseFreq) {
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
newFreq=chan[c.chan].baseFreq+c.value;
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
newFreq=chan[c.chan].baseFreq-c.value;
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;
}
}
// check for octave boundary
// what the heck!
if (!chan[c.chan].portaPause) {
if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) {
if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) {
chan[c.chan].portaPauseFreq=(644)|((newFreq+0x800)&0xf800);
chan[c.chan].portaPause=true;
break;
}
if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) {
chan[c.chan].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800);
chan[c.chan].portaPause=true;
break;
}
}
chan[c.chan].baseFreq=newFreq;
chan[c.chan].portaPause=false;
chan[c.chan].freqChanged=true;
chan[c.chan].baseFreq=newFreq;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
@ -701,7 +720,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
}
break;
case DIV_CMD_LEGATO: {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
if (c.chan==5 && chan[c.chan].furnaceDac) {
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
} else {
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
}
chan[c.chan].note=c.value;
chan[c.chan].freqChanged=true;
break;
@ -853,6 +876,8 @@ void DivPlatformGenesis::reset() {
dacPeriod=0;
dacPos=0;
dacRate=0;
dacDelay=0;
dacReady=true;
dacSample=-1;
sampleBank=0;
lfoValue=8;

View file

@ -36,8 +36,8 @@ class DivPlatformGenesis: public DivDispatch {
DivInstrumentFM state;
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, note;
unsigned char ins;
int freq, baseFreq, pitch, portaPauseFreq, note;
int ins;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset;
int vol, outVol;
unsigned char pan;
@ -47,6 +47,7 @@ class DivPlatformGenesis: public DivDispatch {
freq(0),
baseFreq(0),
pitch(0),
portaPauseFreq(0),
note(0),
ins(-1),
active(false),
@ -84,6 +85,8 @@ class DivPlatformGenesis: public DivDispatch {
int dacRate;
unsigned int dacPos;
int dacSample;
int dacDelay;
bool dacReady;
unsigned char sampleBank;
unsigned char lfoValue;
@ -93,9 +96,6 @@ class DivPlatformGenesis: public DivDispatch {
short oldWrites[512];
short pendingWrites[512];
int octave(int freq);
int toFreq(int freq);
friend void putDispatchChan(void*,int,int);
void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len);

View file

@ -72,7 +72,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
opChan[ch].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
opChan[ch].portaPause=false;
opChan[ch].freqChanged=true;
}
@ -127,31 +127,49 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq;
bool return2=false;
if (opChan[ch].portaPause) {
opChan[ch].baseFreq=opChan[ch].portaPauseFreq;
}
if (destFreq>opChan[ch].baseFreq) {
newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq);
newFreq=opChan[ch].baseFreq+c.value;
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq);
newFreq=opChan[ch].baseFreq-c.value;
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;
}
}
// what the heck!
if (!opChan[ch].portaPause) {
if (octave(opChan[ch].baseFreq)!=octave(newFreq)) {
opChan[ch].portaPause=true;
break;
if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=(644)|((newFreq+0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
}
}
if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800);
}
}
}
opChan[ch].baseFreq=newFreq;
opChan[ch].portaPause=false;
opChan[ch].freqChanged=true;
opChan[ch].baseFreq=newFreq;
if (return2) return 2;
break;
}
@ -172,7 +190,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
}
break;
case DIV_CMD_LEGATO: {
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
opChan[ch].freqChanged=true;
break;
}
@ -289,35 +307,20 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
unsigned char writeMask=2;
if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch);
if (opChan[i].freq>262143) opChan[i].freq=262143;
if (opChan[i].freq>=82432) {
opChan[i].freqH=((opChan[i].freq>>15)&7)|0x38;
opChan[i].freqL=(opChan[i].freq>>7)&0xff;
} else if (opChan[i].freq>=41216) {
opChan[i].freqH=((opChan[i].freq>>14)&7)|0x30;
opChan[i].freqL=(opChan[i].freq>>6)&0xff;
} else if (opChan[i].freq>=20608) {
opChan[i].freqH=((opChan[i].freq>>13)&7)|0x28;
opChan[i].freqL=(opChan[i].freq>>5)&0xff;
} else if (opChan[i].freq>=10304) {
opChan[i].freqH=((opChan[i].freq>>12)&7)|0x20;
opChan[i].freqL=(opChan[i].freq>>4)&0xff;
} else if (opChan[i].freq>=5152) {
opChan[i].freqH=((opChan[i].freq>>11)&7)|0x18;
opChan[i].freqL=(opChan[i].freq>>3)&0xff;
} else if (opChan[i].freq>=2576) {
opChan[i].freqH=((opChan[i].freq>>10)&7)|0x10;
opChan[i].freqL=(opChan[i].freq>>2)&0xff;
} else if (opChan[i].freq>=1288) {
opChan[i].freqH=((opChan[i].freq>>9)&7)|0x08;
opChan[i].freqL=(opChan[i].freq>>1)&0xff;
} else {
opChan[i].freqH=(opChan[i].freq>>8)&7;
opChan[i].freqL=opChan[i].freq&0xff;
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4)+opChan[i].std.pitch.val;
int block=(opChan[i].baseFreq&0xf800)>>11;
if (fNum<0) fNum=0;
if (fNum>2047) {
while (block<7) {
fNum>>=1;
block++;
}
if (fNum>2047) fNum=2047;
}
immWrite(opChanOffsH[i],opChan[i].freqH);
immWrite(opChanOffsL[i],opChan[i].freqL);
opChan[i].freq=(block<<11)|fNum;
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
}
writeMask|=opChan[i].active<<(4+i);
if (opChan[i].keyOn) {

View file

@ -25,13 +25,27 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
struct OpChannel {
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch;
unsigned char ins;
int freq, baseFreq, pitch, portaPauseFreq, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
int vol;
unsigned char pan;
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
OpChannel():
freqH(0),
freqL(0),
freq(0),
baseFreq(0),
pitch(0),
portaPauseFreq(0),
ins(-1),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
portaPause(false),
vol(0),
pan(3) {}
};
OpChannel opChan[4];
bool isOpMuted[4];

View file

@ -44,8 +44,8 @@ class DivPlatformLynx: public DivDispatch {
DivMacroInt std;
MikeyFreqDiv fd;
MikeyDuty duty;
int baseFreq, pitch, note, actualNote, lfsr;
unsigned char ins, pan;
int baseFreq, pitch, note, actualNote, lfsr, ins;
unsigned char pan;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
signed char vol, outVol;
Channel():

View file

@ -25,8 +25,8 @@
class DivPlatformMMC5: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, prevFreq, note;
unsigned char ins, duty, sweep;
int freq, baseFreq, pitch, prevFreq, note, ins;
unsigned char duty, sweep;
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac;
signed char vol, outVol, wave;
DivMacroInt std;

View file

@ -25,8 +25,8 @@
class DivPlatformNES: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, prevFreq, note;
unsigned char ins, duty, sweep;
int freq, baseFreq, pitch, prevFreq, note, ins;
unsigned char duty, sweep;
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac;
signed char vol, outVol, wave;
DivMacroInt std;

View file

@ -548,6 +548,9 @@ int DivPlatformOPL::dispatch(DivCommand c) {
if (chan[c.chan].insChanged) {
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
chan[c.chan].fourOp=(ops==4);
if (chan[c.chan].fourOp) {
chan[c.chan+1].std.init(NULL);
}
update4OpMask=true;
for (int i=0; i<ops; i++) {
unsigned char slot=slots[i][c.chan];

View file

@ -30,8 +30,7 @@ class DivPlatformOPL: public DivDispatch {
DivInstrumentFM state;
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, note;
unsigned char ins;
int freq, baseFreq, pitch, note, ins;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, fourOp;
int vol, outVol;
unsigned char pan;

View file

@ -33,8 +33,7 @@ class DivPlatformOPLL: public DivDispatch {
DivInstrumentFM state;
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, note;
unsigned char ins;
int freq, baseFreq, pitch, note, ins;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta;
int vol, outVol;
unsigned char pan;

View file

@ -31,8 +31,8 @@ class DivPlatformPCE: public DivDispatch {
int freq, baseFreq, pitch, note;
int dacPeriod, dacRate;
unsigned int dacPos;
int dacSample;
unsigned char ins, pan;
int dacSample, ins;
unsigned char pan;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac;
signed char vol, outVol, wave;
DivMacroInt std;

View file

@ -53,6 +53,7 @@ void DivPlatformPCSpeaker::acquire_unfilt(short* bufL, short* bufR, size_t start
for (size_t i=start; i<start+len; i++) {
if (on) {
pos-=PCSPKR_DIVIDER;
if (pos>freq) pos=freq;
while (pos<0) {
if (freq<1) {
pos=1;
@ -71,6 +72,7 @@ void DivPlatformPCSpeaker::acquire_cone(short* bufL, short* bufR, size_t start,
for (size_t i=start; i<start+len; i++) {
if (on) {
pos-=PCSPKR_DIVIDER;
if (pos>freq) pos=freq;
while (pos<0) {
if (freq<1) {
pos=1;
@ -95,6 +97,7 @@ void DivPlatformPCSpeaker::acquire_piezo(short* bufL, short* bufR, size_t start,
for (size_t i=start; i<start+len; i++) {
if (on) {
pos-=PCSPKR_DIVIDER;
if (pos>freq) pos=freq;
while (pos<0) {
if (freq<1) {
pos=1;

View file

@ -25,8 +25,8 @@
class DivPlatformPCSpeaker: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, note;
unsigned char ins, duty, sweep;
int freq, baseFreq, pitch, note, ins;
unsigned char duty, sweep;
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac;
signed char vol, outVol, wave;
DivMacroInt std;

View file

@ -25,8 +25,7 @@
class DivPlatformPET: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, note;
unsigned char ins;
int freq, baseFreq, pitch, note, ins;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
int vol, outVol, wave;
unsigned char sreg;

View file

@ -30,8 +30,7 @@ class DivPlatformQSound: public DivDispatch {
int freq, baseFreq, pitch;
unsigned short audLen;
unsigned int audPos;
int sample, wave;
unsigned char ins;
int sample, wave, ins;
int note;
int panning;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave;

View file

@ -35,8 +35,8 @@ class DivPlatformSAA1099: public DivDispatch {
protected:
struct Channel {
unsigned char freqH, freqL;
int freq, baseFreq, pitch, note;
unsigned char ins, psgMode;
int freq, baseFreq, pitch, note, ins;
unsigned char psgMode;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
int vol, outVol;

View file

@ -29,8 +29,7 @@ class DivPlatformSegaPCM: public DivDispatch {
struct Channel {
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, note;
unsigned char ins;
int freq, baseFreq, pitch, note, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM;
int vol, outVol;

View file

@ -86,11 +86,13 @@ void DivPlatformSMS::tick(bool sysTick) {
}
if (i==3) {
if (chan[i].std.duty.had) {
snNoiseMode=chan[i].std.duty.val;
if (chan[i].std.duty.val<2) {
chan[3].freqChanged=false;
if (chan[i].std.duty.val!=snNoiseMode || parent->song.snDutyReset) {
snNoiseMode=chan[i].std.duty.val;
if (chan[i].std.duty.val<2) {
chan[3].freqChanged=false;
}
updateSNMode=true;
}
updateSNMode=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {

View file

@ -26,8 +26,7 @@
class DivPlatformSMS: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, note, actualNote;
unsigned char ins;
int freq, baseFreq, pitch, note, actualNote, ins;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
signed char vol, outVol;
DivMacroInt std;

View file

@ -226,12 +226,14 @@ void DivPlatformSwan::tick(bool sysTick) {
}
}
if (chan[3].std.duty.had) {
noise=chan[3].std.duty.val;
if (noise>0) {
rWrite(0x0e,((noise-1)&0x07)|0x18);
sndCtrl|=0x80;
} else {
sndCtrl&=~0x80;
if (noise!=chan[3].std.duty.val) {
noise=chan[3].std.duty.val;
if (noise>0) {
rWrite(0x0e,((noise-1)&0x07)|0x18);
sndCtrl|=0x80;
} else {
sndCtrl&=~0x80;
}
}
}
rWrite(0x10,sndCtrl);

View file

@ -28,8 +28,8 @@
class DivPlatformSwan: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, note;
unsigned char ins, pan;
int freq, baseFreq, pitch, note, ins;
unsigned char pan;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
int vol, outVol, wave;
DivMacroInt std;

View file

@ -27,8 +27,8 @@
class DivPlatformTIA: public DivDispatch {
protected:
struct Channel {
int freq, baseFreq, pitch, note;
unsigned char ins, shape;
int freq, baseFreq, pitch, note, ins;
unsigned char shape;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
int vol, outVol;

View file

@ -35,8 +35,7 @@ class DivPlatformTX81Z: public DivDispatch {
DivInstrumentFM state;
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, note;
unsigned char ins;
int freq, baseFreq, pitch, note, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset;
int vol, outVol;

View file

@ -29,8 +29,8 @@ struct VERA_PCM;
class DivPlatformVERA: public DivDispatch {
protected:
struct Channel {
int freq, baseFreq, pitch, note;
unsigned char ins, pan;
int freq, baseFreq, pitch, note, ins;
unsigned char pan;
bool active, freqChanged, inPorta;
int vol, outVol;
unsigned accum;

View file

@ -27,8 +27,8 @@
class DivPlatformVIC20: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, note;
unsigned char ins, pan;
int freq, baseFreq, pitch, note, ins;
unsigned char pan;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
int vol, outVol, wave, waveWriteCycle;
DivMacroInt std;

View file

@ -31,8 +31,8 @@ class DivPlatformVRC6: public DivDispatch {
int freq, baseFreq, pitch, note;
int dacPeriod, dacRate, dacOut;
unsigned int dacPos;
int dacSample;
unsigned char ins, duty;
int dacSample, ins;
unsigned char duty;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, pcm, furnaceDac;
signed char vol, outVol;
DivMacroInt std;

View file

@ -43,8 +43,8 @@ class DivPlatformYM2610: public DivDispatch {
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, note;
unsigned char ins, psgMode, autoEnvNum, autoEnvDen;
int freq, baseFreq, pitch, note, ins;
unsigned char psgMode, autoEnvNum, autoEnvDen;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
int vol, outVol;

View file

@ -35,8 +35,8 @@ class DivPlatformYM2610B: public DivDispatch {
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, note;
unsigned char ins, psgMode, autoEnvNum, autoEnvDen;
int freq, baseFreq, pitch, note, ins;
unsigned char psgMode, autoEnvNum, autoEnvDen;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
int vol, outVol;

View file

@ -25,8 +25,7 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B {
struct OpChannel {
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch;
unsigned char ins;
int freq, baseFreq, pitch, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
int vol;

View file

@ -25,8 +25,7 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 {
struct OpChannel {
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch;
unsigned char ins;
int freq, baseFreq, pitch, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
int vol;

View file

@ -144,7 +144,9 @@ String SafeReader::readString(size_t stlen) {
logD("SR: reading string len %d at %x",stlen,curSeek);
#endif
size_t curPos=0;
while (curPos<stlen) {
if (isEOF()) throw EndOfFileException(this, len);
while (!isEOF() && curPos<stlen) {
unsigned char c=readC();
if (c!=0) ret.push_back(c);
curPos++;
@ -155,8 +157,48 @@ String SafeReader::readString(size_t stlen) {
String SafeReader::readString() {
String ret;
unsigned char c;
while ((c=readC())!=0) {
if (isEOF()) throw EndOfFileException(this, len);
while (!isEOF() && (c=readC())!=0) {
ret.push_back(c);
}
return ret;
}
String SafeReader::readStringLine() {
String ret;
unsigned char c;
if (isEOF()) throw EndOfFileException(this, len);
while (!isEOF() && (c = readC()) != 0) {
if (c=='\r'||c=='\n') {
break;
}
ret.push_back(c);
}
return ret;
}
String SafeReader::readStringToken(unsigned char delim) {
String ret;
unsigned char c;
if (isEOF()) throw EndOfFileException(this, len);
while (!isEOF() && (c=readC())!=0) {
if (c == '\r' || c == '\n') {
break;
}
if (c == delim) {
if (ret.length() == 0) {
continue;
}
break;
}
ret.push_back(c);
}
return ret;
}
String SafeReader::readStringToken() {
return readStringToken(' ');
}

View file

@ -66,6 +66,10 @@ class SafeReader {
double readD_BE();
String readString();
String readString(size_t len);
String readStringLine();
String readStringToken(unsigned char delim);
String readStringToken();
inline bool isEOF() { return curSeek >= len; };
SafeReader(void* b, size_t l):
buf((unsigned char*)b),

View file

@ -19,21 +19,57 @@
#include "song.h"
void DivSong::clearSongData() {
for (int i=0; i<DIV_MAX_CHANS; i++) {
pat[i].wipePatterns();
}
memset(orders.ord,0,DIV_MAX_CHANS*256);
ordersLen=1;
}
void DivSong::clearInstruments() {
for (DivInstrument* i: ins) {
delete i;
}
ins.clear();
insLen=0;
}
void DivSong::clearWavetables() {
for (DivWavetable* i: wave) {
delete i;
}
wave.clear();
waveLen=0;
}
void DivSong::clearSamples() {
for (DivSample* i: sample) {
delete i;
}
sample.clear();
sampleLen=0;
}
void DivSong::unload() {
for (DivInstrument* i: ins) {
delete i;
}
ins.clear();
insLen=0;
for (DivWavetable* i: wave) {
delete i;
}
wave.clear();
waveLen=0;
for (DivSample* i: sample) {
delete i;
}
sample.clear();
sampleLen=0;
for (int i=0; i<DIV_MAX_CHANS; i++) {
pat[i].wipePatterns();

View file

@ -325,6 +325,8 @@ struct DivSong {
bool ignoreDACModeOutsideIntendedChannel;
bool e1e2AlsoTakePriority;
bool newSegaPCM;
bool fbPortaPause;
bool snDutyReset;
DivOrders orders;
std::vector<DivInstrument*> ins;
@ -339,6 +341,27 @@ struct DivSong {
DivWavetable nullWave;
DivSample nullSample;
/**
* clear orders and patterns.
*/
void clearSongData();
/**
* clear instruments.
*/
void clearInstruments();
/**
* clear wavetables.
*/
void clearWavetables();
/**
* clear samples.
*/
void clearSamples();
/**
* unloads the song, freeing all memory associated with it.
* use before destroying the object.
@ -407,7 +430,9 @@ struct DivSong {
sharedExtStat(true),
ignoreDACModeOutsideIntendedChannel(false),
e1e2AlsoTakePriority(false),
newSegaPCM(true) {
newSegaPCM(true),
fbPortaPause(false),
snDutyReset(false) {
for (int i=0; i<32; i++) {
system[i]=DIV_SYSTEM_NULL;
systemVol[i]=64;