diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index cd7eb3ea2..3352a8737 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -19,6 +19,7 @@ #include "dataErrors.h" #include "song.h" +#include #define _USE_MATH_DEFINES #include "engine.h" #include "instrument.h" @@ -2275,6 +2276,12 @@ bool DivEngine::switchMaster() { return true; } +void DivEngine::synchronized(const std::function& what) { + isBusy.lock(); + what(); + isBusy.unlock(); +} + TAAudioDesc& DivEngine::getAudioDescWant() { return want; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 28e2a8702..ba6a770e5 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -25,6 +25,7 @@ #include "safeWriter.h" #include "../audio/taAudio.h" #include "blip_buf.h" +#include #include #include #include @@ -244,7 +245,6 @@ class DivEngine { bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal); bool perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal); void recalcChans(); - void renderSamples(); void reset(); void playSub(bool preserveDrift, int goalRow=0); @@ -587,6 +587,9 @@ class DivEngine { // get register cheatsheet const char** getRegisterSheet(int sys); + // UNSAFE render samples - only execute when locked + void renderSamples(); + // public render samples void renderSamplesP(); @@ -614,6 +617,9 @@ class DivEngine { // switch master bool switchMaster(); + // perform secure/sync operation + void synchronized(const std::function& what); + // get audio desc want TAAudioDesc& getAudioDescWant(); diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 04bdb91eb..53fa42fe2 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -149,6 +149,35 @@ bool DivSample::init(unsigned int count) { return true; } +bool DivSample::resize(unsigned int count) { + if (depth==8) { + if (data8!=NULL) { + signed char* oldData8=data8; + data8=NULL; + initInternal(8,count); + memcpy(data8,oldData8,MIN(count,samples)); + delete[] oldData8; + } else { + initInternal(8,count); + } + samples=count; + return true; + } else if (depth==16) { + if (data16!=NULL) { + short* oldData16=data16; + data16=NULL; + initInternal(16,count); + memcpy(data16,oldData16,sizeof(short)*MIN(count,samples)); + delete[] oldData16; + } else { + initInternal(16,count); + } + samples=count; + return true; + } + return false; +} + void DivSample::render() { // step 1: convert to 16-bit if needed if (depth!=16) { diff --git a/src/engine/sample.h b/src/engine/sample.h index 3598d67a7..026ca290e 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -76,6 +76,14 @@ struct DivSample { */ bool init(unsigned int count); + /** + * resize sample data. make sure the sample has been initialized before doing so. + * @warning do not attempt to resize a sample outside of a synchronized block! + * @param count number of samples. + * @return whether it was successful. + */ + bool resize(unsigned int count); + /** * initialize the rest of sample formats for this sample. */ diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 3844ea383..78deccd3e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -7096,7 +7096,15 @@ FurnaceGUI::FurnaceGUI(): randomMode(false), oldOrdersLen(0), sampleZoom(1.0), - samplePos(0) { + samplePos(0), + resizeSize(1024), + resampleTarget(32000), + resampleStrat(5), + amplifyVol(100.0), + sampleSelStart(-1), + sampleSelEnd(-1), + sampleDragActive(false), + sampleDragMode(false) { // octave 1 /* diff --git a/src/gui/gui.h b/src/gui/gui.h index 73d34a1af..568ea8ed0 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -738,6 +738,12 @@ class FurnaceGUI { // sample editor specific double sampleZoom; int samplePos; + int resizeSize; + double resampleTarget; + int resampleStrat; + float amplifyVol; + int sampleSelStart, sampleSelEnd; + bool sampleDragActive, sampleDragMode; // visualizer float keyHit[DIV_MAX_CHANS]; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index ef7610496..11795e665 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -112,4 +112,13 @@ const char* sampleDepths[17]={ NULL, NULL, "16-bit PCM" +}; + +const char* resampleStrats[]={ + "none", + "linear", + "cubic spline", + "blep synthesis", + "sinc", + "best possible" }; \ No newline at end of file diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 0bf621180..ee49c1daa 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -24,4 +24,5 @@ extern const char* noteNames[180]; extern const char* noteNamesG[180]; extern const char* pitchLabel[11]; extern const char* insTypes[]; -extern const char* sampleDepths[17]; \ No newline at end of file +extern const char* sampleDepths[17]; +extern const char* resampleStrats[]; \ No newline at end of file diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index d2003d41d..07afe23ff 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -50,7 +50,10 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::BeginTable("SampleProps",4,ImGuiTableFlags_SizingStretchSame)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); - if (ImGui::BeginCombo("Type",sampleType.c_str())) { + ImGui::Text("Type"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##SampleType",sampleType.c_str())) { for (int i=0; i<17; i++) { if (sampleDepths[i]==NULL) continue; if (ImGui::Selectable(sampleDepths[i])) { @@ -66,13 +69,19 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::TableNextColumn(); - if (ImGui::InputInt("Rate (Hz)",&sample->rate,10,200)) { + ImGui::Text("Rate (Hz)"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SampleRate",&sample->rate,10,200)) { if (sample->rate<100) sample->rate=100; if (sample->rate>96000) sample->rate=96000; } ImGui::TableNextColumn(); - if (ImGui::InputInt("Pitch of C-4 (Hz)",&sample->centerRate,10,200)) { + ImGui::Text("C-4 (Hz)"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SampleCenter",&sample->centerRate,10,200)) { if (sample->centerRate<100) sample->centerRate=100; if (sample->centerRate>65535) sample->centerRate=65535; } @@ -89,6 +98,7 @@ void FurnaceGUI::drawSampleEdit() { } if (doLoop) { ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { sample->loopStart=0; @@ -123,6 +133,133 @@ void FurnaceGUI::drawSampleEdit() { }*/ ImGui::Separator(); + ImGui::Button(ICON_FA_I_CURSOR "##SSelect"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Edit mode: Select"); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_PENCIL "##SDraw"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Edit mode: Draw"); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); + ImGui::SameLine(); + ImGui::Button(ICON_FA_ARROWS_H "##SResize"); + if (ImGui::IsItemClicked()) { + resizeSize=sample->samples; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Resize"); + } + if (ImGui::BeginPopupContextItem("SResizeOpt",ImGuiPopupFlags_MouseButtonLeft)) { + if (ImGui::InputInt("Samples",&resizeSize,1,64)) { + if (resizeSize<0) resizeSize=0; + if (resizeSize>16777215) resizeSize=16777215; + } + if (ImGui::Button("Resize")) { + e->synchronized([this,sample]() { + sample->resize(resizeSize); + e->renderSamples(); + }); + updateSampleTex=true; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } else { + resizeSize=sample->samples; + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_EXPAND "##SResample"); + if (ImGui::IsItemClicked()) { + resampleTarget=sample->rate; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Resample"); + } + if (ImGui::BeginPopupContextItem("SResampleOpt",ImGuiPopupFlags_MouseButtonLeft)) { + ImGui::Text("Rate"); + if (ImGui::InputDouble("##SRRate",&resampleTarget,1.0,50.0,"%g")) { + if (resampleTarget<0) resampleTarget=0; + if (resampleTarget>96000) resampleTarget=96000; + } + ImGui::SameLine(); + if (ImGui::Button("0.5x")) { + resampleTarget*=0.5; + } + ImGui::SameLine(); + if (ImGui::Button("==")) { + resampleTarget=sample->rate; + } + ImGui::SameLine(); + if (ImGui::Button("2.0x")) { + resampleTarget*=2.0; + } + double factor=resampleTarget/(double)sample->rate; + if (ImGui::InputDouble("Factor",&factor,0.125,0.5,"%g")) { + resampleTarget=(double)sample->rate*factor; + if (resampleTarget<0) resampleTarget=0; + if (resampleTarget>96000) resampleTarget=96000; + } + ImGui::Combo("Filter",&resampleStrat,resampleStrats,6); + ImGui::Button("Resample"); + ImGui::EndPopup(); + } else { + resampleTarget=sample->rate; + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); + ImGui::SameLine(); + ImGui::Button(ICON_FA_VOLUME_UP "##SAmplify"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Amplify"); + } + if (ImGui::BeginPopupContextItem("SAmplifyOpt",ImGuiPopupFlags_MouseButtonLeft)) { + ImGui::Text("Volume"); + if (ImGui::InputFloat("##SRVolume",&lifyVol,10.0,50.0,"%g%%")) { + if (amplifyVol<0) amplifyVol=0; + if (amplifyVol>10000) amplifyVol=10000; + } + ImGui::SameLine(); + ImGui::Text("(%.1fdB)",20.0*log10(amplifyVol/100.0f)); + ImGui::Button("Apply"); + ImGui::EndPopup(); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_ARROWS_V "##SNormalize"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Normalize"); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_ERASER "##SSilence"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Apply silence"); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); + ImGui::SameLine(); + ImGui::Button(ICON_FA_BACKWARD "##SReverse"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Reverse"); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_SORT_AMOUNT_ASC "##SInvert"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Invert"); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_LEVEL_DOWN "##SSign"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Signed/unsigned exchange"); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_INDUSTRY "##SFilter"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Apply filter"); + } + + ImGui::Separator(); + ImVec2 avail=ImGui::GetContentRegionAvail(); avail.y-=ImGui::GetFontSize()+ImGui::GetStyle().ItemSpacing.y; int availX=avail.x; @@ -193,7 +330,10 @@ void FurnaceGUI::drawSampleEdit() { updateSampleTex=false; } - ImGui::Image(sampleTex,avail); + ImGui::ImageButton(sampleTex,avail,ImVec2(0,0),ImVec2(1,1),0); + if (ImGui::IsItemClicked()) { + printf("drawing\n"); + } ImGui::Text("A workaround! Pretty cool huh?"); }