add experimental command stream player
for verification after that I am going to write optimization code
This commit is contained in:
parent
24c39c7819
commit
c4510e16e0
|
@ -469,6 +469,7 @@ src/engine/blip_buf.c
|
||||||
src/engine/brrUtils.c
|
src/engine/brrUtils.c
|
||||||
src/engine/safeReader.cpp
|
src/engine/safeReader.cpp
|
||||||
src/engine/safeWriter.cpp
|
src/engine/safeWriter.cpp
|
||||||
|
src/engine/cmdStream.cpp
|
||||||
src/engine/config.cpp
|
src/engine/config.cpp
|
||||||
src/engine/configEngine.cpp
|
src/engine/configEngine.cpp
|
||||||
src/engine/dispatchContainer.cpp
|
src/engine/dispatchContainer.cpp
|
||||||
|
|
301
src/engine/cmdStream.cpp
Normal file
301
src/engine/cmdStream.cpp
Normal file
|
@ -0,0 +1,301 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "cmdStream.h"
|
||||||
|
#include "engine.h"
|
||||||
|
#include "../ta-log.h"
|
||||||
|
|
||||||
|
void DivCSPlayer::cleanup() {
|
||||||
|
delete b;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DivCSPlayer::tick() {
|
||||||
|
bool ticked=false;
|
||||||
|
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||||
|
bool sendVolume=false;
|
||||||
|
if (chan[i].readPos==0) continue;
|
||||||
|
|
||||||
|
ticked=true;
|
||||||
|
|
||||||
|
chan[i].waitTicks--;
|
||||||
|
while (chan[i].waitTicks<=0) {
|
||||||
|
stream.seek(chan[i].readPos,SEEK_SET);
|
||||||
|
unsigned char next=stream.readC();
|
||||||
|
unsigned char command=0;
|
||||||
|
|
||||||
|
if (next<0xb3) { // note
|
||||||
|
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,next-60));
|
||||||
|
} else if (next>=0xd0 && next<=0xdf) {
|
||||||
|
command=fastCmds[next&15];
|
||||||
|
} else if (next>=0xe0 && next<=0xef) { // preset delay
|
||||||
|
chan[i].waitTicks=fastDelays[next&15];
|
||||||
|
} else switch (next) {
|
||||||
|
case 0xb4: // note on null
|
||||||
|
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL));
|
||||||
|
break;
|
||||||
|
case 0xb5: // note off
|
||||||
|
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i));
|
||||||
|
break;
|
||||||
|
case 0xb6: // note off env
|
||||||
|
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF_ENV,i));
|
||||||
|
break;
|
||||||
|
case 0xb7: // env release
|
||||||
|
e->dispatchCmd(DivCommand(DIV_CMD_ENV_RELEASE,i));
|
||||||
|
break;
|
||||||
|
case 0xb8: case 0xbe: case 0xc0: case 0xc2:
|
||||||
|
case 0xc3: case 0xc4: case 0xc5: case 0xc6:
|
||||||
|
case 0xc7: case 0xc8: case 0xc9: case 0xca:
|
||||||
|
command=next-0xb4;
|
||||||
|
break;
|
||||||
|
case 0xf7:
|
||||||
|
command=stream.readC();
|
||||||
|
break;
|
||||||
|
case 0xf8:
|
||||||
|
logE("TODO: CALL");
|
||||||
|
break;
|
||||||
|
case 0xf9:
|
||||||
|
logE("TODO: RET");
|
||||||
|
break;
|
||||||
|
case 0xfa:
|
||||||
|
logE("TODO: JMP");
|
||||||
|
break;
|
||||||
|
case 0xfb:
|
||||||
|
logE("TODO: RATE");
|
||||||
|
break;
|
||||||
|
case 0xfc:
|
||||||
|
chan[i].waitTicks=(unsigned short)stream.readS();
|
||||||
|
break;
|
||||||
|
case 0xfd:
|
||||||
|
chan[i].waitTicks=(unsigned char)stream.readC();
|
||||||
|
break;
|
||||||
|
case 0xfe:
|
||||||
|
chan[i].waitTicks=1;
|
||||||
|
break;
|
||||||
|
case 0xff:
|
||||||
|
chan[i].readPos=0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chan[i].readPos==0) break;
|
||||||
|
|
||||||
|
if (command) {
|
||||||
|
int arg0=0;
|
||||||
|
int arg1=0;
|
||||||
|
switch (command) {
|
||||||
|
case DIV_CMD_INSTRUMENT:
|
||||||
|
case DIV_CMD_HINT_VIBRATO_RANGE:
|
||||||
|
case DIV_CMD_HINT_VIBRATO_SHAPE:
|
||||||
|
case DIV_CMD_HINT_PITCH:
|
||||||
|
case DIV_CMD_HINT_VOLUME:
|
||||||
|
arg0=(unsigned char)stream.readC();
|
||||||
|
break;
|
||||||
|
case DIV_CMD_PANNING:
|
||||||
|
case DIV_CMD_HINT_VIBRATO:
|
||||||
|
case DIV_CMD_HINT_ARPEGGIO:
|
||||||
|
case DIV_CMD_HINT_PORTA:
|
||||||
|
arg0=(unsigned char)stream.readC();
|
||||||
|
arg1=(unsigned char)stream.readC();
|
||||||
|
break;
|
||||||
|
case DIV_CMD_PRE_PORTA:
|
||||||
|
arg0=(unsigned char)stream.readC();
|
||||||
|
arg1=(arg0&0x40)?1:0;
|
||||||
|
arg0=(arg0&0x80)?1:0;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_HINT_VOL_SLIDE:
|
||||||
|
arg0=(short)stream.readS();
|
||||||
|
break;
|
||||||
|
case DIV_CMD_SAMPLE_MODE:
|
||||||
|
case DIV_CMD_SAMPLE_FREQ:
|
||||||
|
case DIV_CMD_SAMPLE_BANK:
|
||||||
|
case DIV_CMD_SAMPLE_POS:
|
||||||
|
case DIV_CMD_SAMPLE_DIR:
|
||||||
|
case DIV_CMD_FM_HARD_RESET:
|
||||||
|
case DIV_CMD_FM_LFO:
|
||||||
|
case DIV_CMD_FM_LFO_WAVE:
|
||||||
|
case DIV_CMD_FM_FB:
|
||||||
|
case DIV_CMD_FM_EXTCH:
|
||||||
|
case DIV_CMD_FM_AM_DEPTH:
|
||||||
|
case DIV_CMD_FM_PM_DEPTH:
|
||||||
|
case DIV_CMD_STD_NOISE_FREQ:
|
||||||
|
case DIV_CMD_STD_NOISE_MODE:
|
||||||
|
case DIV_CMD_WAVE:
|
||||||
|
case DIV_CMD_GB_SWEEP_TIME:
|
||||||
|
case DIV_CMD_GB_SWEEP_DIR:
|
||||||
|
case DIV_CMD_PCE_LFO_MODE:
|
||||||
|
case DIV_CMD_PCE_LFO_SPEED:
|
||||||
|
case DIV_CMD_NES_DMC:
|
||||||
|
case DIV_CMD_C64_CUTOFF:
|
||||||
|
case DIV_CMD_C64_RESONANCE:
|
||||||
|
case DIV_CMD_C64_FILTER_MODE:
|
||||||
|
case DIV_CMD_C64_RESET_TIME:
|
||||||
|
case DIV_CMD_C64_RESET_MASK:
|
||||||
|
case DIV_CMD_C64_FILTER_RESET:
|
||||||
|
case DIV_CMD_C64_DUTY_RESET:
|
||||||
|
case DIV_CMD_C64_EXTENDED:
|
||||||
|
case DIV_CMD_AY_ENVELOPE_SET:
|
||||||
|
case DIV_CMD_AY_ENVELOPE_LOW:
|
||||||
|
case DIV_CMD_AY_ENVELOPE_HIGH:
|
||||||
|
case DIV_CMD_AY_ENVELOPE_SLIDE:
|
||||||
|
case DIV_CMD_AY_NOISE_MASK_AND:
|
||||||
|
case DIV_CMD_AY_NOISE_MASK_OR:
|
||||||
|
case DIV_CMD_AY_AUTO_ENVELOPE:
|
||||||
|
case DIV_CMD_FDS_MOD_DEPTH:
|
||||||
|
case DIV_CMD_FDS_MOD_HIGH:
|
||||||
|
case DIV_CMD_FDS_MOD_LOW:
|
||||||
|
case DIV_CMD_FDS_MOD_POS:
|
||||||
|
case DIV_CMD_FDS_MOD_WAVE:
|
||||||
|
case DIV_CMD_SAA_ENVELOPE:
|
||||||
|
case DIV_CMD_AMIGA_FILTER:
|
||||||
|
case DIV_CMD_AMIGA_AM:
|
||||||
|
case DIV_CMD_AMIGA_PM:
|
||||||
|
case DIV_CMD_MACRO_OFF:
|
||||||
|
case DIV_CMD_MACRO_ON:
|
||||||
|
arg0=(unsigned char)stream.readC();
|
||||||
|
break;
|
||||||
|
case DIV_CMD_FM_TL:
|
||||||
|
case DIV_CMD_FM_AM:
|
||||||
|
case DIV_CMD_FM_AR:
|
||||||
|
case DIV_CMD_FM_DR:
|
||||||
|
case DIV_CMD_FM_SL:
|
||||||
|
case DIV_CMD_FM_D2R:
|
||||||
|
case DIV_CMD_FM_RR:
|
||||||
|
case DIV_CMD_FM_DT:
|
||||||
|
case DIV_CMD_FM_DT2:
|
||||||
|
case DIV_CMD_FM_RS:
|
||||||
|
case DIV_CMD_FM_KSR:
|
||||||
|
case DIV_CMD_FM_VIB:
|
||||||
|
case DIV_CMD_FM_SUS:
|
||||||
|
case DIV_CMD_FM_WS:
|
||||||
|
case DIV_CMD_FM_SSG:
|
||||||
|
case DIV_CMD_FM_REV:
|
||||||
|
case DIV_CMD_FM_EG_SHIFT:
|
||||||
|
case DIV_CMD_FM_MULT:
|
||||||
|
case DIV_CMD_FM_FINE:
|
||||||
|
case DIV_CMD_AY_IO_WRITE:
|
||||||
|
case DIV_CMD_AY_AUTO_PWM:
|
||||||
|
case DIV_CMD_SURROUND_PANNING:
|
||||||
|
arg0=(unsigned char)stream.readC();
|
||||||
|
arg1=(unsigned char)stream.readC();
|
||||||
|
break;
|
||||||
|
case DIV_CMD_C64_FINE_DUTY:
|
||||||
|
case DIV_CMD_C64_FINE_CUTOFF:
|
||||||
|
case DIV_CMD_LYNX_LFSR_LOAD:
|
||||||
|
arg0=(unsigned short)stream.readS();
|
||||||
|
break;
|
||||||
|
case DIV_CMD_FM_FIXFREQ:
|
||||||
|
arg0=(unsigned short)stream.readS();
|
||||||
|
arg1=arg0&0x7ff;
|
||||||
|
arg0>>=12;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_NES_SWEEP:
|
||||||
|
arg0=(unsigned char)stream.readC();
|
||||||
|
arg1=arg0&0x77;
|
||||||
|
arg0=(arg0&8)?1:0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case DIV_CMD_HINT_VOLUME:
|
||||||
|
chan[i].volume=arg0<<8;
|
||||||
|
sendVolume=true;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_HINT_VOL_SLIDE:
|
||||||
|
chan[i].volSpeed=arg0;
|
||||||
|
break;
|
||||||
|
default: // dispatch it
|
||||||
|
e->dispatchCmd(DivCommand((DivDispatchCmds)command,i,arg0,arg1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chan[i].readPos=stream.tell();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendVolume || chan[i].volSpeed!=0) {
|
||||||
|
chan[i].volume+=chan[i].volSpeed;
|
||||||
|
if (chan[i].volume<0) {
|
||||||
|
chan[i].volume=0;
|
||||||
|
}
|
||||||
|
if (chan[i].volume>chan[i].volMax) {
|
||||||
|
chan[i].volume=chan[i].volMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
e->dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ticked;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DivCSPlayer::init() {
|
||||||
|
unsigned char magic[4];
|
||||||
|
stream.seek(0,SEEK_SET);
|
||||||
|
stream.read(magic,4);
|
||||||
|
|
||||||
|
if (memcmp(magic,"FCS",4)!=0) return false;
|
||||||
|
|
||||||
|
unsigned int chans=stream.readI();
|
||||||
|
|
||||||
|
for (unsigned int i=0; i<chans; i++) {
|
||||||
|
if (i>=DIV_MAX_CHANS) {
|
||||||
|
stream.readI();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((int)i>=e->getTotalChannelCount()) {
|
||||||
|
stream.readI();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
chan[i].readPos=stream.readI();
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.read(fastDelays,16);
|
||||||
|
stream.read(fastCmds,16);
|
||||||
|
|
||||||
|
// initialize state
|
||||||
|
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||||
|
chan[i].volMax=(e->getDispatch(e->dispatchOfChan[i])->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,e->dispatchChanOfChan[i]))<<8)|0xff;
|
||||||
|
chan[i].volume=chan[i].volMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DivEngine
|
||||||
|
|
||||||
|
bool DivEngine::playStream(unsigned char* f, size_t length) {
|
||||||
|
BUSY_BEGIN;
|
||||||
|
cmdStreamInt=new DivCSPlayer(this,f,length);
|
||||||
|
if (!cmdStreamInt->init()) {
|
||||||
|
logE("not a command stream!");
|
||||||
|
lastError="not a command stream";
|
||||||
|
delete[] f;
|
||||||
|
delete cmdStreamInt;
|
||||||
|
cmdStreamInt=NULL;
|
||||||
|
BUSY_END;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playing) {
|
||||||
|
reset();
|
||||||
|
freelance=true;
|
||||||
|
playing=true;
|
||||||
|
}
|
||||||
|
BUSY_END;
|
||||||
|
return true;
|
||||||
|
}
|
59
src/engine/cmdStream.h
Normal file
59
src/engine/cmdStream.h
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CMD_STREAM_H
|
||||||
|
#define _CMD_STREAM_H
|
||||||
|
|
||||||
|
#include "defines.h"
|
||||||
|
#include "safeReader.h"
|
||||||
|
|
||||||
|
class DivEngine;
|
||||||
|
|
||||||
|
struct DivCSChannelState {
|
||||||
|
unsigned int readPos;
|
||||||
|
int waitTicks;
|
||||||
|
|
||||||
|
int volume, volMax, volSpeed;
|
||||||
|
|
||||||
|
DivCSChannelState():
|
||||||
|
readPos(0),
|
||||||
|
waitTicks(0),
|
||||||
|
volume(0x7f00),
|
||||||
|
volMax(0),
|
||||||
|
volSpeed(0) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class DivCSPlayer {
|
||||||
|
DivEngine* e;
|
||||||
|
unsigned char* b;
|
||||||
|
SafeReader stream;
|
||||||
|
DivCSChannelState chan[DIV_MAX_CHANS];
|
||||||
|
unsigned char fastDelays[16];
|
||||||
|
unsigned char fastCmds[16];
|
||||||
|
public:
|
||||||
|
void cleanup();
|
||||||
|
bool tick();
|
||||||
|
bool init();
|
||||||
|
DivCSPlayer(DivEngine* en, unsigned char* buf, size_t len):
|
||||||
|
e(en),
|
||||||
|
b(buf),
|
||||||
|
stream(buf,len) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -26,6 +26,7 @@
|
||||||
#include "export.h"
|
#include "export.h"
|
||||||
#include "dataErrors.h"
|
#include "dataErrors.h"
|
||||||
#include "safeWriter.h"
|
#include "safeWriter.h"
|
||||||
|
#include "cmdStream.h"
|
||||||
#include "../audio/taAudio.h"
|
#include "../audio/taAudio.h"
|
||||||
#include "blip_buf.h"
|
#include "blip_buf.h"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
@ -405,6 +406,8 @@ class DivEngine {
|
||||||
static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS];
|
static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS];
|
||||||
static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS];
|
static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS];
|
||||||
|
|
||||||
|
DivCSPlayer* cmdStreamInt;
|
||||||
|
|
||||||
struct SamplePreview {
|
struct SamplePreview {
|
||||||
double rate;
|
double rate;
|
||||||
int sample;
|
int sample;
|
||||||
|
@ -449,7 +452,6 @@ class DivEngine {
|
||||||
// MIDI stuff
|
// MIDI stuff
|
||||||
std::function<int(const TAMidiMessage&)> midiCallback=[](const TAMidiMessage&) -> int {return -2;};
|
std::function<int(const TAMidiMessage&)> midiCallback=[](const TAMidiMessage&) -> int {return -2;};
|
||||||
|
|
||||||
int dispatchCmd(DivCommand c);
|
|
||||||
void processRow(int i, bool afterDelay);
|
void processRow(int i, bool afterDelay);
|
||||||
void nextOrder();
|
void nextOrder();
|
||||||
void nextRow();
|
void nextRow();
|
||||||
|
@ -537,6 +539,8 @@ class DivEngine {
|
||||||
void createNewFromDefaults();
|
void createNewFromDefaults();
|
||||||
// load a file.
|
// load a file.
|
||||||
bool load(unsigned char* f, size_t length);
|
bool load(unsigned char* f, size_t length);
|
||||||
|
// play a binary command stream.
|
||||||
|
bool playStream(unsigned char* f, size_t length);
|
||||||
// save as .dmf.
|
// save as .dmf.
|
||||||
SafeWriter* saveDMF(unsigned char version);
|
SafeWriter* saveDMF(unsigned char version);
|
||||||
// save as .fur.
|
// save as .fur.
|
||||||
|
@ -567,6 +571,9 @@ class DivEngine {
|
||||||
// notify wavetable change
|
// notify wavetable change
|
||||||
void notifyWaveChange(int wave);
|
void notifyWaveChange(int wave);
|
||||||
|
|
||||||
|
// dispatch a command
|
||||||
|
int dispatchCmd(DivCommand c);
|
||||||
|
|
||||||
// get system IDs
|
// get system IDs
|
||||||
static DivSystem systemFromFileFur(unsigned char val);
|
static DivSystem systemFromFileFur(unsigned char val);
|
||||||
static unsigned char systemToFileFur(DivSystem val);
|
static unsigned char systemToFileFur(DivSystem val);
|
||||||
|
@ -1152,6 +1159,7 @@ class DivEngine {
|
||||||
audioEngine(DIV_AUDIO_NULL),
|
audioEngine(DIV_AUDIO_NULL),
|
||||||
exportMode(DIV_EXPORT_MODE_ONE),
|
exportMode(DIV_EXPORT_MODE_ONE),
|
||||||
exportFadeOut(0.0),
|
exportFadeOut(0.0),
|
||||||
|
cmdStreamInt(NULL),
|
||||||
midiBaseChan(0),
|
midiBaseChan(0),
|
||||||
midiPoly(true),
|
midiPoly(true),
|
||||||
midiAgeCounter(0),
|
midiAgeCounter(0),
|
||||||
|
|
|
@ -1385,6 +1385,15 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subticks==tickMult && cmdStreamInt) {
|
||||||
|
if (!cmdStreamInt->tick()) {
|
||||||
|
cmdStreamInt->cleanup();
|
||||||
|
delete cmdStreamInt;
|
||||||
|
cmdStreamInt=NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
firstTick=false;
|
firstTick=false;
|
||||||
|
|
||||||
if (shallStop) {
|
if (shallStop) {
|
||||||
|
|
|
@ -58,6 +58,8 @@ void FurnaceGUI::drawDebug() {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Pattern Advance")) e->haltWhen(DIV_HALT_PATTERN);
|
if (ImGui::Button("Pattern Advance")) e->haltWhen(DIV_HALT_PATTERN);
|
||||||
|
|
||||||
|
if (ImGui::Button("Play Command Stream")) openFileDialog(GUI_FILE_CMDSTREAM_OPEN);
|
||||||
|
|
||||||
if (ImGui::Button("Panic")) e->syncReset();
|
if (ImGui::Button("Panic")) e->syncReset();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Abort")) {
|
if (ImGui::Button("Abort")) {
|
||||||
|
|
|
@ -526,7 +526,21 @@ void FurnaceGUI::drawMobileControls() {
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
drawSongInfo(true);
|
if (ImGui::BeginTabBar("MobileSong")) {
|
||||||
|
if (ImGui::BeginTabItem("Song Info")) {
|
||||||
|
drawSongInfo(true);
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
if (ImGui::BeginTabItem("Subsongs")) {
|
||||||
|
drawSubSongs(true);
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
if (ImGui::BeginTabItem("Speed")) {
|
||||||
|
drawSpeed(true);
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GUI_SCENE_CHANNELS:
|
case GUI_SCENE_CHANNELS:
|
||||||
|
|
|
@ -1849,6 +1849,17 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
||||||
dpiScale
|
dpiScale
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case GUI_FILE_CMDSTREAM_OPEN:
|
||||||
|
if (!dirExists(workingDirROM)) workingDirROM=getHomeDir();
|
||||||
|
hasOpened=fileDialog->openLoad(
|
||||||
|
"Play Command Stream",
|
||||||
|
{"command stream", "*.bin",
|
||||||
|
"all files", "*"},
|
||||||
|
"command stream{.bin},.*",
|
||||||
|
workingDirROM,
|
||||||
|
dpiScale
|
||||||
|
);
|
||||||
|
break;
|
||||||
case GUI_FILE_TEST_OPEN:
|
case GUI_FILE_TEST_OPEN:
|
||||||
if (!dirExists(workingDirTest)) workingDirTest=getHomeDir();
|
if (!dirExists(workingDirTest)) workingDirTest=getHomeDir();
|
||||||
hasOpened=fileDialog->openLoad(
|
hasOpened=fileDialog->openLoad(
|
||||||
|
@ -2103,6 +2114,64 @@ void FurnaceGUI::pushRecentFile(String path) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int FurnaceGUI::loadStream(String path) {
|
||||||
|
if (!path.empty()) {
|
||||||
|
logI("loading stream...");
|
||||||
|
FILE* f=ps_fopen(path.c_str(),"rb");
|
||||||
|
if (f==NULL) {
|
||||||
|
perror("error");
|
||||||
|
lastError=strerror(errno);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (fseek(f,0,SEEK_END)<0) {
|
||||||
|
perror("size error");
|
||||||
|
lastError=fmt::sprintf("on seek: %s",strerror(errno));
|
||||||
|
fclose(f);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
ssize_t len=ftell(f);
|
||||||
|
if (len==(SIZE_MAX>>1)) {
|
||||||
|
perror("could not get file length");
|
||||||
|
lastError=fmt::sprintf("on pre tell: %s",strerror(errno));
|
||||||
|
fclose(f);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (len<1) {
|
||||||
|
if (len==0) {
|
||||||
|
logE("that file is empty!");
|
||||||
|
lastError="file is empty";
|
||||||
|
} else {
|
||||||
|
perror("tell error");
|
||||||
|
lastError=fmt::sprintf("on tell: %s",strerror(errno));
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (fseek(f,0,SEEK_SET)<0) {
|
||||||
|
perror("size error");
|
||||||
|
lastError=fmt::sprintf("on get size: %s",strerror(errno));
|
||||||
|
fclose(f);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
unsigned char* file=new unsigned char[len];
|
||||||
|
if (fread(file,1,(size_t)len,f)!=(size_t)len) {
|
||||||
|
perror("read error");
|
||||||
|
lastError=fmt::sprintf("on read: %s",strerror(errno));
|
||||||
|
fclose(f);
|
||||||
|
delete[] file;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
if (!e->playStream(file,(size_t)len)) {
|
||||||
|
lastError=e->getLastError();
|
||||||
|
logE("could not open file!");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
|
void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
|
||||||
e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut);
|
e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut);
|
||||||
displayExporting=true;
|
displayExporting=true;
|
||||||
|
@ -4241,6 +4310,9 @@ bool FurnaceGUI::loop() {
|
||||||
case GUI_FILE_MU5_ROM_OPEN:
|
case GUI_FILE_MU5_ROM_OPEN:
|
||||||
workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||||
break;
|
break;
|
||||||
|
case GUI_FILE_CMDSTREAM_OPEN:
|
||||||
|
workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||||
|
break;
|
||||||
case GUI_FILE_TEST_OPEN:
|
case GUI_FILE_TEST_OPEN:
|
||||||
case GUI_FILE_TEST_OPEN_MULTI:
|
case GUI_FILE_TEST_OPEN_MULTI:
|
||||||
case GUI_FILE_TEST_SAVE:
|
case GUI_FILE_TEST_SAVE:
|
||||||
|
@ -4706,6 +4778,11 @@ bool FurnaceGUI::loop() {
|
||||||
case GUI_FILE_MU5_ROM_OPEN:
|
case GUI_FILE_MU5_ROM_OPEN:
|
||||||
settings.mu5Path=copyOfName;
|
settings.mu5Path=copyOfName;
|
||||||
break;
|
break;
|
||||||
|
case GUI_FILE_CMDSTREAM_OPEN:
|
||||||
|
if (loadStream(copyOfName)>0) {
|
||||||
|
showError(fmt::sprintf("Error while loading file! (%s)",lastError));
|
||||||
|
}
|
||||||
|
break;
|
||||||
case GUI_FILE_TEST_OPEN:
|
case GUI_FILE_TEST_OPEN:
|
||||||
showWarning(fmt::sprintf("You opened: %s",copyOfName),GUI_WARN_GENERIC);
|
showWarning(fmt::sprintf("You opened: %s",copyOfName),GUI_WARN_GENERIC);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -383,6 +383,7 @@ enum FurnaceGUIFileDialogs {
|
||||||
GUI_FILE_YRW801_ROM_OPEN,
|
GUI_FILE_YRW801_ROM_OPEN,
|
||||||
GUI_FILE_TG100_ROM_OPEN,
|
GUI_FILE_TG100_ROM_OPEN,
|
||||||
GUI_FILE_MU5_ROM_OPEN,
|
GUI_FILE_MU5_ROM_OPEN,
|
||||||
|
GUI_FILE_CMDSTREAM_OPEN,
|
||||||
|
|
||||||
GUI_FILE_TEST_OPEN,
|
GUI_FILE_TEST_OPEN,
|
||||||
GUI_FILE_TEST_OPEN_MULTI,
|
GUI_FILE_TEST_OPEN_MULTI,
|
||||||
|
@ -1982,7 +1983,7 @@ class FurnaceGUI {
|
||||||
void drawNewSong();
|
void drawNewSong();
|
||||||
void drawLog();
|
void drawLog();
|
||||||
void drawEffectList();
|
void drawEffectList();
|
||||||
void drawSubSongs();
|
void drawSubSongs(bool asChild=false);
|
||||||
void drawFindReplace();
|
void drawFindReplace();
|
||||||
void drawSpoiler();
|
void drawSpoiler();
|
||||||
void drawClock();
|
void drawClock();
|
||||||
|
@ -2074,6 +2075,7 @@ class FurnaceGUI {
|
||||||
void openFileDialog(FurnaceGUIFileDialogs type);
|
void openFileDialog(FurnaceGUIFileDialogs type);
|
||||||
int save(String path, int dmfVersion);
|
int save(String path, int dmfVersion);
|
||||||
int load(String path);
|
int load(String path);
|
||||||
|
int loadStream(String path);
|
||||||
void pushRecentFile(String path);
|
void pushRecentFile(String path);
|
||||||
void exportAudio(String path, DivAudioExportModes mode);
|
void exportAudio(String path, DivAudioExportModes mode);
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,18 @@
|
||||||
#include "misc/cpp/imgui_stdlib.h"
|
#include "misc/cpp/imgui_stdlib.h"
|
||||||
#include "intConst.h"
|
#include "intConst.h"
|
||||||
|
|
||||||
void FurnaceGUI::drawSubSongs() {
|
void FurnaceGUI::drawSubSongs(bool asChild) {
|
||||||
if (nextWindow==GUI_WINDOW_SUBSONGS) {
|
if (nextWindow==GUI_WINDOW_SUBSONGS) {
|
||||||
subSongsOpen=true;
|
subSongsOpen=true;
|
||||||
ImGui::SetNextWindowFocus();
|
ImGui::SetNextWindowFocus();
|
||||||
nextWindow=GUI_WINDOW_NOTHING;
|
nextWindow=GUI_WINDOW_NOTHING;
|
||||||
}
|
}
|
||||||
if (!subSongsOpen) return;
|
if (!subSongsOpen && !asChild) return;
|
||||||
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH));
|
if (!asChild) {
|
||||||
if (ImGui::Begin("Subsongs",&subSongsOpen,globalWinFlags)) {
|
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH));
|
||||||
|
}
|
||||||
|
bool began=asChild?ImGui::BeginChild("Subsongs"):ImGui::Begin("Subsongs",&subSongsOpen,globalWinFlags);
|
||||||
|
if (began) {
|
||||||
char id[1024];
|
char id[1024];
|
||||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing()*2.0f-ImGui::GetStyle().ItemSpacing.x);
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing()*2.0f-ImGui::GetStyle().ItemSpacing.x);
|
||||||
if (e->curSubSong->name.empty()) {
|
if (e->curSubSong->name.empty()) {
|
||||||
|
@ -107,12 +110,16 @@ void FurnaceGUI::drawSubSongs() {
|
||||||
MARK_MODIFIED;
|
MARK_MODIFIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::GetContentRegionAvail().y>(10.0f*dpiScale)) {
|
if (!asChild && ImGui::GetContentRegionAvail().y>(10.0f*dpiScale)) {
|
||||||
if (ImGui::InputTextMultiline("##SubSongNotes",&e->curSubSong->notes,ImGui::GetContentRegionAvail(),ImGuiInputTextFlags_UndoRedo)) {
|
if (ImGui::InputTextMultiline("##SubSongNotes",&e->curSubSong->notes,ImGui::GetContentRegionAvail(),ImGuiInputTextFlags_UndoRedo)) {
|
||||||
MARK_MODIFIED;
|
MARK_MODIFIED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS;
|
if (!asChild && ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS;
|
||||||
ImGui::End();
|
if (asChild) {
|
||||||
|
ImGui::EndChild();
|
||||||
|
} else {
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue