diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 9f40c3f84..cf8429cd2 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1542,12 +1542,14 @@ int DivEngine::calcFreq(int base, int pitch, bool period) { void DivEngine::play() { isBusy.lock(); + freelance=false; playSub(false); isBusy.unlock(); } void DivEngine::stop() { isBusy.lock(); + freelance=false; playing=false; extValuePresent=false; isBusy.unlock(); @@ -1693,7 +1695,7 @@ unsigned char DivEngine::getExtValue() { } bool DivEngine::isPlaying() { - return playing; + return (playing && !freelance); } bool DivEngine::isChannelMuted(int chan) { @@ -1902,7 +1904,7 @@ void DivEngine::addOrder(bool duplicate, bool where) { } song.ordersLen++; curOrder++; - if (playing) { + if (playing && !freelance) { playSub(false); } } @@ -1919,7 +1921,7 @@ void DivEngine::deleteOrder() { } song.ordersLen--; if (curOrder>=song.ordersLen) curOrder=song.ordersLen-1; - if (playing) { + if (playing && !freelance) { playSub(false); } isBusy.unlock(); @@ -1937,7 +1939,7 @@ void DivEngine::moveOrderUp() { song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder-1]; } curOrder--; - if (playing) { + if (playing && !freelance) { playSub(false); } isBusy.unlock(); @@ -1955,17 +1957,39 @@ void DivEngine::moveOrderDown() { song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder+1]; } curOrder++; - if (playing) { + if (playing && !freelance) { playSub(false); } isBusy.unlock(); } +void DivEngine::noteOn(int chan, int ins, int note, int vol) { + isBusy.lock(); + pendingNotes.push(DivNoteEvent(chan,ins,note,vol,true)); + if (!playing) { + reset(); + freelance=true; + playing=true; + } + isBusy.unlock(); +} + +void DivEngine::noteOff(int chan) { + isBusy.lock(); + pendingNotes.push(DivNoteEvent(chan,-1,-1,-1,false)); + if (!playing) { + reset(); + freelance=true; + playing=true; + } + isBusy.unlock(); +} + void DivEngine::setOrder(unsigned char order) { isBusy.lock(); curOrder=order; if (order>=song.ordersLen) curOrder=0; - if (playing) { + if (playing && !freelance) { playSub(false); } isBusy.unlock(); diff --git a/src/engine/engine.h b/src/engine/engine.h index aeb0cabf7..5550f008c 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -7,6 +7,7 @@ #include "blip_buf.h" #include #include +#include #define DIV_VERSION "0.1" #define DIV_ENGINE_VERSION 11 @@ -67,6 +68,17 @@ struct DivChannelState { inPorta(false) {} }; +struct DivNoteEvent { + int channel, ins, note, volume; + bool on; + DivNoteEvent(int c, int i, int n, int v, bool o): + channel(c), + ins(i), + note(n), + volume(v), + on(o) {} +}; + class DivEngine { DivDispatch* dispatch; TAAudio* output; @@ -74,6 +86,7 @@ class DivEngine { int chans; bool active; bool playing; + bool freelance; bool speedAB; bool endOfSong; bool consoleMode; @@ -87,6 +100,7 @@ class DivEngine { DivChannelState chan[17]; DivAudioEngines audioEngine; std::map conf; + std::queue pendingNotes; bool isMuted[17]; std::mutex isBusy; String configPath; @@ -302,6 +316,12 @@ class DivEngine { // move order down void moveOrderDown(); + // play note + void noteOn(int chan, int ins, int note, int vol=-1); + + // stop note + void noteOff(int chan); + // go to order void setOrder(unsigned char order); @@ -347,6 +367,7 @@ class DivEngine { chans(0), active(false), playing(false), + freelance(false), speedAB(false), endOfSong(false), consoleMode(false), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index c7d3df89a..7570e0a03 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -705,108 +705,123 @@ bool DivEngine::nextTick(bool noAccum) { cycles++; } - if (--ticks<=0) { - ret=endOfSong; - if (endOfSong) { - playSub(true); + while (!pendingNotes.empty()) { + DivNoteEvent& note=pendingNotes.front(); + if (note.on) { + dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,note.channel,note.ins)); + dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,note.channel,note.note)); + } else { + dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,note.channel)); } - endOfSong=false; - nextRow(); + pendingNotes.pop(); } - // process stuff - for (int i=0; i0) { - if (--chan[i].rowDelay==0) { - processRow(i,true); + + if (!freelance) { + if (--ticks<=0) { + ret=endOfSong; + if (endOfSong) { + playSub(true); } + endOfSong=false; + nextRow(); } - if (chan[i].volSpeed!=0) { - chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8); - chan[i].volume+=chan[i].volSpeed; - if (chan[i].volume>chan[i].volMax) { - chan[i].volume=chan[i].volMax; - chan[i].volSpeed=0; - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); - } else if (chan[i].volume<0) { - chan[i].volSpeed=0; - chan[i].volume=chan[i].volMax+1; - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); - } else { - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); - } - } - if (chan[i].vibratoDepth>0) { - chan[i].vibratoPos+=chan[i].vibratoRate; - if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64; - switch (chan[i].vibratoDir) { - case 1: // up - dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MAX(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); - break; - case 2: // down - dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MIN(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); - break; - default: // both - dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); - break; - } - - } - if (chan[i].portaSpeed>0) { - if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop) { - chan[i].portaSpeed=0; - chan[i].oldNote=chan[i].note; - chan[i].note=chan[i].portaNote; - chan[i].inPorta=false; - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); - } - } - if (chan[i].cut>0) { - if (--chan[i].cut<1) { - chan[i].oldNote=chan[i].note; - chan[i].note=-1; - dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i)); - } - } - if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) { - if (--chan[i].arpTicks<1) { - chan[i].arpTicks=song.arpLen; - chan[i].arpStage++; - if (chan[i].arpStage>2) chan[i].arpStage=0; - switch (chan[i].arpStage) { - case 0: - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); - break; - case 1: - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp>>4))); - break; - case 2: - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp&15))); - break; + // process stuff + for (int i=0; i0) { + if (--chan[i].rowDelay==0) { + processRow(i,true); } } - } else { - chan[i].arpYield=false; + if (chan[i].volSpeed!=0) { + chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8); + chan[i].volume+=chan[i].volSpeed; + if (chan[i].volume>chan[i].volMax) { + chan[i].volume=chan[i].volMax; + chan[i].volSpeed=0; + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + } else if (chan[i].volume<0) { + chan[i].volSpeed=0; + chan[i].volume=chan[i].volMax+1; + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + } else { + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + } + } + if (chan[i].vibratoDepth>0) { + chan[i].vibratoPos+=chan[i].vibratoRate; + if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64; + switch (chan[i].vibratoDir) { + case 1: // up + dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MAX(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + break; + case 2: // down + dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MIN(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + break; + default: // both + dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); + break; + } + + } + if (chan[i].portaSpeed>0) { + if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop) { + chan[i].portaSpeed=0; + chan[i].oldNote=chan[i].note; + chan[i].note=chan[i].portaNote; + chan[i].inPorta=false; + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + } + } + if (chan[i].cut>0) { + if (--chan[i].cut<1) { + chan[i].oldNote=chan[i].note; + chan[i].note=-1; + dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i)); + } + } + if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) { + if (--chan[i].arpTicks<1) { + chan[i].arpTicks=song.arpLen; + chan[i].arpStage++; + if (chan[i].arpStage>2) chan[i].arpStage=0; + switch (chan[i].arpStage) { + case 0: + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + break; + case 1: + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp>>4))); + break; + case 2: + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp&15))); + break; + } + } + } else { + chan[i].arpYield=false; + } } } // system tick dispatch->tick(); - if (!noAccum) totalTicks++; + if (!freelance) { + if (!noAccum) totalTicks++; - int hz; - if (song.customTempo) { - hz=song.hz; - } else if (song.pal) { - hz=60; - } else { - hz=50; - } - if (consoleMode) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalTicks/(hz*3600),(totalTicks/(hz*60))%60,(totalTicks/hz)%60,totalTicks%hz,curOrder,song.ordersLen,curRow,song.patLen,cmdsPerSecond); + int hz; + if (song.customTempo) { + hz=song.hz; + } else if (song.pal) { + hz=60; + } else { + hz=50; + } + if (consoleMode) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalTicks/(hz*3600),(totalTicks/(hz*60))%60,(totalTicks/hz)%60,totalTicks%hz,curOrder,song.ordersLen,curRow,song.patLen,cmdsPerSecond); - if ((totalTicks%hz)==0) { - cmdsPerSecond=totalCmds-lastCmds; - lastCmds=totalCmds; + if ((totalTicks%hz)==0) { + cmdsPerSecond=totalCmds-lastCmds; + lastCmds=totalCmds; + } } return ret; diff --git a/src/engine/song.h b/src/engine/song.h index 11e690279..d7cf1eec0 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -124,7 +124,7 @@ struct DivSong { speed1(6), speed2(6), arpLen(1), - pal(false), + pal(true), customTempo(false), hz(60), patLen(64), diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f7d1b7b7b..e6ac9f82e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -270,6 +270,8 @@ void FurnaceGUI::drawEditControls() { if (ImGui::Button(ICON_FA_STOP "##Stop")) { e->stop(); } + ImGui::SameLine(); + ImGui::Checkbox("Edit",&edit); ImGui::Text("Follow"); ImGui::SameLine(); @@ -2083,6 +2085,9 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { } else if (ev.key.keysym.mod&KMOD_ALT) { // nothing. prevents accidental OFF note. } else switch (ev.key.keysym.sym) { + case SDLK_SPACE: + edit=!edit; + break; case SDLK_UP: moveCursor(0,-1); break; @@ -2111,66 +2116,80 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { doInsert(); break; default: - if (cursor.xFine==0) { // note - try { - int key=noteKeys.at(ev.key.keysym.sym); - int num=12*curOctave+key; - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); - - prepareUndo(GUI_ACTION_PATTERN_EDIT); + if (!ev.key.repeat) { + if (cursor.xFine==0) { // note + try { + int key=noteKeys.at(ev.key.keysym.sym); + int num=12*curOctave+key; - if (key==100) { // note off - pat->data[cursor.y][0]=100; - pat->data[cursor.y][1]=0; - } else { - pat->data[cursor.y][0]=num%12; - pat->data[cursor.y][1]=num/12; - if (pat->data[cursor.y][0]==0) { - pat->data[cursor.y][0]=12; - pat->data[cursor.y][1]--; - } - pat->data[cursor.y][2]=curIns; - } - makeUndo(GUI_ACTION_PATTERN_EDIT); - editAdvance(); - curNibble=false; - } catch (std::out_of_range& e) { - } - } else { // value - try { - int num=valueKeys.at(ev.key.keysym.sym); - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); - prepareUndo(GUI_ACTION_PATTERN_EDIT); - if (pat->data[cursor.y][cursor.xFine+1]==-1) pat->data[cursor.y][cursor.xFine+1]=0; - pat->data[cursor.y][cursor.xFine+1]=((pat->data[cursor.y][cursor.xFine+1]<<4)|num)&0xff; - if (cursor.xFine==1) { // instrument - if (pat->data[cursor.y][cursor.xFine+1]>=(int)e->song.ins.size()) { - pat->data[cursor.y][cursor.xFine+1]=(int)e->song.ins.size()-1; - } - makeUndo(GUI_ACTION_PATTERN_EDIT); - if (e->song.ins.size()<16) { - curNibble=false; + if (edit) { + DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); + + prepareUndo(GUI_ACTION_PATTERN_EDIT); + + if (key==100) { // note off + pat->data[cursor.y][0]=100; + pat->data[cursor.y][1]=0; + } else { + pat->data[cursor.y][0]=num%12; + pat->data[cursor.y][1]=num/12; + if (pat->data[cursor.y][0]==0) { + pat->data[cursor.y][0]=12; + pat->data[cursor.y][1]--; + } + pat->data[cursor.y][2]=curIns; + e->noteOn(cursor.xCoarse,curIns,num); + noteOffOnRelease=true; + noteOffOnReleaseKey=ev.key.keysym.sym; + noteOffOnReleaseChan=cursor.xCoarse; + } + makeUndo(GUI_ACTION_PATTERN_EDIT); editAdvance(); + curNibble=false; } else { + e->noteOn(cursor.xCoarse,curIns,num); + noteOffOnRelease=true; + noteOffOnReleaseKey=ev.key.keysym.sym; + noteOffOnReleaseChan=cursor.xCoarse; + } + } catch (std::out_of_range& e) { + } + } else if (edit) { // value + try { + int num=valueKeys.at(ev.key.keysym.sym); + DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); + prepareUndo(GUI_ACTION_PATTERN_EDIT); + if (pat->data[cursor.y][cursor.xFine+1]==-1) pat->data[cursor.y][cursor.xFine+1]=0; + pat->data[cursor.y][cursor.xFine+1]=((pat->data[cursor.y][cursor.xFine+1]<<4)|num)&0xff; + if (cursor.xFine==1) { // instrument + if (pat->data[cursor.y][cursor.xFine+1]>=(int)e->song.ins.size()) { + pat->data[cursor.y][cursor.xFine+1]=(int)e->song.ins.size()-1; + } + makeUndo(GUI_ACTION_PATTERN_EDIT); + if (e->song.ins.size()<16) { + curNibble=false; + editAdvance(); + } else { + curNibble=!curNibble; + if (!curNibble) editAdvance(); + } + } else if (cursor.xFine==2) { // volume + pat->data[cursor.y][cursor.xFine+1]&=e->getMaxVolumeChan(cursor.xCoarse); + makeUndo(GUI_ACTION_PATTERN_EDIT); + if (e->getMaxVolumeChan(cursor.xCoarse)<16) { + curNibble=false; + editAdvance(); + } else { + curNibble=!curNibble; + if (!curNibble) editAdvance(); + } + } else { + makeUndo(GUI_ACTION_PATTERN_EDIT); curNibble=!curNibble; if (!curNibble) editAdvance(); } - } else if (cursor.xFine==2) { // volume - pat->data[cursor.y][cursor.xFine+1]&=e->getMaxVolumeChan(cursor.xCoarse); - makeUndo(GUI_ACTION_PATTERN_EDIT); - if (e->getMaxVolumeChan(cursor.xCoarse)<16) { - curNibble=false; - editAdvance(); - } else { - curNibble=!curNibble; - if (!curNibble) editAdvance(); - } - } else { - makeUndo(GUI_ACTION_PATTERN_EDIT); - curNibble=!curNibble; - if (!curNibble) editAdvance(); + } catch (std::out_of_range& e) { } - } catch (std::out_of_range& e) { } } break; @@ -2183,7 +2202,12 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { } void FurnaceGUI::keyUp(SDL_Event& ev) { - + if (noteOffOnRelease) { + if (ev.key.keysym.sym==noteOffOnReleaseKey) { + noteOffOnRelease=false; + e->noteOff(noteOffOnReleaseChan); + } + } } void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { @@ -2739,6 +2763,7 @@ FurnaceGUI::FurnaceGUI(): e(NULL), quit(false), willCommit(false), + edit(false), curFileDialog(GUI_FILE_OPEN), scrW(1280), scrH(800), @@ -2776,6 +2801,9 @@ FurnaceGUI::FurnaceGUI(): followPattern(true), changeAllOrders(false), curWindow(GUI_WINDOW_NOTHING), + noteOffOnRelease(false), + noteOffOnReleaseKey(0), + noteOffOnReleaseChan(0), arpMacroScroll(0), macroDragStart(0,0), macroDragAreaSize(0,0), diff --git a/src/gui/gui.h b/src/gui/gui.h index a1f69b1e7..02b9e1e46 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1,4 +1,5 @@ #include "../engine/engine.h" +#include "SDL_keycode.h" #include "imgui.h" #include "imgui_impl_sdl.h" #include "imgui_impl_sdlrenderer.h" @@ -130,7 +131,7 @@ class FurnaceGUI { String workingDir, fileName, clipboard, errorString, lastError; - bool quit, willCommit; + bool quit, willCommit, edit; FurnaceGUIFileDialogs curFileDialog; @@ -160,6 +161,10 @@ class FurnaceGUI { bool selecting, curNibble, extraChannelButtons, followOrders, followPattern, changeAllOrders; FurnaceGUIWindows curWindow; + bool noteOffOnRelease; + SDL_Keycode noteOffOnReleaseKey; + int noteOffOnReleaseChan; + std::map noteKeys; std::map valueKeys;