add note preview feature

This commit is contained in:
tildearrow 2021-12-28 18:23:57 -05:00
parent 0479650597
commit be3b4da834
6 changed files with 243 additions and 150 deletions

View file

@ -1542,12 +1542,14 @@ int DivEngine::calcFreq(int base, int pitch, bool period) {
void DivEngine::play() { void DivEngine::play() {
isBusy.lock(); isBusy.lock();
freelance=false;
playSub(false); playSub(false);
isBusy.unlock(); isBusy.unlock();
} }
void DivEngine::stop() { void DivEngine::stop() {
isBusy.lock(); isBusy.lock();
freelance=false;
playing=false; playing=false;
extValuePresent=false; extValuePresent=false;
isBusy.unlock(); isBusy.unlock();
@ -1693,7 +1695,7 @@ unsigned char DivEngine::getExtValue() {
} }
bool DivEngine::isPlaying() { bool DivEngine::isPlaying() {
return playing; return (playing && !freelance);
} }
bool DivEngine::isChannelMuted(int chan) { bool DivEngine::isChannelMuted(int chan) {
@ -1902,7 +1904,7 @@ void DivEngine::addOrder(bool duplicate, bool where) {
} }
song.ordersLen++; song.ordersLen++;
curOrder++; curOrder++;
if (playing) { if (playing && !freelance) {
playSub(false); playSub(false);
} }
} }
@ -1919,7 +1921,7 @@ void DivEngine::deleteOrder() {
} }
song.ordersLen--; song.ordersLen--;
if (curOrder>=song.ordersLen) curOrder=song.ordersLen-1; if (curOrder>=song.ordersLen) curOrder=song.ordersLen-1;
if (playing) { if (playing && !freelance) {
playSub(false); playSub(false);
} }
isBusy.unlock(); isBusy.unlock();
@ -1937,7 +1939,7 @@ void DivEngine::moveOrderUp() {
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder-1]; song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder-1];
} }
curOrder--; curOrder--;
if (playing) { if (playing && !freelance) {
playSub(false); playSub(false);
} }
isBusy.unlock(); isBusy.unlock();
@ -1955,17 +1957,39 @@ void DivEngine::moveOrderDown() {
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder+1]; song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder+1];
} }
curOrder++; curOrder++;
if (playing) { if (playing && !freelance) {
playSub(false); playSub(false);
} }
isBusy.unlock(); 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) { void DivEngine::setOrder(unsigned char order) {
isBusy.lock(); isBusy.lock();
curOrder=order; curOrder=order;
if (order>=song.ordersLen) curOrder=0; if (order>=song.ordersLen) curOrder=0;
if (playing) { if (playing && !freelance) {
playSub(false); playSub(false);
} }
isBusy.unlock(); isBusy.unlock();

View file

@ -7,6 +7,7 @@
#include "blip_buf.h" #include "blip_buf.h"
#include <mutex> #include <mutex>
#include <map> #include <map>
#include <queue>
#define DIV_VERSION "0.1" #define DIV_VERSION "0.1"
#define DIV_ENGINE_VERSION 11 #define DIV_ENGINE_VERSION 11
@ -67,6 +68,17 @@ struct DivChannelState {
inPorta(false) {} 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 { class DivEngine {
DivDispatch* dispatch; DivDispatch* dispatch;
TAAudio* output; TAAudio* output;
@ -74,6 +86,7 @@ class DivEngine {
int chans; int chans;
bool active; bool active;
bool playing; bool playing;
bool freelance;
bool speedAB; bool speedAB;
bool endOfSong; bool endOfSong;
bool consoleMode; bool consoleMode;
@ -87,6 +100,7 @@ class DivEngine {
DivChannelState chan[17]; DivChannelState chan[17];
DivAudioEngines audioEngine; DivAudioEngines audioEngine;
std::map<String,String> conf; std::map<String,String> conf;
std::queue<DivNoteEvent> pendingNotes;
bool isMuted[17]; bool isMuted[17];
std::mutex isBusy; std::mutex isBusy;
String configPath; String configPath;
@ -302,6 +316,12 @@ class DivEngine {
// move order down // move order down
void moveOrderDown(); void moveOrderDown();
// play note
void noteOn(int chan, int ins, int note, int vol=-1);
// stop note
void noteOff(int chan);
// go to order // go to order
void setOrder(unsigned char order); void setOrder(unsigned char order);
@ -347,6 +367,7 @@ class DivEngine {
chans(0), chans(0),
active(false), active(false),
playing(false), playing(false),
freelance(false),
speedAB(false), speedAB(false),
endOfSong(false), endOfSong(false),
consoleMode(false), consoleMode(false),

View file

@ -705,108 +705,123 @@ bool DivEngine::nextTick(bool noAccum) {
cycles++; cycles++;
} }
if (--ticks<=0) { while (!pendingNotes.empty()) {
ret=endOfSong; DivNoteEvent& note=pendingNotes.front();
if (endOfSong) { if (note.on) {
playSub(true); 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; pendingNotes.pop();
nextRow();
} }
// process stuff
for (int i=0; i<chans; i++) { if (!freelance) {
if (chan[i].rowDelay>0) { if (--ticks<=0) {
if (--chan[i].rowDelay==0) { ret=endOfSong;
processRow(i,true); if (endOfSong) {
playSub(true);
} }
endOfSong=false;
nextRow();
} }
if (chan[i].volSpeed!=0) { // process stuff
chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8); for (int i=0; i<chans; i++) {
chan[i].volume+=chan[i].volSpeed; if (chan[i].rowDelay>0) {
if (chan[i].volume>chan[i].volMax) { if (--chan[i].rowDelay==0) {
chan[i].volume=chan[i].volMax; processRow(i,true);
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 { if (chan[i].volSpeed!=0) {
chan[i].arpYield=false; 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 // system tick
dispatch->tick(); dispatch->tick();
if (!noAccum) totalTicks++; if (!freelance) {
if (!noAccum) totalTicks++;
int hz; int hz;
if (song.customTempo) { if (song.customTempo) {
hz=song.hz; hz=song.hz;
} else if (song.pal) { } else if (song.pal) {
hz=60; hz=60;
} else { } else {
hz=50; 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 (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) { if ((totalTicks%hz)==0) {
cmdsPerSecond=totalCmds-lastCmds; cmdsPerSecond=totalCmds-lastCmds;
lastCmds=totalCmds; lastCmds=totalCmds;
}
} }
return ret; return ret;

View file

@ -124,7 +124,7 @@ struct DivSong {
speed1(6), speed1(6),
speed2(6), speed2(6),
arpLen(1), arpLen(1),
pal(false), pal(true),
customTempo(false), customTempo(false),
hz(60), hz(60),
patLen(64), patLen(64),

View file

@ -270,6 +270,8 @@ void FurnaceGUI::drawEditControls() {
if (ImGui::Button(ICON_FA_STOP "##Stop")) { if (ImGui::Button(ICON_FA_STOP "##Stop")) {
e->stop(); e->stop();
} }
ImGui::SameLine();
ImGui::Checkbox("Edit",&edit);
ImGui::Text("Follow"); ImGui::Text("Follow");
ImGui::SameLine(); ImGui::SameLine();
@ -2083,6 +2085,9 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
} else if (ev.key.keysym.mod&KMOD_ALT) { } else if (ev.key.keysym.mod&KMOD_ALT) {
// nothing. prevents accidental OFF note. // nothing. prevents accidental OFF note.
} else switch (ev.key.keysym.sym) { } else switch (ev.key.keysym.sym) {
case SDLK_SPACE:
edit=!edit;
break;
case SDLK_UP: case SDLK_UP:
moveCursor(0,-1); moveCursor(0,-1);
break; break;
@ -2111,66 +2116,80 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
doInsert(); doInsert();
break; break;
default: default:
if (cursor.xFine==0) { // note if (!ev.key.repeat) {
try { if (cursor.xFine==0) { // note
int key=noteKeys.at(ev.key.keysym.sym); try {
int num=12*curOctave+key; int key=noteKeys.at(ev.key.keysym.sym);
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); int num=12*curOctave+key;
prepareUndo(GUI_ACTION_PATTERN_EDIT);
if (key==100) { // note off if (edit) {
pat->data[cursor.y][0]=100; DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true);
pat->data[cursor.y][1]=0;
} else { prepareUndo(GUI_ACTION_PATTERN_EDIT);
pat->data[cursor.y][0]=num%12;
pat->data[cursor.y][1]=num/12; if (key==100) { // note off
if (pat->data[cursor.y][0]==0) { pat->data[cursor.y][0]=100;
pat->data[cursor.y][0]=12; pat->data[cursor.y][1]=0;
pat->data[cursor.y][1]--; } else {
} pat->data[cursor.y][0]=num%12;
pat->data[cursor.y][2]=curIns; pat->data[cursor.y][1]=num/12;
} if (pat->data[cursor.y][0]==0) {
makeUndo(GUI_ACTION_PATTERN_EDIT); pat->data[cursor.y][0]=12;
editAdvance(); pat->data[cursor.y][1]--;
curNibble=false; }
} catch (std::out_of_range& e) { pat->data[cursor.y][2]=curIns;
} e->noteOn(cursor.xCoarse,curIns,num);
} else { // value noteOffOnRelease=true;
try { noteOffOnReleaseKey=ev.key.keysym.sym;
int num=valueKeys.at(ev.key.keysym.sym); noteOffOnReleaseChan=cursor.xCoarse;
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); }
prepareUndo(GUI_ACTION_PATTERN_EDIT); makeUndo(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(); editAdvance();
curNibble=false;
} else { } 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; curNibble=!curNibble;
if (!curNibble) editAdvance(); if (!curNibble) editAdvance();
} }
} else if (cursor.xFine==2) { // volume } catch (std::out_of_range& e) {
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) {
} }
} }
break; break;
@ -2183,7 +2202,12 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
} }
void FurnaceGUI::keyUp(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) { void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
@ -2739,6 +2763,7 @@ FurnaceGUI::FurnaceGUI():
e(NULL), e(NULL),
quit(false), quit(false),
willCommit(false), willCommit(false),
edit(false),
curFileDialog(GUI_FILE_OPEN), curFileDialog(GUI_FILE_OPEN),
scrW(1280), scrW(1280),
scrH(800), scrH(800),
@ -2776,6 +2801,9 @@ FurnaceGUI::FurnaceGUI():
followPattern(true), followPattern(true),
changeAllOrders(false), changeAllOrders(false),
curWindow(GUI_WINDOW_NOTHING), curWindow(GUI_WINDOW_NOTHING),
noteOffOnRelease(false),
noteOffOnReleaseKey(0),
noteOffOnReleaseChan(0),
arpMacroScroll(0), arpMacroScroll(0),
macroDragStart(0,0), macroDragStart(0,0),
macroDragAreaSize(0,0), macroDragAreaSize(0,0),

View file

@ -1,4 +1,5 @@
#include "../engine/engine.h" #include "../engine/engine.h"
#include "SDL_keycode.h"
#include "imgui.h" #include "imgui.h"
#include "imgui_impl_sdl.h" #include "imgui_impl_sdl.h"
#include "imgui_impl_sdlrenderer.h" #include "imgui_impl_sdlrenderer.h"
@ -130,7 +131,7 @@ class FurnaceGUI {
String workingDir, fileName, clipboard, errorString, lastError; String workingDir, fileName, clipboard, errorString, lastError;
bool quit, willCommit; bool quit, willCommit, edit;
FurnaceGUIFileDialogs curFileDialog; FurnaceGUIFileDialogs curFileDialog;
@ -160,6 +161,10 @@ class FurnaceGUI {
bool selecting, curNibble, extraChannelButtons, followOrders, followPattern, changeAllOrders; bool selecting, curNibble, extraChannelButtons, followOrders, followPattern, changeAllOrders;
FurnaceGUIWindows curWindow; FurnaceGUIWindows curWindow;
bool noteOffOnRelease;
SDL_Keycode noteOffOnReleaseKey;
int noteOffOnReleaseChan;
std::map<SDL_Keycode,int> noteKeys; std::map<SDL_Keycode,int> noteKeys;
std::map<SDL_Keycode,int> valueKeys; std::map<SDL_Keycode,int> valueKeys;