/** * Furnace Tracker - multi-system chiptune tracker * Copyright (C) 2021-2023 tildearrow and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #define _USE_MATH_DEFINES #include "gui.h" #include #include #include #include "../ta-log.h" #include "IconsFontAwesome4.h" #include "furIcons.h" #include "misc/cpp/imgui_stdlib.h" #include #include "guiConst.h" #include "sampleUtil.h" #include "util.h" const double timeDivisors[10]={ 1000.0, 500.0, 200.0, 100.0, 50.0, 20.0, 10.0, 5.0, 2.0, 1.0 }; const double timeMultipliers[13]={ 1.0, 2.0, 5.0, 10.0, 20.0, 30.0, 60.0, 2*60.0, 5*60.0, 10*60.0, 20*60.0, 30*60.0, 3600.0 }; #define CENTER_TEXT(text) \ ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x)); #define SAMPLE_WARN(_x,_text) \ if (_x.find(_text)==String::npos) { \ if (!_x.empty()) _x+='\n'; \ _x+=_text; \ } #define MAX_RATE(_name,_x) \ if (e->isPreviewingSample()) { \ if ((int)e->getSamplePreviewRate()>(int)(_x)) { \ SAMPLE_WARN(warnRate,fmt::sprintf("%s: maximum sample rate is %d",_name,(int)(_x))); \ } \ } #define MIN_RATE(_name,_x) \ if (e->isPreviewingSample()) { \ if ((int)e->getSamplePreviewRate()<(int)(_x)) { \ SAMPLE_WARN(warnRate,fmt::sprintf("%s: minimum sample rate is %d",_name,(int)(_x))); \ } \ } #define EXACT_RATE(_name,_x) \ if (e->isPreviewingSample()) { \ if ((int)e->getSamplePreviewRate()!=(int)(_x)) { \ SAMPLE_WARN(warnRate,fmt::sprintf("%s: sample rate must be %d",_name,(int)(_x))); \ } \ } void FurnaceGUI::drawSampleEdit() { if (nextWindow==GUI_WINDOW_SAMPLE_EDIT) { sampleEditOpen=true; ImGui::SetNextWindowFocus(); nextWindow=GUI_WINDOW_NOTHING; } if (!sampleEditOpen) return; if (mobileUI) { patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*canvasH)):ImVec2((0.16*canvasH)+0.5*canvasW*mobileMenuPos,0.0f)); patWindowSize=(portrait?ImVec2(canvasW,canvasH-(0.16*canvasW)-(pianoOpen?(0.4*canvasW):0.0f)):ImVec2(canvasW-(0.16*canvasH),canvasH-(pianoOpen?(0.3*canvasH):0.0f))); ImGui::SetNextWindowPos(patWindowPos); ImGui::SetNextWindowSize(patWindowSize); } if (ImGui::Begin("Sample Editor",&sampleEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curSample<0 || curSample>=(int)e->song.sample.size()) { ImGui::SetCursorPosY(ImGui::GetCursorPosY()+(ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()*2.0f)*0.5f); CENTER_TEXT("no sample selected"); ImGui::Text("no sample selected"); if (ImGui::BeginTable("noAssetCenter",3)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.5f); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5f); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TableNextColumn(); if (e->song.sample.size()>0) { if (ImGui::BeginCombo("##SampleSelect","select one...")) { if (ImGui::BeginTable("SampleSelCombo",1,ImGuiTableFlags_ScrollY)) { actualSampleList(); ImGui::EndTable(); } ImGui::EndCombo(); } ImGui::SameLine(); ImGui::TextUnformatted("or"); ImGui::SameLine(); } if (ImGui::Button("Open")) { doAction(GUI_ACTION_SAMPLE_LIST_OPEN); } ImGui::SameLine(); ImGui::TextUnformatted("or"); ImGui::SameLine(); if (ImGui::Button("Create New")) { doAction(GUI_ACTION_SAMPLE_LIST_ADD); } ImGui::TableNextColumn(); ImGui::EndTable(); } } else { DivSample* sample=e->song.sample[curSample]; String sampleType="Invalid"; if (sample->depthdepth]!=NULL) { sampleType=sampleDepths[sample->depth]; } } String loopType="Invalid"; if (sample->loopModeloopMode]!=NULL) { loopType=sampleLoopModes[sample->loopMode]; } } String sampleIndex=fmt::sprintf("%d",curSample); ImGui::SetNextItemWidth(72.0f*dpiScale); if (ImGui::BeginCombo("##SampleSelect",sampleIndex.c_str())) { String name; for (size_t i=0; isong.sample.size(); i++) { name=fmt::sprintf("%d: %s##_SMPS%d",i,e->song.sample[i]->name,i); if (ImGui::Selectable(name.c_str(),curSample==(int)i)) { curSample=i; sample=e->song.sample[curSample]; updateSampleTex=true; } } ImGui::EndCombo(); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FOLDER_OPEN "##SELoad")) { doAction(GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Open"); } if (ImGui::BeginPopupContextItem("SampleEOpenOpt")) { if (ImGui::MenuItem("import raw...")) { doAction((curSample>=0 && curSample<(int)e->song.sample.size())?GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE_RAW:GUI_ACTION_SAMPLE_LIST_OPEN_RAW); } ImGui::EndPopup(); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FLOPPY_O "##SESave")) { doAction(GUI_ACTION_SAMPLE_LIST_SAVE); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Save"); } if (ImGui::BeginPopupContextItem("SampleESaveOpt")) { if (ImGui::MenuItem("save raw...")) { doAction(GUI_ACTION_SAMPLE_LIST_SAVE_RAW); } ImGui::EndPopup(); } ImGui::SameLine(); ImGui::Text("Name"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::PushID(2+curSample); if (ImGui::InputText("##SampleName",&sample->name)) { MARK_MODIFIED; } ImGui::PopID(); ImGui::Separator(); String warnLoop, warnLoopMode, warnLoopPos; String warnLength, warnRate; bool isChipVisible[DIV_MAX_CHIPS]; bool isTypeVisible[DIV_MAX_SAMPLE_TYPE]; bool isMemVisible[DIV_MAX_SAMPLE_TYPE][DIV_MAX_CHIPS]; bool isMemWarning[DIV_MAX_SAMPLE_TYPE][DIV_MAX_CHIPS]; memset(isChipVisible,0,DIV_MAX_CHIPS*sizeof(bool)); memset(isTypeVisible,0,DIV_MAX_SAMPLE_TYPE*sizeof(bool)); memset(isMemVisible,0,DIV_MAX_CHIPS*DIV_MAX_SAMPLE_TYPE*sizeof(bool)); memset(isMemWarning,0,DIV_MAX_CHIPS*DIV_MAX_SAMPLE_TYPE*sizeof(bool)); for (int i=0; isong.systemLen; i++) { DivDispatch* dispatch=e->getDispatch(i); // warnings switch (e->song.system[i]) { case DIV_SYSTEM_SNES: if (sample->loop) { if (sample->loopStart&15 || sample->loopEnd&15) { SAMPLE_WARN(warnLoopPos,"SNES: loop must be a multiple of 16"); } } if (dispatch!=NULL) { MAX_RATE("SNES",dispatch->chipClock/8.0); } break; case DIV_SYSTEM_QSOUND: if (sample->loop) { if (sample->loopEnd-sample->loopStart>32767) { SAMPLE_WARN(warnLoopPos,"QSound: loop cannot be longer than 32767 samples"); } } if (sample->samples>65535) { SAMPLE_WARN(warnLength,"QSound: maximum sample length is 65535"); } break; case DIV_SYSTEM_NES: if (sample->loop) { if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) { SAMPLE_WARN(warnLoopPos,"NES: loop point ignored on DPCM (may only loop entire sample)"); } } if (sample->samples>32648) { SAMPLE_WARN(warnLength,"NES: maximum DPCM sample length is 32648"); } break; case DIV_SYSTEM_X1_010: if (sample->loop) { SAMPLE_WARN(warnLoop,"X1-010: samples can't loop"); } if (sample->samples>131072) { SAMPLE_WARN(warnLength,"X1-010: maximum sample length is 131072"); } break; case DIV_SYSTEM_GA20: if (sample->loop) { SAMPLE_WARN(warnLoop,"GA20: samples can't loop"); } if (dispatch!=NULL) { MIN_RATE("GA20",dispatch->chipClock/1024); } break; case DIV_SYSTEM_YM2608: case DIV_SYSTEM_YM2608_EXT: case DIV_SYSTEM_YM2608_CSM: if (sample->loop) { if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) { SAMPLE_WARN(warnLoopPos,"YM2608: loop point ignored on ADPCM-B (may only loop entire sample)"); } } break; case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610_CSM: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: if (sample->loop) { SAMPLE_WARN(warnLoop,"YM2610: ADPCM-A samples can't loop"); if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) { SAMPLE_WARN(warnLoopPos,"YM2610: loop point ignored on ADPCM-B (may only loop entire sample)"); } } if (sample->samples>2097152) { SAMPLE_WARN(warnLength,"YM2610: maximum ADPCM-A sample length is 2097152"); } if (dispatch!=NULL) { EXACT_RATE("YM2610 (ADPCM-A)",dispatch->chipClock/432); } break; case DIV_SYSTEM_AMIGA: if (sample->loop) { if (sample->loopStart&1 || sample->loopEnd&1) { SAMPLE_WARN(warnLoopPos,"Amiga: loop must be a multiple of 2"); } } if (sample->samples>131070) { SAMPLE_WARN(warnLength,"Amiga: maximum sample length is 131070"); } break; case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: if (sample->samples>65280) { SAMPLE_WARN(warnLength,"SegaPCM: maximum sample length is 65280"); } if (dispatch!=NULL) { MAX_RATE("SegaPCM",dispatch->chipClock/256); } break; case DIV_SYSTEM_K053260: if (sample->loop) { if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) { SAMPLE_WARN(warnLoopPos,"K053260: loop point ignored (may only loop entire sample)"); } } if (sample->samples>65535) { SAMPLE_WARN(warnLength,"K053260: maximum sample length is 65535"); } break; case DIV_SYSTEM_C140: if (sample->samples>65535) { SAMPLE_WARN(warnLength,"C140: maximum sample length is 65535"); } break; default: break; } if (e->song.system[i]!=DIV_SYSTEM_PCM_DAC) { if (e->song.system[i]==DIV_SYSTEM_ES5506) { if (sample->loopMode==DIV_SAMPLE_LOOP_BACKWARD) { SAMPLE_WARN(warnLoopMode,"ES5506: backward loop mode isn't supported"); } } else if (sample->loopMode!=DIV_SAMPLE_LOOP_FORWARD) { SAMPLE_WARN(warnLoopMode,"backward/ping-pong only supported in Generic PCM DAC\nping-pong also on ES5506"); } } // chips grid if (dispatch==NULL) continue; for (int j=0; jgetSampleMemCapacity(j)==0) continue; isChipVisible[i]=true; isTypeVisible[j]=true; isMemVisible[j][i]=true; if (!dispatch->isSampleLoaded(j,curSample)) isMemWarning[j][i]=true; } } int selColumns=1; for (int i=0; irate:sample->centerRate; if (ImGui::BeginTable("SampleProps",(selColumns>1)?4:3,ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_BordersV|ImGuiTableFlags_BordersOuterH)) { ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); if (ImGui::Button(sampleInfo?(ICON_FA_CHEVRON_UP "##SECollapse"):(ICON_FA_CHEVRON_DOWN "##SECollapse"))) { sampleInfo=!sampleInfo; } ImGui::SameLine(); ImGui::Text("Info"); ImGui::TableNextColumn(); pushToggleColors(!sampleCompatRate); if (ImGui::Button("Rate")) { sampleCompatRate=false; } popToggleColors(); ImGui::SameLine(); pushToggleColors(sampleCompatRate); if (ImGui::Button("Compat Rate")) { sampleCompatRate=true; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("used in DefleMask-compatible sample mode (17xx), in where samples are mapped to an octave."); } popToggleColors(); ImGui::TableNextColumn(); bool doLoop=(sample->loop); pushWarningColor(!warnLoop.empty()); if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED if (doLoop) { sample->loop=true; if (sample->loopStart<0) { sample->loopStart=0; } if (sample->loopEnd<0) { sample->loopEnd=sample->samples; } } else { sample->loop=false; /* sample->loopStart=-1; sample->loopEnd=sample->samples;*/ } updateSampleTex=true; if (e->getSampleFormatMask()&(1U<renderSamplesP(); } } popWarningColor(); if (ImGui::IsItemHovered() && (!warnLoop.empty() || sample->depth==DIV_SAMPLE_DEPTH_BRR)) { if (sample->depth==DIV_SAMPLE_DEPTH_BRR) { SAMPLE_WARN(warnLoop,"changing the loop in a BRR sample may result in glitches!"); } ImGui::SetTooltip("%s",warnLoop.c_str()); } if (selColumns>1) { ImGui::TableNextColumn(); ImGui::Text("Chips"); } if (sampleInfo) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("Type"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::BeginCombo("##SampleType",sampleType.c_str())) { for (int i=0; iprepareUndo(true); e->lockEngine([this,sample,i]() { sample->convert((DivSampleDepth)i); e->renderSamples(); }); updateSampleTex=true; MARK_MODIFIED; } } ImGui::EndCombo(); } bool isThereSNES=false; for (int i=0; isong.systemLen; i++) { if (e->song.system[i]==DIV_SYSTEM_SNES) { isThereSNES=true; break; } } if (sample->depth==DIV_SAMPLE_DEPTH_BRR || isThereSNES) { bool be=sample->brrEmphasis; if (ImGui::Checkbox("BRR emphasis",&be)) { sample->prepareUndo(true); sample->brrEmphasis=be; e->renderSamplesP(); updateSampleTex=true; MARK_MODIFIED; } if (ImGui::IsItemHovered()) { if (sample->depth==DIV_SAMPLE_DEPTH_BRR) { ImGui::SetTooltip("this is a BRR sample.\nenabling this option will muffle it (only affects non-SNES chips)."); } else { ImGui::SetTooltip("enable this option to slightly boost high frequencies\nto compensate for the SNES' Gaussian filter's muffle."); } } } if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && e->getSampleFormatMask()&(1L<dither; if (ImGui::Checkbox("8-bit dither",&di)) { sample->prepareUndo(true); sample->dither=di; e->renderSamplesP(); updateSampleTex=true; MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("dither the sample when used on a chip that only supports 8-bit samples."); } } int sampleNote=round(64.0+(128.0*12.0*log((double)targetRate/8363.0)/log(2.0))); int sampleNoteCoarse=60+(sampleNote>>7); int sampleNoteFine=(sampleNote&127)-64; if (sampleNoteCoarse<0) { sampleNoteCoarse=0; sampleNoteFine=-64; } if (sampleNoteCoarse>119) { sampleNoteCoarse=119; sampleNoteFine=63; } bool coarseChanged=false; ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("Hz"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::InputInt("##SampleRate",&targetRate,10,200)) { MARK_MODIFIED if (targetRate<100) targetRate=100; if (targetRate>384000) targetRate=384000; if (sampleCompatRate) { sample->rate=targetRate; } else { sample->centerRate=targetRate; } } ImGui::AlignTextToFramePadding(); ImGui::Text("Note"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::BeginCombo("##SampleNote",noteNames[sampleNoteCoarse+60])) { char temp[1024]; for (int i=0; i<120; i++) { snprintf(temp,1023,"%s##_SRN%d",noteNames[i+60],i); if (ImGui::Selectable(temp,i==sampleNoteCoarse)) { sampleNoteCoarse=i; coarseChanged=true; } } ImGui::EndCombo(); } else if (ImGui::IsItemHovered()) { if (wheelY!=0) { sampleNoteCoarse-=wheelY; if (sampleNoteCoarse<0) { sampleNoteCoarse=0; sampleNoteFine=-64; } if (sampleNoteCoarse>119) { sampleNoteCoarse=119; sampleNoteFine=63; } coarseChanged=true; } } if (coarseChanged) { MARK_MODIFIED sampleNote=((sampleNoteCoarse-60)<<7)+sampleNoteFine; targetRate=8363.0*pow(2.0,(double)sampleNote/(128.0*12.0)); if (targetRate<100) targetRate=100; if (targetRate>384000) targetRate=384000; if (sampleCompatRate) { sample->rate=targetRate; } else { sample->centerRate=targetRate; } } ImGui::AlignTextToFramePadding(); ImGui::Text("Fine"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); int prevFine=sampleNoteFine; int prevSampleRate=targetRate; if (ImGui::InputInt("##SampleFine",&sampleNoteFine,1,10)) { MARK_MODIFIED if (sampleNoteFine>63) sampleNoteFine=63; if (sampleNoteFine<-64) sampleNoteFine=-64; sampleNote=((sampleNoteCoarse-60)<<7)+sampleNoteFine; targetRate=round(8363.0*pow(2.0,(double)sampleNote/(128.0*12.0))); if (targetRate==prevSampleRate) { if (prevFine==sampleNoteFine) { // do nothing } else if (prevFine>sampleNoteFine) { // coarse incr/decr due to precision loss targetRate--; } else { targetRate++; } } if (targetRate<100) targetRate=100; if (targetRate>384000) targetRate=384000; if (sampleCompatRate) { sample->rate=targetRate; } else { sample->centerRate=targetRate; } } ImGui::TableNextColumn(); ImGui::BeginDisabled(!(doLoop || keepLoopAlive)); keepLoopAlive=false; ImGui::AlignTextToFramePadding(); ImGui::Text("Mode"); ImGui::SameLine(); pushWarningColor(!warnLoopMode.empty()); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::BeginCombo("##SampleLoopMode",loopType.c_str())) { for (int i=0; iprepareUndo(true); sample->loopMode=(DivSampleLoopMode)i; e->renderSamplesP(); updateSampleTex=true; MARK_MODIFIED; } } ImGui::EndCombo(); } if (ImGui::IsItemHovered() && !warnLoopMode.empty()) { ImGui::SetTooltip("%s",warnLoopMode.c_str()); } popWarningColor(); pushWarningColor(!warnLoopPos.empty()); ImGui::AlignTextToFramePadding(); ImGui::Text("Start"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,16)) { MARK_MODIFIED if (sample->loopStart<0) { sample->loopStart=0; } if (sample->loopStart>sample->loopEnd) { sample->loopStart=sample->loopEnd; } updateSampleTex=true; if (e->getSampleFormatMask()&(1U<renderSamplesP(); } } if (ImGui::IsItemActive()) { keepLoopAlive=true; } if (ImGui::IsItemHovered() && (!warnLoopPos.empty() || sample->depth==DIV_SAMPLE_DEPTH_BRR)) { if (sample->depth==DIV_SAMPLE_DEPTH_BRR) { SAMPLE_WARN(warnLoopPos,"changing the loop in a BRR sample may result in glitches!"); } ImGui::SetTooltip("%s",warnLoopPos.c_str()); } ImGui::AlignTextToFramePadding(); ImGui::Text("End"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,16)) { MARK_MODIFIED if (sample->loopEndloopStart) { sample->loopEnd=sample->loopStart; } if (sample->loopEnd>=(int)sample->samples) { sample->loopEnd=sample->samples; } updateSampleTex=true; if (e->getSampleFormatMask()&(1U<renderSamplesP(); } } if (ImGui::IsItemActive()) { keepLoopAlive=true; } if (ImGui::IsItemHovered() && (!warnLoopPos.empty() || sample->depth==DIV_SAMPLE_DEPTH_BRR)) { if (sample->depth==DIV_SAMPLE_DEPTH_BRR) { SAMPLE_WARN(warnLoopPos,"changing the loop in a BRR sample may result in glitches!"); } ImGui::SetTooltip("%s",warnLoopPos.c_str()); } popWarningColor(); ImGui::EndDisabled(); if (selColumns>1) { ImGui::TableNextColumn(); if (ImGui::BeginTable("SEChipSel",selColumns,ImGuiTableFlags_SizingFixedSame)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); for (int i=0; isong.systemLen; i++) { if (!isChipVisible[i]) continue; ImGui::TableNextColumn(); ImGui::Text("%d",i+1); } char id[1024]; for (int i=0; isong.systemLen; j++) { if (!isChipVisible[j]) continue; ImGui::TableNextColumn(); if (!isMemVisible[i][j]) continue; snprintf(id,1023,"##_SEC%d_%d",i,j); ImVec4 baseColor=sample->renderOn[i][j]?(isMemWarning[i][j]?uiColors[GUI_COLOR_SAMPLE_CHIP_WARNING]:uiColors[GUI_COLOR_SAMPLE_CHIP_ENABLED]):uiColors[GUI_COLOR_SAMPLE_CHIP_DISABLED]; ImVec4 color=baseColor; ImVec4 colorHovered=baseColor; ImVec4 colorActive=baseColor; if (settings.guiColorsBase) { color.x*=0.8f; color.y*=0.8f; color.z*=0.8f; colorHovered.x*=0.65f; colorHovered.y*=0.65f; colorHovered.z*=0.65f; colorActive.x*=0.3f; colorActive.y*=0.3f; colorActive.z*=0.3f; } else { color.x*=0.2f; color.y*=0.2f; color.z*=0.2f; colorHovered.x*=0.4f; colorHovered.y*=0.4f; colorHovered.z*=0.4f; } ImGui::PushStyleColor(ImGuiCol_FrameBg,color); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,colorHovered); ImGui::PushStyleColor(ImGuiCol_FrameBgActive,colorActive); ImGui::PushStyleColor(ImGuiCol_CheckMark,baseColor); if (ImGui::Checkbox(id,&sample->renderOn[i][j])) { e->renderSamplesP(); } ImGui::PopStyleColor(4); if (ImGui::IsItemHovered()) { const char* memName=NULL; size_t capacity=0; size_t usage=0; int totalFree=0; DivDispatch* dispatch=e->getDispatch(j); if (dispatch!=NULL) { memName=dispatch->getSampleMemName(i); capacity=dispatch->getSampleMemCapacity(i); usage=dispatch->getSampleMemUsage(i); if (usagegetSystemName(e->song.system[j]),totalFree); } else { toolText=fmt::sprintf("%s (%s)\n%d bytes free",e->getSystemName(e->song.system[j]),memName,totalFree); } if (isMemWarning[i][j] && sample->renderOn[i][j]) { toolText+="\n\nnot enough memory for this sample!"; } ImGui::SetTooltip("%s",toolText.c_str()); } } } ImGui::EndTable(); } } } ImGui::EndTable(); } /* if (ImGui::Button("Apply")) { e->renderSamplesP(); } ImGui::SameLine(); */ ImGui::Separator(); pushToggleColors(!sampleDragMode); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; } popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Select"); } sameLineMaybe(); pushToggleColors(sampleDragMode); if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { sampleDragMode=true; } popToggleColors(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); sameLineMaybe(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); sameLineMaybe(); ImGui::Button(ICON_FUR_SAMPLE_RESIZE "##SResize"); if (ImGui::IsItemClicked()) { resizeSize=sample->samples; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Resize"); } if (openSampleResizeOpt) { openSampleResizeOpt=false; ImGui::OpenPopup("SResizeOpt"); } 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")) { sample->prepareUndo(true); e->lockEngine([this,sample]() { if (!sample->resize(resizeSize)) { showError("couldn't resize! make sure your sample is 8 or 16-bit."); } e->renderSamples(); }); updateSampleTex=true; sampleSelStart=-1; sampleSelEnd=-1; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } else { resizeSize=sample->samples; } sameLineMaybe(); ImGui::Button(ICON_FUR_SAMPLE_RESAMPLE "##SResample"); if (ImGui::IsItemClicked()) { resampleTarget=targetRate; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Resample"); } if (openSampleResampleOpt) { openSampleResampleOpt=false; ImGui::OpenPopup("SResampleOpt"); } 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=targetRate; } ImGui::SameLine(); if (ImGui::Button("2.0x")) { resampleTarget*=2.0; } double factor=resampleTarget/(double)targetRate; if (ImGui::InputDouble("Factor",&factor,0.125,0.5,"%g")) { resampleTarget=(double)targetRate*factor; if (resampleTarget<0) resampleTarget=0; if (resampleTarget>96000) resampleTarget=96000; } ImGui::Combo("Filter",&resampleStrat,resampleStrats,6); if (ImGui::Button("Resample")) { sample->prepareUndo(true); e->lockEngine([this,sample,targetRate]() { if (!sample->resample(targetRate,resampleTarget,resampleStrat)) { showError("couldn't resample! make sure your sample is 8 or 16-bit."); } e->renderSamples(); }); updateSampleTex=true; sampleSelStart=-1; sampleSelEnd=-1; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } else { resampleTarget=targetRate; } ImGui::SameLine(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); sameLineMaybe(); if (ImGui::Button(ICON_FA_UNDO "##SUndo")) { doUndoSample(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Undo"); } sameLineMaybe(); if (ImGui::Button(ICON_FA_REPEAT "##SRedo")) { doRedoSample(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Redo"); } ImGui::SameLine(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); sameLineMaybe(); ImGui::Button(ICON_FA_VOLUME_UP "##SAmplify"); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Amplify"); } if (openSampleAmplifyOpt) { openSampleAmplifyOpt=false; ImGui::OpenPopup("SAmplifyOpt"); } 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)); if (ImGui::Button("Apply")) { sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; float vol=amplifyVol/100.0f; if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; idata16[i]*vol; if (val<-32768) val=-32768; if (val>32767) val=32767; sample->data16[i]=val; } } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; idata8[i]*vol; if (val<-128) val=-128; if (val>127) val=127; sample->data8[i]=val; } } updateSampleTex=true; e->renderSamples(); }); MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } sameLineMaybe(); if (ImGui::Button(ICON_FUR_SAMPLE_NORMALIZE "##SNormalize")) { doAction(GUI_ACTION_SAMPLE_NORMALIZE); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Normalize"); } sameLineMaybe(); if (ImGui::Button(ICON_FUR_SAMPLE_FADEIN "##SFadeIn")) { doAction(GUI_ACTION_SAMPLE_FADE_IN); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Fade in"); } sameLineMaybe(); if (ImGui::Button(ICON_FUR_SAMPLE_FADEOUT "##SFadeOut")) { doAction(GUI_ACTION_SAMPLE_FADE_OUT); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Fade out"); } sameLineMaybe(); ImGui::Button(ICON_FUR_SAMPLE_INSERT_SILENCE "##SInsertSilence"); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Insert silence"); } if (openSampleSilenceOpt) { openSampleSilenceOpt=false; ImGui::OpenPopup("SSilenceOpt"); } if (ImGui::BeginPopupContextItem("SSilenceOpt",ImGuiPopupFlags_MouseButtonLeft)) { if (ImGui::InputInt("Samples",&silenceSize,1,64)) { if (silenceSize<0) silenceSize=0; if (silenceSize>16777215) silenceSize=16777215; } if (ImGui::Button("Go")) { int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; sample->prepareUndo(true); e->lockEngine([this,sample,pos]() { if (!sample->insert(pos,silenceSize)) { showError("couldn't insert! make sure your sample is 8 or 16-bit."); } e->renderSamples(); }); updateSampleTex=true; sampleSelStart=pos; sampleSelEnd=pos+silenceSize; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } sameLineMaybe(); if (ImGui::Button(ICON_FUR_SAMPLE_APPLY_SILENCE "##SSilence")) { doAction(GUI_ACTION_SAMPLE_SILENCE); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Apply silence"); } sameLineMaybe(); if (ImGui::Button(ICON_FA_TIMES "##SDelete")) { doAction(GUI_ACTION_SAMPLE_DELETE); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Delete"); } sameLineMaybe(); if (ImGui::Button(ICON_FA_CROP "##STrim")) { doAction(GUI_ACTION_SAMPLE_TRIM); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Trim"); } ImGui::SameLine(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); sameLineMaybe(); if (ImGui::Button(ICON_FUR_SAMPLE_REVERSE "##SReverse")) { doAction(GUI_ACTION_SAMPLE_REVERSE); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Reverse"); } sameLineMaybe(); if (ImGui::Button(ICON_FUR_SAMPLE_INVERT "##SInvert")) { doAction(GUI_ACTION_SAMPLE_INVERT); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Invert"); } sameLineMaybe(); if (ImGui::Button(ICON_FUR_SAMPLE_SIGN "##SSign")) { doAction(GUI_ACTION_SAMPLE_SIGN); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Signed/unsigned exchange"); } sameLineMaybe(); ImGui::Button(ICON_FUR_SAMPLE_FILTER "##SFilter"); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Apply filter"); } if (openSampleFilterOpt) { openSampleFilterOpt=false; ImGui::OpenPopup("SFilterOpt"); } if (ImGui::BeginPopupContextItem("SFilterOpt",ImGuiPopupFlags_MouseButtonLeft)) { float lowP=sampleFilterL*100.0f; float bandP=sampleFilterB*100.0f; float highP=sampleFilterH*100.0f; float resP=sampleFilterRes*100.0f; ImGui::Text("Cutoff:"); if (ImGui::InputFloat("From",&sampleFilterCutStart,1.0f,100.0f,"%.0f")) { if (sampleFilterCutStart<0.0) sampleFilterCutStart=0.0; if (sampleFilterCutStart>sample->rate*0.5) sampleFilterCutStart=sample->rate*0.5; } if (ImGui::InputFloat("To",&sampleFilterCutEnd,1.0f,100.0f,"%.0f")) { if (sampleFilterCutEnd<0.0) sampleFilterCutEnd=0.0; if (sampleFilterCutEnd>sample->rate*0.5) sampleFilterCutEnd=sample->rate*0.5; } ImGui::Separator(); if (ImGui::SliderFloat("Resonance",&resP,0.0f,99.0f,"%.1f%%")) { sampleFilterRes=resP/100.0f; if (sampleFilterRes<0.0f) sampleFilterRes=0.0f; if (sampleFilterRes>0.99f) sampleFilterRes=0.99f; } ImGui::AlignTextToFramePadding(); ImGui::Text("Power"); ImGui::SameLine(); if (ImGui::RadioButton("1x",sampleFilterPower==1)) { sampleFilterPower=1; } ImGui::SameLine(); if (ImGui::RadioButton("2x",sampleFilterPower==2)) { sampleFilterPower=2; } ImGui::SameLine(); if (ImGui::RadioButton("3x",sampleFilterPower==3)) { sampleFilterPower=3; } ImGui::Separator(); if (ImGui::SliderFloat("Low-pass",&lowP,0.0f,100.0f,"%.1f%%")) { sampleFilterL=lowP/100.0f; if (sampleFilterL<0.0f) sampleFilterL=0.0f; if (sampleFilterL>1.0f) sampleFilterL=1.0f; } if (ImGui::SliderFloat("Band-pass",&bandP,0.0f,100.0f,"%.1f%%")) { sampleFilterB=bandP/100.0f; if (sampleFilterB<0.0f) sampleFilterB=0.0f; if (sampleFilterB>1.0f) sampleFilterB=1.0f; } if (ImGui::SliderFloat("High-pass",&highP,0.0f,100.0f,"%.1f%%")) { sampleFilterH=highP/100.0f; if (sampleFilterH<0.0f) sampleFilterH=0.0f; if (sampleFilterH>1.0f) sampleFilterH=1.0f; } if (ImGui::Button("Apply")) { sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; float res=1.0-pow(sampleFilterRes,0.5f); float low=0; float band=0; float high=0; double power=(sampleFilterCutStart>sampleFilterCutEnd)?0.5:2.0; if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=start; irate))*M_PI); for (int j=0; jdata16[i])-low-(res*band); band=cut*high+band; } float val=low*sampleFilterL+band*sampleFilterB+high*sampleFilterH; if (val<-32768) val=-32768; if (val>32767) val=32767; sample->data16[i]=val; } } else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=start; irate))*M_PI); for (int j=0; jdata8[i])-low-(res*band); band=cut*high+band; } float val=low*sampleFilterL+band*sampleFilterB+high*sampleFilterH; if (val<-128) val=-128; if (val>127) val=127; sample->data8[i]=val; } } updateSampleTex=true; e->renderSamples(); }); MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } ImGui::EndDisabled(); ImGui::SameLine(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); sameLineMaybe(); if (ImGui::Button(ICON_FA_PLAY "##PreviewSample")) { e->previewSample(curSample); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Preview sample"); } sameLineMaybe(); if (ImGui::Button(ICON_FA_STOP "##StopSample")) { e->stopSamplePreview(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Stop sample preview"); } sameLineMaybe(); if (ImGui::Button(ICON_FA_UPLOAD "##MakeIns")) { doAction(GUI_ACTION_SAMPLE_MAKE_INS); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Create instrument from sample"); } sameLineMaybe(ImGui::CalcTextSize("Zoom").x+150.0f*dpiScale+ImGui::CalcTextSize("100%").x); double zoomPercent=100.0/sampleZoom; bool checkZoomLimit=false; ImGui::Text("Zoom"); ImGui::SameLine(); ImGui::SetNextItemWidth(150.0f*dpiScale); if (ImGui::InputDouble("##SZoom",&zoomPercent,zoomPercent/8.0,20.0,"%g%%")) { if (zoomPercent>10000.0) zoomPercent=10000.0; if (zoomPercent<0.01) zoomPercent=0.01; sampleZoom=100.0/zoomPercent; sampleZoomAuto=false; checkZoomLimit=true; updateSampleTex=true; } ImGui::SameLine(); if (sampleZoomAuto) { ImGui::BeginDisabled(minSampleZoom<1.0); if (ImGui::Button("100%")) { sampleZoom=1.0; sampleZoomAuto=false; updateSampleTex=true; checkZoomLimit=true; } ImGui::EndDisabled(); } else { if (ImGui::Button("Auto")) { sampleZoomAuto=true; updateSampleTex=true; } } ImGui::Separator(); // time ImDrawList* dl=ImGui::GetWindowDrawList(); ImGuiWindow* window=ImGui::GetCurrentWindow(); ImVec2 size=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetFontSize()+2.0*dpiScale); ImVec2 minArea=window->DC.CursorPos; ImVec2 maxArea=ImVec2( minArea.x+size.x, minArea.y+size.y ); ImRect rect=ImRect(minArea,maxArea); ImGuiStyle& style=ImGui::GetStyle(); ImGui::ItemSize(size,style.FramePadding.y); if (ImGui::ItemAdd(rect,ImGui::GetID("SETime"))) { int targetRate=sampleCompatRate?sample->rate:sample->centerRate; int curDivisorSel=0; int curMultiplierSel=0; double divisor=1000.0; double multiplier=1.0; while ((((double)targetRate/divisor)/sampleZoom)<60.0*dpiScale) { if (curDivisorSel>=10) break; divisor=timeDivisors[++curDivisorSel]; } if (curDivisorSel>=10) { while ((((double)targetRate*multiplier)/sampleZoom)<60.0*dpiScale) { if (curMultiplierSel>=13) { multiplier+=3600.0; } else { multiplier=timeMultipliers[++curMultiplierSel]; } } } double timeStep=multiplier*((double)targetRate/divisor); double timeMin=-fmod(samplePos,timeStep); double timeMax=size.x*sampleZoom; ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_TIME_FG]); dl->AddRectFilled(minArea,maxArea,ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_TIME_BG])); for (double i=timeMin; i=9) { if (timeMs>=3600000) { t=fmt::sprintf("%d:%02d:%02d",timeMs/3600000,(timeMs/60000)%60,(timeMs/1000)%60); } else if (timeMs>=60000) { t=fmt::sprintf("%d:%02d.%02d",timeMs/60000,(timeMs/1000)%60,(timeMs%1000)/10); } else { t=fmt::sprintf("%d.%03d",timeMs/1000,timeMs%1000); } } else { t=fmt::sprintf("%dms",timeMs); } dl->AddText(pos,color,t.c_str()); } } ImVec2 avail=ImGui::GetContentRegionAvail(); // sample view size determined here // don't do this. reason: mobile. /*if (ImGui::GetContentRegionAvail().y>(ImGui::GetContentRegionAvail().x*0.5f)) { avail=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().x*0.5f); }*/ avail.y-=ImGui::GetFontSize()+ImGui::GetStyle().ItemSpacing.y+ImGui::GetStyle().ScrollbarSize; if (avail.y<1.0) { // Prevents crash avail.y=1.0; } int availX=avail.x; int availY=avail.y; minSampleZoom=(double)sample->samples/avail.x; if (checkZoomLimit) { logV("sample: %f min: %f",sampleZoom,minSampleZoom); int bounds=((int)sample->samples-round(avail.x*sampleZoom)); if (bounds<0) bounds=0; if (samplePos>bounds) samplePos=bounds; if (sampleZoom>minSampleZoom) { sampleZoomAuto=true; } } if (sampleZoomAuto) { samplePos=0; if (sample->samples<1 || avail.x<=0) { sampleZoom=1.0; } else { sampleZoom=minSampleZoom; } if (sampleZoom!=prevSampleZoom) { prevSampleZoom=sampleZoom; updateSampleTex=true; } } if (sampleTex==NULL || sampleTexW!=avail.x || sampleTexH!=avail.y) { if (sampleTex!=NULL) { rend->destroyTexture(sampleTex); sampleTex=NULL; } if (avail.x>=1 && avail.y>=1) { logD("recreating sample texture."); sampleTex=rend->createTexture(true,avail.x,avail.y); sampleTexW=avail.x; sampleTexH=avail.y; if (sampleTex==NULL) { logE("error while creating sample texture! %s",SDL_GetError()); } else { updateSampleTex=true; } } } if (sampleTex!=NULL) { if (updateSampleTex) { unsigned int* dataT=NULL; int pitch=0; logD("updating sample texture."); if (!rend->lockTexture(sampleTex,(void**)&dataT,&pitch)) { logE("error while locking sample texture! %s",SDL_GetError()); } else { unsigned int* data=new unsigned int[sampleTexW*sampleTexH]; ImU32 bgColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_BG]); ImU32 bgColorLoop=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_LOOP]); ImU32 lineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_FG]); ImU32 centerLineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_CENTER]); int ij=0; for (int i=0; iisLoopable() && (scaledPos>=sample->loopStart && scaledPos<=sample->loopEnd)) { data[ij++]=bgColorLoop; } else { data[ij++]=bgColor; } } } if (availY>0) { for (int i=availX*(availY>>1); i>1)); i++) { data[i]=centerLineColor; } } unsigned int xCoarse=samplePos; unsigned int xFine=0; unsigned int xAdvanceCoarse=sampleZoom; unsigned int xAdvanceFine=fmod(sampleZoom,1.0)*16777216; for (unsigned int i=0; i<(unsigned int)availX; i++) { if (xCoarse>=sample->samples) break; int y1, y2; int candMin=INT_MAX; int candMax=INT_MIN; int totalAdvance=0; if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { if (candMin>sample->data8[xCoarse]) candMin=sample->data8[xCoarse]; if (candMaxdata8[xCoarse]) candMax=sample->data8[xCoarse]; } else { if (candMin>sample->data16[xCoarse]) candMin=sample->data16[xCoarse]; if (candMaxdata16[xCoarse]) candMax=sample->data16[xCoarse]; } xFine+=xAdvanceFine; if (xFine>=16777216) { xFine-=16777216; totalAdvance++; } totalAdvance+=xAdvanceCoarse; if (xCoarse>=sample->samples) break; do { if (xCoarse>=sample->samples) break; if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { if (candMin>sample->data8[xCoarse]) candMin=sample->data8[xCoarse]; if (candMaxdata8[xCoarse]) candMax=sample->data8[xCoarse]; } else { if (candMin>sample->data16[xCoarse]) candMin=sample->data16[xCoarse]; if (candMaxdata16[xCoarse]) candMax=sample->data16[xCoarse]; } if (totalAdvance>0) xCoarse++; } while ((totalAdvance--)>0); if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { y1=(((unsigned char)candMin^0x80)*availY)>>8; y2=(((unsigned char)candMax^0x80)*availY)>>8; } else { y1=(((unsigned short)candMin^0x8000)*availY)>>16; y2=(((unsigned short)candMax^0x8000)*availY)>>16; } if (y1>y2) { y2^=y1; y1^=y2; y2^=y1; } if (y1<0) y1=0; if (y1>=availY) y1=availY-1; if (y2<0) y2=0; if (y2>=availY) y2=availY-1; const int s1=i+availX*(availY-y1-1); const int s2=i+availX*(availY-y2-1); for (int j=s2; j<=s1; j+=availX) { data[j]=lineColor; } } if ((pitch>>2)==sampleTexW) { memcpy(dataT,data,sampleTexW*sampleTexH*sizeof(unsigned int)); } else { int srcY=0; int destY=0; for (int i=0; i>2; } } rend->unlockTexture(sampleTex); delete[] data; } updateSampleTex=false; } ImGui::ImageButton(rend->getTextureID(sampleTex),avail,ImVec2(0,0),ImVec2(1,1),0); ImVec2 rectMin=ImGui::GetItemRectMin(); ImVec2 rectMax=ImGui::GetItemRectMax(); ImVec2 rectSize=ImGui::GetItemRectSize(); unsigned char selectTarget=255; if (ImGui::IsItemHovered()) { int start=sampleSelStart; int end=sampleSelEnd; if (start>end) { start^=end; end^=start; start^=end; } ImVec2 p1=rectMin; p1.x+=(start-samplePos)/sampleZoom; ImVec2 p2=ImVec2(rectMin.x+(end-samplePos)/sampleZoom,rectMax.y); ImVec2 mousePos=ImGui::GetMousePos(); if (p1.x>=rectMin.x && p1.x<=rectMax.x && fabs(mousePos.x-p1.x)<2.0*dpiScale) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); selectTarget=0; } else if (p2.x>=rectMin.x && p2.x<=rectMax.x && fabs(mousePos.x-p2.x)<2.0*dpiScale) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); selectTarget=1; } } if (ImGui::IsItemClicked()) { nextWindow=GUI_WINDOW_SAMPLE_EDIT; if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { sampleDragActive=false; sampleSelStart=0; sampleSelEnd=sample->samples; } else { if (sample->samples>0) { sampleDragStart=rectMin; sampleDragAreaSize=rectSize; switch (sample->depth) { case DIV_SAMPLE_DEPTH_8BIT: sampleDrag16=false; sampleDragTarget=(void*)sample->data8; break; case DIV_SAMPLE_DEPTH_16BIT: sampleDrag16=true; sampleDragTarget=(void*)sample->data16; break; default: sampleDrag16=true; sampleDragTarget=NULL; break; } sampleDragLen=sample->samples; sampleDragActive=true; if (!sampleDragMode) { switch (selectTarget) { case 0: sampleSelStart^=sampleSelEnd; sampleSelEnd^=sampleSelStart; sampleSelStart^=sampleSelEnd; break; case 1: break; default: sampleSelStart=-1; sampleSelEnd=-1; break; } } else { sample->prepareUndo(true); } processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } } } if (ImGui::IsItemActive()) { if (ImGui::GetMousePos().x>rectMax.x) { double delta=pow(MAX(1.0,(ImGui::GetMousePos().x-rectMax.x)*0.04),2.0); samplePos+=MAX(1.0,sampleZoom*delta); int bounds=((int)sample->samples-round(avail.x*sampleZoom)); if (bounds<0) bounds=0; if (samplePos>bounds) samplePos=bounds; updateSampleTex=true; processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); WAKE_UP; } if (ImGui::GetMousePos().xdepth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_SAMPLE_CUT))) { doAction(GUI_ACTION_SAMPLE_CUT); } ImGui::EndDisabled(); if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_SAMPLE_COPY))) { doAction(GUI_ACTION_SAMPLE_COPY); } ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_SAMPLE_PASTE))) { doAction(GUI_ACTION_SAMPLE_PASTE); } if (ImGui::MenuItem("paste (replace)",BIND_FOR(GUI_ACTION_SAMPLE_PASTE_REPLACE))) { doAction(GUI_ACTION_SAMPLE_PASTE_REPLACE); } if (ImGui::MenuItem("paste (mix)",BIND_FOR(GUI_ACTION_SAMPLE_PASTE_MIX))) { doAction(GUI_ACTION_SAMPLE_PASTE_MIX); } ImGui::EndDisabled(); if (ImGui::MenuItem("select all",BIND_FOR(GUI_ACTION_SAMPLE_SELECT_ALL))) { doAction(GUI_ACTION_SAMPLE_SELECT_ALL); } ImGui::Separator(); if (ImGui::MenuItem("set loop to selection",BIND_FOR(GUI_ACTION_SAMPLE_SET_LOOP))) { doAction(GUI_ACTION_SAMPLE_SET_LOOP); } if (ImGui::MenuItem("create wavetable from selection",BIND_FOR(GUI_ACTION_SAMPLE_CREATE_WAVE))) { doAction(GUI_ACTION_SAMPLE_CREATE_WAVE); } ImGui::EndPopup(); } String statusBar=sampleDragMode?"Draw":"Select"; String statusBar2=""; String statusBar3=fmt::sprintf("%d samples, %d bytes",sample->samples,sample->getCurBufLen()); bool drawSelection=false; if (!sampleDragMode) { if (sampleSelStart>=0 && sampleSelEnd>=0) { int start=sampleSelStart; int end=sampleSelEnd; if (start>end) { start^=end; end^=start; start^=end; } if (start==end) { statusBar+=fmt::sprintf(" (%d)",start); } else { statusBar+=fmt::sprintf(" (%d-%d: %d samples)",start,end,end-start); } drawSelection=true; } } if (ImGui::IsItemHovered()) { if (ctrlWheeling) { double zoomPercent=100.0/sampleZoom; if (wheelY>0) { for (int i=0; i10000.0) zoomPercent=10000.0; if (zoomPercent<0.01) zoomPercent=0.01; sampleZoom=100.0/zoomPercent; sampleZoomAuto=false; int bounds=((int)sample->samples-round(rectSize.x*sampleZoom)); if (bounds<0) bounds=0; if (samplePos>bounds) samplePos=bounds; updateSampleTex=true; if (sampleZoom>minSampleZoom) { sampleZoomAuto=true; } } else { if (wheelY!=0) { if (!sampleZoomAuto) { double scrollAmount=MAX(fabs((double)wheelY*sampleZoom*60.0),1.0); if (wheelY>0) { samplePos+=scrollAmount; } else { samplePos-=scrollAmount; } if (samplePos<0) samplePos=0; int bounds=((int)sample->samples-round(rectSize.x*sampleZoom)); if (bounds<0) bounds=0; if (samplePos>bounds) samplePos=bounds; updateSampleTex=true; } } } int posX=-1; int posY=0; ImVec2 pos=ImGui::GetMousePos(); pos.x-=rectMin.x; pos.y-=rectMin.y; if (sampleZoom>0) { posX=samplePos+pos.x*sampleZoom; if (posX>(int)sample->samples) posX=-1; } posY=(0.5-pos.y/rectSize.y)*((sample->depth==DIV_SAMPLE_DEPTH_8BIT)?255:65535); if (posX>=0) { statusBar2=fmt::sprintf("(%d, %d)",posX,posY); } } dl->PushClipRect(rectMin,rectMax); if (e->isPreviewingSample()) { if (!statusBar2.empty()) { statusBar2+=" | "; } statusBar2+=fmt::sprintf("%.2fHz",e->getSamplePreviewRate()); int start=sampleSelStart; int end=sampleSelEnd; if (start>end) { start^=end; end^=start; start^=end; } ImVec2 p1=rectMin; p1.x+=(e->getSamplePreviewPos()-samplePos)/sampleZoom; ImVec4 posColor=uiColors[GUI_COLOR_SAMPLE_NEEDLE]; ImVec4 posTrail1=posColor; ImVec4 posTrail2=posColor; posTrail1.w*=0.5f; posTrail2.w=0.0f; float trailDistance=(e->getSamplePreviewRate()/100.0f)/sampleZoom; //if (p1.xrectMax.x) p1.x=rectMax.x; ImVec2 p2=p1; p2.y=rectMax.y; dl->AddRectFilledMultiColor( ImVec2(p1.x-trailDistance,p1.y), p2, ImGui::GetColorU32(posTrail2), ImGui::GetColorU32(posTrail1), ImGui::GetColorU32(posTrail1), ImGui::GetColorU32(posTrail2) ); dl->AddLine(p1,p2,ImGui::GetColorU32(posColor)); } if (e->isRunning()) { for (int i=0; igetTotalChannelCount(); i++) { DivSamplePos chanPos=e->getSamplePos(i); if (chanPos.sample!=curSample) continue; int start=sampleSelStart; int end=sampleSelEnd; if (start>end) { start^=end; end^=start; start^=end; } ImVec2 p1=rectMin; p1.x+=(chanPos.pos-samplePos)/sampleZoom; ImVec4 posColor=uiColors[GUI_COLOR_SAMPLE_NEEDLE_PLAYING]; ImVec4 posTrail1=posColor; ImVec4 posTrail2=posColor; posTrail1.w*=0.5f; posTrail2.w=0.0f; float trailDistance=((float)chanPos.freq/100.0f)/sampleZoom; //if (p1.xrectMax.x) p1.x=rectMax.x; ImVec2 p2=p1; p2.y=rectMax.y; dl->AddRectFilledMultiColor( ImVec2(p1.x-trailDistance,p1.y), p2, ImGui::GetColorU32(posTrail2), ImGui::GetColorU32(posTrail1), ImGui::GetColorU32(posTrail1), ImGui::GetColorU32(posTrail2) ); dl->AddLine(p1,p2,ImGui::GetColorU32(posColor)); } } dl->PopClipRect(); if (drawSelection) { int start=sampleSelStart; int end=sampleSelEnd; if (start>end) { start^=end; end^=start; start^=end; } ImVec2 p1=rectMin; p1.x+=(start-samplePos)/sampleZoom; ImVec2 p2=ImVec2(rectMin.x+(end-samplePos)/sampleZoom,rectMax.y); ImVec4 boundColor=uiColors[GUI_COLOR_SAMPLE_SEL_POINT]; ImVec4 selColor=uiColors[GUI_COLOR_SAMPLE_SEL]; if (p1.xrectMax.x) p1.x=rectMax.x; if (p2.xrectMax.x) p2.x=rectMax.x; dl->AddRectFilled(p1,p2,ImGui::GetColorU32(selColor)); dl->AddLine(ImVec2(p1.x,p1.y),ImVec2(p1.x,p2.y),ImGui::GetColorU32(boundColor)); if (start!=end) { dl->AddLine(ImVec2(p2.x,p1.y),ImVec2(p2.x,p2.y),ImGui::GetColorU32(boundColor)); } } ImS64 scrollV=samplePos; ImS64 availV=round(rectSize.x*sampleZoom); ImS64 contentsV=MAX(sample->samples,MAX(availV,1)); ImGuiID scrollbarID=ImGui::GetID("sampleScroll"); ImGui::KeepAliveID(scrollbarID); if (ImGui::ScrollbarEx(ImRect(ImVec2(rectMin.x,rectMax.y),ImVec2(rectMax.x,rectMax.y+ImGui::GetStyle().ScrollbarSize)),scrollbarID,ImGuiAxis_X,&scrollV,availV,contentsV,0)) { if (!sampleZoomAuto && samplePos!=scrollV) { samplePos=scrollV; updateSampleTex=true; } } if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT && sampleDragMode) { statusBar="Non-8/16-bit samples cannot be edited without prior conversion."; } ImGui::SetCursorPosY(ImGui::GetCursorPosY()+ImGui::GetStyle().ScrollbarSize); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0,0)); if (ImGui::BeginTable("SEStatus",3,ImGuiTableFlags_BordersInnerV)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.7); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.3); ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextUnformatted(statusBar.c_str()); ImGui::TableNextColumn(); if (!warnRate.empty()) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_WARNING]); ImGui::TextUnformatted(statusBar2.c_str()); ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s",warnRate.c_str()); } } else { ImGui::TextUnformatted(statusBar2.c_str()); } ImGui::TableNextColumn(); if (!warnLength.empty()) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_WARNING]); ImGui::TextUnformatted(statusBar3.c_str()); ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s",warnLength.c_str()); } } else { ImGui::TextUnformatted(statusBar3.c_str()); } ImGui::EndTable(); } ImGui::PopStyleVar(); } } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_EDIT; ImGui::End(); } void FurnaceGUI::doUndoSample() { if (!sampleEditOpen) return; if (curSample<0 || curSample>=(int)e->song.sample.size()) return; DivSample* sample=e->song.sample[curSample]; e->lockEngine([this,sample]() { if (sample->undo()==2) { e->renderSamples(); updateSampleTex=true; } }); } void FurnaceGUI::doRedoSample() { if (!sampleEditOpen) return; if (curSample<0 || curSample>=(int)e->song.sample.size()) return; DivSample* sample=e->song.sample[curSample]; e->lockEngine([this,sample]() { if (sample->redo()==2) { e->renderSamples(); updateSampleTex=true; } }); }