furnace/src/engine/playback.cpp
tildearrow 7568f1a1b9 better YMU759 support
time base now loaded as rate and timer no longer bugs out

also you can save YMU759 songs as .fur now, even if it is pointless
2022-01-09 17:08:14 -05:00

974 lines
28 KiB
C++

#include "dispatch.h"
#include "engine.h"
#include "../ta-log.h"
#include <math.h>
#include <sndfile.h>
void DivEngine::nextOrder() {
curRow=0;
if (repeatPattern) return;
if (++curOrder>=song.ordersLen) {
endOfSong=true;
curOrder=0;
}
}
const char* notes[12]={
"C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"
};
const char* cmdName[DIV_CMD_MAX]={
"NOTE_ON",
"NOTE_OFF",
"INSTRUMENT",
"VOLUME",
"GET_VOLUME",
"GET_VOLMAX",
"NOTE_PORTA",
"PITCH",
"PANNING",
"LEGATO",
"PRE_PORTA",
"PRE_NOTE",
"SAMPLE_MODE",
"SAMPLE_FREQ",
"SAMPLE_BANK",
"FM_LFO",
"FM_LFO_WAVE",
"FM_TL",
"FM_AR",
"FM_FB",
"FM_MULT",
"FM_EXTCH",
"GENESIS_LFO",
"ARCADE_LFO",
"STD_NOISE_FREQ",
"STD_NOISE_MODE",
"WAVE",
"GB_SWEEP_TIME",
"GB_SWEEP_DIR",
"PCE_LFO_MODE",
"PCE_LFO_SPEED",
"C64_CUTOFF",
"C64_RESONANCE",
"C64_FILTER_MODE",
"C64_RESET_TIME",
"C64_RESET_MASK",
"C64_FILTER_RESET",
"C64_DUTY_RESET",
"C64_EXTENDED",
"AY_ENVELOPE_SET",
"AY_ENVELOPE_LOW",
"AY_ENVELOPE_HIGH",
"ALWAYS_SET_VOLUME"
};
const char* formatNote(unsigned char note, unsigned char octave) {
static char ret[4];
if (note==100) {
return "OFF";
} else if (octave==0 && note==0) {
return "---";
}
snprintf(ret,4,"%s%d",notes[note%12],octave+note/12);
return ret;
}
int DivEngine::dispatchCmd(DivCommand c) {
if (view==DIV_STATUS_COMMANDS) {
printf("%8d | %d: %s(%d, %d)\n",totalTicks,c.chan,cmdName[c.cmd],c.value,c.value2);
}
totalCmds++;
c.chan=dispatchChanOfChan[c.dis];
return disCont[dispatchOfChan[c.dis]].dispatch->dispatch(c);
}
bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effectVal) {
switch (sysOfChan[ch]) {
case DIV_SYSTEM_GENESIS:
case DIV_SYSTEM_GENESIS_EXT:
switch (effect) {
case 0x17: // DAC enable
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0)));
break;
case 0x20: // SN noise mode
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
break;
default:
return false;
}
break;
case DIV_SYSTEM_SMS:
switch (effect) {
case 0x20: // SN noise mode
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
break;
default:
return false;
}
break;
case DIV_SYSTEM_GB:
switch (effect) {
case 0x10: // select waveform
dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal));
break;
case 0x11: case 0x12: // duty or noise mode
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
break;
case 0x13: // sweep params
dispatchCmd(DivCommand(DIV_CMD_GB_SWEEP_TIME,ch,effectVal));
break;
case 0x14: // sweep direction
dispatchCmd(DivCommand(DIV_CMD_GB_SWEEP_DIR,ch,effectVal));
break;
default:
return false;
}
break;
case DIV_SYSTEM_PCE:
switch (effect) {
case 0x10: // select waveform
dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal));
break;
case 0x11: // noise mode
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
break;
case 0x12: // LFO mode
dispatchCmd(DivCommand(DIV_CMD_PCE_LFO_MODE,ch,effectVal));
break;
case 0x13: // LFO speed
dispatchCmd(DivCommand(DIV_CMD_PCE_LFO_SPEED,ch,effectVal));
break;
case 0x17: // PCM enable
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0)));
break;
default:
return false;
}
break;
case DIV_SYSTEM_NES:
switch (effect) {
case 0x12: // duty or noise mode
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
break;
default:
return false;
}
break;
default:
return false;
}
return true;
}
bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal) {
switch (sysOfChan[ch]) {
case DIV_SYSTEM_GENESIS:
case DIV_SYSTEM_GENESIS_EXT:
case DIV_SYSTEM_ARCADE:
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_EXT:
switch (effect) {
case 0x10: // LFO or noise mode
if (sysOfChan[ch]==DIV_SYSTEM_ARCADE) {
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal));
} else {
dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal));
}
break;
case 0x11: // FB
dispatchCmd(DivCommand(DIV_CMD_FM_FB,ch,effectVal&7));
break;
case 0x12: // TL op1
dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,0,effectVal&0x7f));
break;
case 0x13: // TL op2
dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,1,effectVal&0x7f));
break;
case 0x14: // TL op3
dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,2,effectVal&0x7f));
break;
case 0x15: // TL op4
dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,3,effectVal&0x7f));
break;
case 0x16: // MULT
if ((effectVal>>4)>0 && (effectVal>>4)<5) {
dispatchCmd(DivCommand(DIV_CMD_FM_MULT,ch,(effectVal>>4)-1,effectVal&15));
}
break;
case 0x17: // arcade LFO
if (sysOfChan[ch]==DIV_SYSTEM_ARCADE) {
dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal));
}
break;
case 0x18: // EXT or LFO waveform
if (sysOfChan[ch]==DIV_SYSTEM_ARCADE) {
dispatchCmd(DivCommand(DIV_CMD_FM_LFO_WAVE,ch,effectVal));
} else {
dispatchCmd(DivCommand(DIV_CMD_FM_EXTCH,ch,effectVal));
}
break;
case 0x19: // AR global
dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,-1,effectVal&31));
break;
case 0x1a: // AR op1
dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,0,effectVal&31));
break;
case 0x1b: // AR op2
dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,1,effectVal&31));
break;
case 0x1c: // AR op3
dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,2,effectVal&31));
break;
case 0x1d: // AR op4
dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,3,effectVal&31));
break;
case 0x20: // PCM frequency or Neo Geo PSG mode
if (sysOfChan[ch]==DIV_SYSTEM_ARCADE) {
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal));
} else if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) {
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
}
break;
case 0x21: // Neo Geo PSG noise freq
if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) {
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal));
}
break;
case 0x22: // UNOFFICIAL: Neo Geo PSG envelope enable
if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) {
dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SET,ch,effectVal));
}
break;
case 0x23: // UNOFFICIAL: Neo Geo PSG envelope period low
if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) {
dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_LOW,ch,effectVal));
}
break;
case 0x24: // UNOFFICIAL: Neo Geo PSG envelope period high
if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) {
dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_HIGH,ch,effectVal));
}
break;
default:
return false;
}
break;
case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580:
switch (effect) {
case 0x10: // select waveform
dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal));
break;
case 0x11: // cutoff
dispatchCmd(DivCommand(DIV_CMD_C64_CUTOFF,ch,effectVal));
break;
case 0x12: // duty
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
break;
case 0x13: // resonance
dispatchCmd(DivCommand(DIV_CMD_C64_RESONANCE,ch,effectVal));
break;
case 0x14: // filter mode
dispatchCmd(DivCommand(DIV_CMD_C64_FILTER_MODE,ch,effectVal));
break;
case 0x15: // reset time
dispatchCmd(DivCommand(DIV_CMD_C64_RESET_TIME,ch,effectVal));
break;
case 0x1a: // reset mask
dispatchCmd(DivCommand(DIV_CMD_C64_RESET_MASK,ch,effectVal));
break;
case 0x1b: // cutoff reset
dispatchCmd(DivCommand(DIV_CMD_C64_FILTER_RESET,ch,effectVal));
break;
case 0x1c: // duty reset
dispatchCmd(DivCommand(DIV_CMD_C64_DUTY_RESET,ch,effectVal));
break;
case 0x1e: // extended
dispatchCmd(DivCommand(DIV_CMD_C64_EXTENDED,ch,effectVal));
break;
default:
return false;
}
break;
default:
return false;
}
return true;
}
void DivEngine::processRow(int i, bool afterDelay) {
int whatOrder=afterDelay?chan[i].delayOrder:curOrder;
int whatRow=afterDelay?chan[i].delayRow:curRow;
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][whatOrder],false);
// pre effects
if (!afterDelay) for (int j=0; j<song.pat[i].effectRows; j++) {
short effect=pat->data[whatRow][4+(j<<1)];
short effectVal=pat->data[whatRow][5+(j<<1)];
if (effectVal==-1) effectVal=0;
if (effect==0xed && effectVal!=0) {
if (effectVal<=nextSpeed) {
chan[i].rowDelay=effectVal+1;
chan[i].delayOrder=whatOrder;
chan[i].delayRow=whatRow;
if (effectVal==nextSpeed) {
if (sysOfChan[i]!=DIV_SYSTEM_YM2610 && sysOfChan[i]!=DIV_SYSTEM_YM2610_EXT) chan[i].delayLocked=true;
} else {
chan[i].delayLocked=false;
}
return;
} else {
chan[i].delayLocked=false;
}
}
}
if (chan[i].delayLocked) return;
// instrument
if (pat->data[whatRow][2]!=-1) {
dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2]));
}
// note
if (pat->data[whatRow][0]==100) {
//chan[i].note=-1;
chan[i].keyOn=false;
if (chan[i].stopOnOff) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
chan[i].stopOnOff=false;
}
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) {
chan[i+1].portaNote=-1;
chan[i+1].portaSpeed=-1;
}
}
dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i));
} else if (!(pat->data[whatRow][0]==0 && pat->data[whatRow][1]==0)) {
chan[i].oldNote=chan[i].note;
chan[i].note=pat->data[whatRow][0]+pat->data[whatRow][1]*12;
if (!chan[i].keyOn) {
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsArp(dispatchChanOfChan[i])) {
chan[i].arp=0;
}
}
chan[i].doNote=true;
if (chan[i].arp!=0) {
chan[i].arpYield=true;
}
}
// volume
if (pat->data[whatRow][3]!=-1) {
if (dispatchCmd(DivCommand(DIV_ALWAYS_SET_VOLUME,i)) || (MIN(chan[i].volMax,chan[i].volume)>>8)!=pat->data[whatRow][3]) {
chan[i].volume=pat->data[whatRow][3]<<8;
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
}
}
// effects
for (int j=0; j<song.pat[i].effectRows; j++) {
short effect=pat->data[whatRow][4+(j<<1)];
short effectVal=pat->data[whatRow][5+(j<<1)];
if (effectVal==-1) effectVal=0;
// per-system effect
if (!perSystemEffect(i,effect,effectVal)) switch (effect) {
case 0x09: // speed 1
speed1=effectVal;
break;
case 0x0f: // speed 2
speed2=effectVal;
break;
case 0x0b: // change order
if (changeOrd<0) {
changeOrd=effectVal;
changePos=0;
}
break;
case 0x0d: // next order
if (changeOrd<0) {
changeOrd=-2;
changePos=effectVal;
}
break;
case 0x08: // panning
dispatchCmd(DivCommand(DIV_CMD_PANNING,i,effectVal));
break;
case 0x01: // ramp up
if (effectVal==0) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
chan[i].inPorta=false;
} else {
chan[i].portaNote=0x60;
chan[i].portaSpeed=effectVal;
chan[i].portaStop=true;
chan[i].nowYouCanStop=false;
chan[i].stopOnOff=false;
}
break;
case 0x02: // ramp down
if (effectVal==0) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
chan[i].inPorta=false;
} else {
chan[i].portaNote=disCont[dispatchOfChan[i]].dispatch->getPortaFloor(dispatchChanOfChan[i]);
chan[i].portaSpeed=effectVal;
chan[i].portaStop=true;
chan[i].nowYouCanStop=false;
chan[i].stopOnOff=false;
}
break;
case 0x03: // portamento
if (effectVal==0) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
chan[i].inPorta=false;
dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false));
} else {
if (chan[i].note==chan[i].oldNote && !chan[i].inPorta) {
chan[i].portaNote=chan[i].note;
chan[i].portaSpeed=-1;
} else {
chan[i].portaNote=chan[i].note;
chan[i].portaSpeed=effectVal;
chan[i].inPorta=true;
}
chan[i].portaStop=true;
if (chan[i].keyOn) chan[i].doNote=false;
chan[i].stopOnOff=true;
dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true));
}
break;
case 0x04: // vibrato
chan[i].vibratoDepth=effectVal&15;
chan[i].vibratoRate=effectVal>>4;
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
break;
case 0x0a: // volume ramp
if (effectVal!=0) {
if ((effectVal&15)!=0) {
chan[i].volSpeed=-(effectVal&15)*64;
} else {
chan[i].volSpeed=(effectVal>>4)*64;
}
} else {
chan[i].volSpeed=0;
}
break;
case 0x00: // arpeggio
chan[i].arp=effectVal;
break;
case 0x0c: // retrigger
if (effectVal!=0) {
chan[i].rowDelay=effectVal; // this was +1 before. what happened?!
chan[i].delayOrder=whatOrder;
chan[i].delayRow=whatRow;
}
break;
case 0xe0: // arp speed
song.arpLen=effectVal;
break;
case 0xe1: // portamento up
chan[i].portaNote=chan[i].note+(effectVal&15);
chan[i].portaSpeed=(effectVal>>4)*4;
chan[i].portaStop=true;
chan[i].nowYouCanStop=false;
chan[i].stopOnOff=true;
break;
case 0xe2: // portamento down
chan[i].portaNote=chan[i].note-(effectVal&15);
chan[i].portaSpeed=(effectVal>>4)*4;
chan[i].portaStop=true;
chan[i].nowYouCanStop=false;
chan[i].stopOnOff=true;
break;
case 0xe3: // vibrato direction
chan[i].vibratoDir=effectVal;
break;
case 0xe4: // vibrato fine
chan[i].vibratoFine=effectVal;
break;
case 0xe5: // pitch
chan[i].pitch=effectVal-0x80;
if (sysOfChan[i]==DIV_SYSTEM_ARCADE) { // arcade pitch oddity
chan[i].pitch*=2;
if (chan[i].pitch<-128) chan[i].pitch=-128;
if (chan[i].pitch>127) chan[i].pitch=127;
}
chan[i].pitch+=globalPitch;
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
break;
case 0xea: // legato mode
chan[i].legato=effectVal;
break;
case 0xeb: // sample bank
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_BANK,i,effectVal));
break;
case 0xec: // delayed note cut
if (effectVal>0 && effectVal<nextSpeed) {
chan[i].cut=effectVal+1;
}
break;
case 0xee: // external command
//printf("\x1b[1;36m%d: extern command %d\x1b[m\n",i,effectVal);
extValue=effectVal;
extValuePresent=true;
break;
case 0xef: // global pitch
globalPitch+=(signed char)(effectVal-0x80);
break;
}
}
if (chan[i].doNote) {
chan[i].vibratoPos=0;
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
if (chan[i].legato) {
dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
} else {
dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,chan[i].note,chan[i].volume>>8));
}
chan[i].doNote=false;
/*
if (!chan[i].keyOn) {
if (chan[i].portaStop && chan[i].nowYouCanStop) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
}
}*/
if (!chan[i].keyOn && chan[i].volume>chan[i].volMax) {
chan[i].volume=chan[i].volMax;
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
}
chan[i].keyOn=true;
}
chan[i].nowYouCanStop=true;
// post effects
for (int j=0; j<song.pat[i].effectRows; j++) {
short effect=pat->data[whatRow][4+(j<<1)];
short effectVal=pat->data[whatRow][5+(j<<1)];
if (effectVal==-1) effectVal=0;
perSystemPostEffect(i,effect,effectVal);
}
}
void DivEngine::nextRow() {
static char pb[4096];
static char pb1[4096];
static char pb2[4096];
static char pb3[4096];
if (view==DIV_STATUS_PATTERN) {
strcpy(pb1,"");
strcpy(pb3,"");
for (int i=0; i<chans; i++) {
snprintf(pb,4095," %.2x",song.orders.ord[i][curOrder]);
strcat(pb1,pb);
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][curOrder],false);
snprintf(pb2,4095,"\x1b[37m %s",
formatNote(pat->data[curRow][0],pat->data[curRow][1]));
strcat(pb3,pb2);
if (pat->data[curRow][3]==-1) {
strcat(pb3,"\x1b[m--");
} else {
snprintf(pb2,4095,"\x1b[1;32m%.2x",pat->data[curRow][3]);
strcat(pb3,pb2);
}
if (pat->data[curRow][2]==-1) {
strcat(pb3,"\x1b[m--");
} else {
snprintf(pb2,4095,"\x1b[0;36m%.2x",pat->data[curRow][2]);
strcat(pb3,pb2);
}
for (int j=0; j<song.pat[i].effectRows; j++) {
if (pat->data[curRow][4+(j<<1)]==-1) {
strcat(pb3,"\x1b[m--");
} else {
snprintf(pb2,4095,"\x1b[1;31m%.2x",pat->data[curRow][4+(j<<1)]);
strcat(pb3,pb2);
}
if (pat->data[curRow][5+(j<<1)]==-1) {
strcat(pb3,"\x1b[m--");
} else {
snprintf(pb2,4095,"\x1b[1;37m%.2x",pat->data[curRow][5+(j<<1)]);
strcat(pb3,pb2);
}
}
}
printf("| %.2x:%s | \x1b[1;33m%3d%s\x1b[m\n",curOrder,pb1,curRow,pb3);
}
for (int i=0; i<chans; i++) {
chan[i].rowDelay=0;
processRow(i,false);
}
if (++curRow>=song.patLen) {
nextOrder();
}
if (changeOrd!=-1) {
if (repeatPattern) {
curRow=0;
changeOrd=-1;
} else {
curRow=changePos;
if (changeOrd==-2) changeOrd=curOrder+1;
if (changeOrd<=curOrder) endOfSong=true;
curOrder=changeOrd;
if (curOrder>=song.ordersLen) {
curOrder=0;
endOfSong=true;
}
changeOrd=-1;
}
}
if (speedAB) {
ticks=speed2*(song.timeBase+1);
nextSpeed=speed1;
} else {
ticks=speed1*(song.timeBase+1);
nextSpeed=speed2;
}
speedAB=!speedAB;
// post row details
for (int i=0; i<chans; i++) {
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][curOrder],false);
if (!(pat->data[curRow][0]==0 && pat->data[curRow][1]==0)) {
if (pat->data[curRow][0]!=100) {
dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks));
}
}
}
}
bool DivEngine::nextTick(bool noAccum) {
bool ret=false;
int divider=60;
if (song.customTempo) {
divider=song.hz;
} else {
if (song.pal) {
divider=60;
} else {
divider=50;
}
}
for (int i=0; i<song.systemLen; i++) {
DivDispatchContainer& dc=disCont[i];
dc.cycles=dc.dispatch->rate/divider;
dc.clockDrift+=dc.dispatch->rate%divider;
if (dc.clockDrift>=divider) {
dc.clockDrift-=divider;
dc.cycles++;
}
}
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));
}
pendingNotes.pop();
}
if (!freelance) {
if (--ticks<=0) {
ret=endOfSong;
if (endOfSong) {
playSub(true);
}
endOfSong=false;
nextRow();
}
// process stuff
for (int i=0; i<chans; i++) {
if (chan[i].rowDelay>0) {
if (--chan[i].rowDelay==0) {
processRow(i,true);
}
}
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
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->tick();
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);
if ((totalTicks%hz)==0) {
cmdsPerSecond=totalCmds-lastCmds;
lastCmds=totalCmds;
}
}
return ret;
}
// TODO: all of this!
// PLAYBACK LOGIC:
// 1. end of buffer for all chips? if so quit
// 2. are all chips clocked yet?
// if not clock them until possible and try again
// 3. engine tick
void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size) {
if (out!=NULL) {
memset(out[0],0,size*sizeof(float));
memset(out[1],0,size*sizeof(float));
}
isBusy.lock();
if (out!=NULL && sPreview.sample>=0 && sPreview.sample<(int)song.sample.size()) {
DivSample* s=song.sample[sPreview.sample];
size_t prevtotal=blip_clocks_needed(samp_bb,size);
for (size_t i=0; i<prevtotal; i++) {
if (sPreview.pos>=s->rendLength) {
samp_temp=0;
} else {
samp_temp=s->rendData[sPreview.pos++];
}
if (s->depth==8) samp_temp<<=8;
blip_add_delta(samp_bb,i,samp_temp-samp_prevSample);
samp_prevSample=samp_temp;
}
if (sPreview.pos>=s->rendLength) sPreview.sample=-1;
blip_end_frame(samp_bb,prevtotal);
blip_read_samples(samp_bb,samp_bbOut,size,0);
for (size_t i=0; i<size; i++) {
out[0][i]+=(float)samp_bbOut[i]/32768.0;
out[1][i]+=(float)samp_bbOut[i]/32768.0;
}
}
if (!playing) {
isBusy.unlock();
return;
}
// logic starts here
size_t runtotal[32];
size_t runLeft[32];
size_t runPos[32];
for (int i=0; i<song.systemLen; i++) {
runtotal[i]=blip_clocks_needed(disCont[i].bb[0],size);
if (runtotal[i]>disCont[i].bbInLen) {
delete disCont[i].bbIn[0];
delete disCont[i].bbIn[1];
disCont[i].bbIn[0]=new short[runtotal[i]+256];
disCont[i].bbIn[1]=new short[runtotal[i]+256];
disCont[i].bbInLen=runtotal[i]+256;
}
runLeft[i]=runtotal[i];
runPos[i]=0;
}
if (metroTickLen<size) {
if (metroTick!=NULL) delete[] metroTick;
metroTick=new unsigned char[size];
metroTickLen=size;
}
memset(metroTick,0,size);
while (true) {
bool allDone=true;
bool getOut=true;
// 1. check whether we are done with all buffers
for (int i=0; i<song.systemLen; i++) {
if (runLeft[i]>0) {
getOut=false;
break;
}
}
if (getOut) break;
// 2. check whether we gonna tick
for (int i=0; i<song.systemLen; i++) {
if (disCont[i].cycles>0) {
allDone=false;
break;
}
}
if (allDone) {
// we have to tick
unsigned int realPos=(runPos[0]*size)/runtotal[0];
if (realPos>=size) realPos=size-1;
if (song.hilightA>0) {
if ((curRow%song.hilightA)==0 && ticks==1) metroTick[realPos]=1;
}
if (song.hilightB>0) {
if ((curRow%song.hilightB)==0 && ticks==1) metroTick[realPos]=2;
}
if (nextTick()) {
if (remainingLoops>0) {
remainingLoops--;
if (!remainingLoops) logI("end of song!\n");
}
}
}
// 3. fill buffers as needed
for (int i=0; i<song.systemLen; i++) {
if (runLeft[i]<=0) continue;
int total=runLeft[i];
if (total>disCont[i].cycles) total=disCont[i].cycles;
runLeft[i]-=total;
disCont[i].cycles-=total;
disCont[i].acquire(runPos[i],total);
runPos[i]+=total;
}
}
totalProcessed=(1+runPos[0])*got.rate/disCont[0].dispatch->rate;
for (int i=0; i<song.systemLen; i++) {
disCont[i].fillBuf(runtotal[i],size);
}
if (out==NULL) {
isBusy.unlock();
return;
}
for (int i=0; i<song.systemLen; i++) {
if (disCont[i].dispatch->isStereo()) {
for (size_t j=0; j<size; j++) {
out[0][j]+=(float)disCont[i].bbOut[0][j]/16384.0;
out[1][j]+=(float)disCont[i].bbOut[1][j]/16384.0;
}
} else {
for (size_t j=0; j<size; j++) {
out[0][j]+=(float)disCont[i].bbOut[0][j]/16384.0;
out[1][j]+=(float)disCont[i].bbOut[0][j]/16384.0;
}
}
}
if (metronome) for (size_t i=0; i<size; i++) {
if (metroTick[i]) {
if (metroTick[i]==2) {
metroFreq=1400/got.rate;
} else {
metroFreq=1050/got.rate;
}
metroPos=0;
metroAmp=0.7f;
}
if (metroAmp>0.0f) {
out[0][i]+=(sin(metroPos*2*M_PI))*metroAmp;
out[1][i]+=(sin(metroPos*2*M_PI))*metroAmp;
}
metroAmp-=0.0003f;
if (metroAmp<0.0f) metroAmp=0.0f;
metroPos+=metroFreq;
while (metroPos>=1) metroPos--;
}
isBusy.unlock();
}