add ability to save ins/wave as .dmp/.dmw

also saving wavetables as raw data
This commit is contained in:
tildearrow 2022-08-13 15:43:13 -05:00
parent 041a76ad81
commit 02fb5abc02
5 changed files with 282 additions and 9 deletions

View File

@ -1150,3 +1150,148 @@ bool DivInstrument::save(const char* path) {
w->finish();
return true;
}
bool DivInstrument::saveDMP(const char* path) {
SafeWriter* w=new SafeWriter();
w->init();
// write version
w->writeC(11);
// guess the system
switch (type) {
case DIV_INS_FM:
// we can't tell between Genesis, Neo Geo and Arcade ins type yet
w->writeC(0x02);
w->writeC(1);
break;
case DIV_INS_STD:
// we can't tell between SMS and NES ins type yet
w->writeC(0x03);
w->writeC(0);
break;
case DIV_INS_GB:
w->writeC(0x04);
w->writeC(0);
break;
case DIV_INS_C64:
w->writeC(0x07);
w->writeC(0);
break;
case DIV_INS_PCE:
w->writeC(0x06);
w->writeC(0);
break;
case DIV_INS_OPLL:
// ???
w->writeC(0x13);
w->writeC(1);
break;
case DIV_INS_OPZ:
// data will be lost
w->writeC(0x08);
w->writeC(1);
break;
case DIV_INS_FDS:
// ???
w->writeC(0x06);
w->writeC(0);
break;
default:
// not supported by .dmp
w->finish();
return false;
}
if (type==DIV_INS_FM || type==DIV_INS_OPLL || type==DIV_INS_OPZ) {
w->writeC(fm.fms);
w->writeC(fm.fb);
w->writeC(fm.alg);
w->writeC(fm.ams);
// TODO: OPLL params
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=fm.op[i];
w->writeC(op.mult);
w->writeC(op.tl);
w->writeC(op.ar);
w->writeC(op.dr);
w->writeC(op.sl);
w->writeC(op.rr);
w->writeC(op.am);
w->writeC(op.rs);
w->writeC(op.dt|(op.dt2<<4));
w->writeC(op.d2r);
w->writeC(op.ssgEnv);
}
} else {
if (type!=DIV_INS_GB) {
w->writeC(std.volMacro.len);
for (int i=0; i<std.volMacro.len; i++) {
w->writeI(std.volMacro.val[i]);
}
if (std.volMacro.len>0) w->writeC(std.volMacro.loop);
}
w->writeC(std.arpMacro.len);
for (int i=0; i<std.arpMacro.len; i++) {
w->writeI(std.arpMacro.val[i]+12);
}
if (std.arpMacro.len>0) w->writeC(std.arpMacro.loop);
w->writeC(std.arpMacro.mode);
w->writeC(std.dutyMacro.len);
for (int i=0; i<std.dutyMacro.len; i++) {
w->writeI(std.dutyMacro.val[i]+12);
}
if (std.dutyMacro.len>0) w->writeC(std.dutyMacro.loop);
w->writeC(std.waveMacro.len);
for (int i=0; i<std.waveMacro.len; i++) {
w->writeI(std.waveMacro.val[i]+12);
}
if (std.waveMacro.len>0) w->writeC(std.waveMacro.loop);
if (type==DIV_INS_C64) {
w->writeC(c64.triOn);
w->writeC(c64.sawOn);
w->writeC(c64.pulseOn);
w->writeC(c64.noiseOn);
w->writeC(c64.a);
w->writeC(c64.d);
w->writeC(c64.s);
w->writeC(c64.r);
w->writeC((c64.duty*100)/4095);
w->writeC(c64.ringMod);
w->writeC(c64.oscSync);
w->writeC(c64.toFilter);
w->writeC(c64.volIsCutoff);
w->writeC(c64.initFilter);
w->writeC(c64.res);
w->writeC((c64.cut*100)/2047);
w->writeC(c64.hp);
w->writeC(c64.lp);
w->writeC(c64.bp);
w->writeC(c64.ch3off);
}
if (type==DIV_INS_GB) {
w->writeC(gb.envVol);
w->writeC(gb.envDir);
w->writeC(gb.envLen);
w->writeC(gb.soundLen);
}
}
FILE* outFile=ps_fopen(path,"wb");
if (outFile==NULL) {
logE("could not save instrument: %s!",strerror(errno));
w->finish();
return false;
}
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
logW("did not write entire instrument!");
}
fclose(outFile);
w->finish();
return true;
}

View File

@ -500,6 +500,13 @@ struct DivInstrument {
* @return whether it was successful.
*/
bool save(const char* path);
/**
* save this instrument to a file in .dmp format.
* @param path file path.
* @return whether it was successful.
*/
bool saveDMP(const char* path);
DivInstrument():
name(""),
type(DIV_INS_FM) {

View File

@ -92,3 +92,65 @@ bool DivWavetable::save(const char* path) {
w->finish();
return true;
}
bool DivWavetable::saveDMW(const char* path) {
SafeWriter* w=new SafeWriter();
w->init();
// write width
w->writeI(len);
// check height
w->writeC(max);
if (max==255) {
// write as new format (because 0xff means that)
w->writeC(1); // format version
w->writeC(max); // actual height
// waveform data
for (int i=0; i<len; i++) {
w->writeI(data[i]&0xff);
}
} else {
// write as old format
for (int i=0; i<len; i++) {
w->writeC(data[i]);
}
}
FILE* outFile=ps_fopen(path,"wb");
if (outFile==NULL) {
logE("could not save wavetable: %s!",strerror(errno));
w->finish();
return false;
}
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
logW("did not write entire wavetable!");
}
fclose(outFile);
w->finish();
return true;
}
bool DivWavetable::saveRaw(const char* path) {
SafeWriter* w=new SafeWriter();
w->init();
// waveform data
for (int i=0; i<len; i++) {
w->writeC(data[i]);
}
FILE* outFile=ps_fopen(path,"wb");
if (outFile==NULL) {
logE("could not save wavetable: %s!",strerror(errno));
w->finish();
return false;
}
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
logW("did not write entire wavetable!");
}
fclose(outFile);
w->finish();
return true;
}

View File

@ -46,6 +46,20 @@ struct DivWavetable {
* @return whether it was successful.
*/
bool save(const char* path);
/**
* save this wavetable to a file in .dmw format.
* @param path file path.
* @return whether it was successful.
*/
bool saveDMW(const char* path);
/**
* save this wavetable to a file in raw format.
* @param path file path.
* @return whether it was successful.
*/
bool saveRaw(const char* path);
DivWavetable():
len(32),
min(0),
@ -56,4 +70,4 @@ struct DivWavetable {
}
};
#endif
#endif

View File

@ -1313,8 +1313,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
if (!dirExists(workingDirIns)) workingDirIns=getHomeDir();
hasOpened=fileDialog->openSave(
"Save Instrument",
{"Furnace instrument", "*.fui"},
"Furnace instrument{.fui}",
{"Furnace instrument", "*.fui",
"DefleMask preset", "*.dmp"},
"Furnace instrument{.fui},DefleMask preset{.dmp}",
workingDirIns,
dpiScale
);
@ -1335,8 +1336,10 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
if (!dirExists(workingDirWave)) workingDirWave=getHomeDir();
hasOpened=fileDialog->openSave(
"Save Wavetable",
{"Furnace wavetable", ".fuw"},
"Furnace wavetable{.fuw}",
{"Furnace wavetable", ".fuw",
"DefleMask wavetable", ".dmw",
"raw data", ".raw"},
"Furnace wavetable{.fuw},DefleMask wavetable{.dmw},raw data{.raw}",
workingDirWave,
dpiScale
);
@ -1921,6 +1924,15 @@ void FurnaceGUI::processDrags(int dragX, int dragY) {
fileName+=fallback; \
}
#define checkExtensionTriple(x,y,z,fallback) \
String lowerCase=fileName; \
for (char& i: lowerCase) { \
if (i>='A' && i<='Z') i+='a'-'A'; \
} \
if (lowerCase.size()<4 || (lowerCase.rfind(x)!=lowerCase.size()-4 && lowerCase.rfind(y)!=lowerCase.size()-4 && lowerCase.rfind(z)!=lowerCase.size()-4)) { \
fileName+=fallback; \
}
#define drawOpMask(m) \
ImGui::PushFont(patFont); \
ImGui::PushID("om_" #m); \
@ -3365,10 +3377,21 @@ bool FurnaceGUI::loop() {
checkExtension(".wav");
}
if (curFileDialog==GUI_FILE_INS_SAVE) {
checkExtension(".fui");
// we can't tell whether the user chose .fui or .dmp in the system file picker
const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace instrument")?".fui":".dmp";
checkExtensionDual(".fui",".dmp",fallbackExt);
}
if (curFileDialog==GUI_FILE_WAVE_SAVE) {
checkExtension(".fuw");
// same thing here
const char* fallbackExt=".fuw";
if (!settings.sysFileDialog) {
if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="raw data") {
fallbackExt=".raw";
} else if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="DefleMask wavetable") {
fallbackExt=".dmw";
}
}
checkExtensionTriple(".fuw",".dmw",".raw",fallbackExt);
}
if (curFileDialog==GUI_FILE_EXPORT_VGM) {
checkExtension(".vgm");
@ -3451,12 +3474,34 @@ bool FurnaceGUI::loop() {
break;
case GUI_FILE_INS_SAVE:
if (curIns>=0 && curIns<(int)e->song.ins.size()) {
e->song.ins[curIns]->save(copyOfName.c_str());
String lowerCase=fileName;
for (char& i: lowerCase) {
if (i>='A' && i<='Z') i+='a'-'A';
}
if ((lowerCase.size()<4 || lowerCase.rfind(".dmp")!=lowerCase.size()-4)) {
e->song.ins[curIns]->save(copyOfName.c_str());
} else {
if (!e->song.ins[curIns]->saveDMP(copyOfName.c_str())) {
showError("error while saving instrument! make sure your instrument is compatible.");
}
}
}
break;
case GUI_FILE_WAVE_SAVE:
if (curWave>=0 && curWave<(int)e->song.wave.size()) {
e->song.wave[curWave]->save(copyOfName.c_str());
String lowerCase=fileName;
for (char& i: lowerCase) {
if (i>='A' && i<='Z') i+='a'-'A';
}
if (lowerCase.size()<4) {
e->song.wave[curWave]->save(copyOfName.c_str());
} else if (lowerCase.rfind(".dmw")==lowerCase.size()-4) {
e->song.wave[curWave]->saveDMW(copyOfName.c_str());
} else if (lowerCase.rfind(".raw")==lowerCase.size()-4) {
e->song.wave[curWave]->saveRaw(copyOfName.c_str());
} else {
e->song.wave[curWave]->save(copyOfName.c_str());
}
}
break;
case GUI_FILE_SAMPLE_OPEN: {