diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 64577efef..14db71853 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3,6 +3,7 @@ #include "safeReader.h" #include "../ta-log.h" #include "../audio/sdl.h" +#include #ifdef HAVE_JACK #include "../audio/jack.h" #endif @@ -20,6 +21,7 @@ #include #include #include +#include void process(void* u, float** in, float** out, int inChans, int outChans, unsigned int size) { ((DivEngine*)u)->nextBuf(in,out,inChans,outChans,size); @@ -1045,6 +1047,12 @@ static double samplePitches[11]={ 2, 3, 4, 5, 6 }; +void DivEngine::renderSamplesP() { + isBusy.lock(); + renderSamples(); + isBusy.unlock(); +} + void DivEngine::renderSamples() { if (jediTable==NULL) { jediTable=new int[16*49]; @@ -1063,6 +1071,10 @@ void DivEngine::renderSamples() { delete[] s->adpcmRendData; } s->rendLength=(double)s->length/samplePitches[s->pitch]; + if (s->rendLength==0) { + s->adpcmRendLength=0; + continue; + } s->rendData=new short[s->rendLength]; size_t adpcmLen=((s->rendLength>>1)+255)&0xffffff00; s->adpcmRendLength=adpcmLen; @@ -1227,6 +1239,135 @@ bool DivEngine::isPlaying() { return playing; } +int DivEngine::addInstrument() { + isBusy.lock(); + DivInstrument* ins=new DivInstrument; + int insCount=(int)song.ins.size(); + ins->name=fmt::sprintf("Instrument %d",insCount); + song.ins.push_back(ins); + song.insLen=insCount+1; + isBusy.unlock(); + return insCount; +} + +void DivEngine::delInstrument(int index) { + isBusy.lock(); + if (index>=0 && index<(int)song.ins.size()) { + delete song.ins[index]; + song.ins.erase(song.ins.begin()+index); + song.insLen=song.ins.size(); + } + isBusy.unlock(); +} + +int DivEngine::addWave() { + isBusy.lock(); + DivWavetable* wave=new DivWavetable; + int waveCount=(int)song.wave.size(); + song.wave.push_back(wave); + song.waveLen=waveCount+1; + isBusy.unlock(); + return waveCount; +} + +void DivEngine::delWave(int index) { + isBusy.lock(); + if (index>=0 && index<(int)song.wave.size()) { + delete song.wave[index]; + song.wave.erase(song.wave.begin()+index); + song.waveLen=song.wave.size(); + } + isBusy.unlock(); +} + +int DivEngine::addSample() { + isBusy.lock(); + DivSample* sample=new DivSample; + int sampleCount=(int)song.sample.size(); + sample->name=fmt::sprintf("Sample %d",sampleCount); + song.sample.push_back(sample); + song.sampleLen=sampleCount+1; + renderSamples(); + isBusy.unlock(); + return sampleCount; +} + +bool DivEngine::addSampleFromFile(const char* path) { + isBusy.lock(); + SF_INFO si; + SNDFILE* f=sf_open(path,SFM_READ,&si); + if (f==NULL) { + isBusy.unlock(); + return false; + } + if (si.frames>1000000) { + sf_close(f); + isBusy.unlock(); + return false; + } + short* buf=new short[si.channels*si.frames]; + if (sf_readf_short(f,buf,si.frames)!=si.frames) { + logW("sample read size mismatch!\n"); + } + sf_close(f); + DivSample* sample=new DivSample; + int sampleCount=(int)song.sample.size(); + const char* sName=strrchr(path,'/'); + if (sName==NULL) { + sName=path; + } else { + sName++; + } + sample->name=sName; + + int index=0; + sample->length=si.frames; + sample->data=new short[si.frames]; + sample->depth=16; + sample->vol=50; + sample->pitch=5; + for (int i=0; idata[index++]=averaged; + } + delete[] buf; + // 4000, 8000, 11025, 16000, 22050, 32000 + if (si.samplerate>26000) { + sample->rate=5; + } else if (si.samplerate>18000) { + sample->rate=4; + } else if (si.samplerate>14000) { + sample->rate=3; + } else if (si.samplerate>9500) { + sample->rate=2; + } else if (si.samplerate>6000) { + sample->rate=1; + } else { + sample->rate=0; + } + + song.sample.push_back(sample); + song.sampleLen=sampleCount+1; + renderSamples(); + isBusy.unlock(); + return sampleCount; +} + +void DivEngine::delSample(int index) { + isBusy.lock(); + if (index>=0 && index<(int)song.sample.size()) { + delete song.sample[index]; + song.sample.erase(song.sample.begin()+index); + song.sampleLen=song.sample.size(); + renderSamples(); + } + isBusy.unlock(); +} + void DivEngine::setOrder(unsigned char order) { isBusy.lock(); curOrder=order; diff --git a/src/engine/engine.h b/src/engine/engine.h index 945f7c745..5c4c13ee8 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -156,6 +156,27 @@ class DivEngine { // is playing bool isPlaying(); + // add instrument + int addInstrument(); + + // delete instrument + void delInstrument(int index); + + // add wavetable + int addWave(); + + // delete wavetable + void delWave(int index); + + // add sample + int addSample(); + + // add sample from file + bool addSampleFromFile(const char* path); + + // delete sample + void delSample(int index); + // go to order void setOrder(unsigned char order); @@ -171,6 +192,9 @@ class DivEngine { // set the view mode. void setView(DivStatusView which); + // public render samples + void renderSamplesP(); + // init dispatch void initDispatch(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 300a8e2a1..4f78b40da 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -23,6 +23,7 @@ const int _ZERO=0; const int _ONE=1; const int _THREE=3; const int _SEVEN=7; +const int _TEN=10; const int _FIFTEEN=15; const int _THIRTY_ONE=31; const int _SIXTY_FOUR=64; @@ -84,6 +85,14 @@ const char* noteNames[120]={ "C-9", "C#9", "D-9", "D#9", "E-9", "F-9", "F#9", "G-9", "G#9", "A-9", "A#9", "B-9" }; +const char* rateLabel[6]={ + "4000Hz", "8000Hz", "11025Hz", "16000Hz", "22050Hz", "32000Hz" +}; + +const char* pitchLabel[11]={ + "1/6", "1/5", "1/4", "1/3", "1/2", "1x", "2x", "3x", "4x", "5x", "6x" +}; + void FurnaceGUI::bindEngine(DivEngine* eng) { e=eng; } @@ -263,6 +272,25 @@ void FurnaceGUI::drawOrders() { void FurnaceGUI::drawInsList() { if (!insListOpen) return; if (ImGui::Begin("Instruments",&insListOpen)) { + if (ImGui::Button("Add##InsAdd")) { + curIns=e->addInstrument(); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("InsUp",ImGuiDir_Up)) { + // TODO + } + ImGui::SameLine(); + if (ImGui::ArrowButton("InsDown",ImGuiDir_Down)) { + // TODO + } + ImGui::SameLine(); + if (ImGui::Button("Delete##InsDelete")) { + e->delInstrument(curIns); + if (curIns>=(int)e->song.ins.size()) { + curIns--; + } + } + ImGui::Separator(); for (int i=0; i<(int)e->song.ins.size(); i++) { DivInstrument* ins=e->song.ins[i]; if (ImGui::Selectable(fmt::sprintf("%d: %s##_INS%d\n",i,ins->name,i).c_str(),curIns==i)) { @@ -282,7 +310,7 @@ void FurnaceGUI::drawInsList() { void FurnaceGUI::drawInsEdit() { if (!insEditOpen) return; if (ImGui::Begin("Instrument Editor",&insEditOpen,ImGuiWindowFlags_NoDocking)) { - if (curIns>=(int)e->song.ins.size()) { + if (curIns<0 || curIns>=(int)e->song.ins.size()) { ImGui::Text("no instrument selected"); } else { DivInstrument* ins=e->song.ins[curIns]; @@ -585,6 +613,25 @@ void FurnaceGUI::drawWaveList() { if (!waveListOpen) return; float wavePreview[256]; if (ImGui::Begin("Wavetables",&waveListOpen)) { + if (ImGui::Button("Add##WaveAdd")) { + curWave=e->addWave(); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("WaveUp",ImGuiDir_Up)) { + // TODO + } + ImGui::SameLine(); + if (ImGui::ArrowButton("WaveDown",ImGuiDir_Down)) { + // TODO + } + ImGui::SameLine(); + if (ImGui::Button("Delete##WaveDelete")) { + e->delWave(curWave); + if (curWave>=(int)e->song.wave.size()) { + curWave--; + } + } + ImGui::Separator(); for (int i=0; i<(int)e->song.wave.size(); i++) { DivWavetable* wave=e->song.wave[i]; for (int i=0; ilen; i++) { @@ -620,6 +667,33 @@ void FurnaceGUI::drawWaveEdit() { void FurnaceGUI::drawSampleList() { if (!sampleListOpen) return; if (ImGui::Begin("Samples",&sampleListOpen)) { + if (ImGui::Button("Add##SampleAdd")) { + curSample=e->addSample(); + } + ImGui::SameLine(); + if (ImGui::Button("Load##SampleLoad")) { + openFileDialog(GUI_FILE_SAMPLE_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button("Save##SampleSave")) { + openFileDialog(GUI_FILE_SAMPLE_SAVE); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("SampleUp",ImGuiDir_Up)) { + // TODO + } + ImGui::SameLine(); + if (ImGui::ArrowButton("SampleDown",ImGuiDir_Down)) { + // TODO + } + ImGui::SameLine(); + if (ImGui::Button("Delete##SampleDelete")) { + e->delSample(curSample); + if (curSample>=(int)e->song.sample.size()) { + curSample--; + } + } + ImGui::Separator(); for (int i=0; i<(int)e->song.sample.size(); i++) { DivSample* sample=e->song.sample[i]; if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d\n",i,sample->name,i).c_str(),curSample==i)) { @@ -639,6 +713,27 @@ void FurnaceGUI::drawSampleList() { void FurnaceGUI::drawSampleEdit() { if (!sampleEditOpen) return; if (ImGui::Begin("Sample Editor",&sampleEditOpen)) { + if (curSample<0 || curSample>=(int)e->song.sample.size()) { + ImGui::Text("no sample selected"); + } else { + DivSample* sample=e->song.sample[curSample]; + ImGui::InputText("Name",&sample->name); + if (ImGui::SliderInt("Rate",&sample->rate,0,5,rateLabel[sample->rate])) { + if (sample->rate<0) sample->rate=0; + if (sample->rate>5) sample->rate=5; + } + if (ImGui::SliderScalar("Volume",ImGuiDataType_S8,&sample->vol,&_ZERO,&_ONE_HUNDRED,fmt::sprintf("%d%%%%",sample->vol*2).c_str())) { + if (sample->vol<0) sample->vol=0; + if (sample->vol>100) sample->vol=100; + } + if (ImGui::SliderScalar("Pitch",ImGuiDataType_S8,&sample->pitch,&_ZERO,&_TEN,pitchLabel[sample->pitch])) { + if (sample->pitch<0) sample->pitch=0; + if (sample->pitch>10) sample->pitch=10; + } + if (ImGui::Button("Apply")) { + e->renderSamplesP(); + } + } } if (ImGui::IsWindowFocused()) curWindow=GUI_WINDOW_SAMPLE_EDIT; ImGui::End(); @@ -1038,6 +1133,24 @@ void FurnaceGUI::keyUp(SDL_Event& ev) { } +void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { + switch (type) { + case GUI_FILE_OPEN: + ImGuiFileDialog::Instance()->OpenDialog("FileDialog","Open File","DefleMask module{.dmf},.*",workingDir); + break; + case GUI_FILE_SAVE: + ImGuiFileDialog::Instance()->OpenDialog("FileDialog","Save File","DefleMask module{.dmf}",workingDir); + break; + case GUI_FILE_SAMPLE_OPEN: + ImGuiFileDialog::Instance()->OpenDialog("FileDialog","Load Sample","Wave file{.wav},.*",workingDir); + break; + case GUI_FILE_SAMPLE_SAVE: + ImGuiFileDialog::Instance()->OpenDialog("FileDialog","Save Sample","Wave file{.wav}",workingDir); + break; + } + curFileDialog=type; +} + #define FURNACE_ZLIB_COMPRESS int FurnaceGUI::save(String path) { @@ -1234,14 +1347,13 @@ bool FurnaceGUI::loop() { if (ImGui::BeginMenu("file")) { ImGui::MenuItem("new"); if (ImGui::MenuItem("open...")) { - ImGuiFileDialog::Instance()->OpenDialog("FileDialog","Open File","DefleMask module{.dmf},.*",workingDir); - isSaving=false; + openFileDialog(GUI_FILE_OPEN); + } ImGui::Separator(); ImGui::MenuItem("save"); if (ImGui::MenuItem("save as...")) { - ImGuiFileDialog::Instance()->OpenDialog("FileDialog","Save File","DefleMask module{.dmf}",workingDir); - isSaving=true; + openFileDialog(GUI_FILE_SAVE); } ImGui::Separator(); ImGui::MenuItem("change platform..."); @@ -1305,19 +1417,32 @@ bool FurnaceGUI::loop() { if (ImGuiFileDialog::Instance()->IsOk()) { fileName=ImGuiFileDialog::Instance()->GetFilePathName(); if (fileName!="") { - if (isSaving) { + if (curFileDialog==GUI_FILE_SAVE) { if (fileName.size()<4 || fileName.rfind(".dmf")!=fileName.size()-4) { fileName+=".dmf"; } } - String copyOfName=fileName; - if (isSaving) { - printf("saving: %s\n",copyOfName.c_str()); - save(copyOfName); - isSaving=false; - } else { - load(copyOfName); + if (curFileDialog==GUI_FILE_SAMPLE_SAVE) { + if (fileName.size()<4 || fileName.rfind(".wav")!=fileName.size()-4) { + fileName+=".wav"; + } } + String copyOfName=fileName; + switch (curFileDialog) { + case GUI_FILE_OPEN: + load(copyOfName); + break; + case GUI_FILE_SAVE: + printf("saving: %s\n",copyOfName.c_str()); + save(copyOfName); + break; + case GUI_FILE_SAMPLE_OPEN: + e->addSampleFromFile(copyOfName.c_str()); + break; + case GUI_FILE_SAMPLE_SAVE: + break; + } + curFileDialog=GUI_FILE_OPEN; } } workingDir=ImGuiFileDialog::Instance()->GetCurrentPath(); @@ -1403,7 +1528,7 @@ bool FurnaceGUI::init() { FurnaceGUI::FurnaceGUI(): e(NULL), quit(false), - isSaving(false), + curFileDialog(GUI_FILE_OPEN), scrW(1280), scrH(800), dpiScale(1), diff --git a/src/gui/gui.h b/src/gui/gui.h index 237fc3483..6fc0badff 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -47,6 +47,13 @@ enum FurnaceGUIWindows { GUI_WINDOW_SAMPLE_EDIT }; +enum FurnaceGUIFileDialogs { + GUI_FILE_OPEN, + GUI_FILE_SAVE, + GUI_FILE_SAMPLE_OPEN, + GUI_FILE_SAMPLE_SAVE +}; + struct SelectionPoint { int xCoarse, xFine; int y; @@ -62,7 +69,9 @@ class FurnaceGUI { String workingDir, fileName; - bool quit, isSaving; + bool quit; + + FurnaceGUIFileDialogs curFileDialog; int scrW, scrH; @@ -124,6 +133,7 @@ class FurnaceGUI { void keyDown(SDL_Event& ev); void keyUp(SDL_Event& ev); + void openFileDialog(FurnaceGUIFileDialogs type); int save(String path); int load(String path);