Merge branch 'master' of https://github.com/tildearrow/furnace into k053260

This commit is contained in:
cam900 2023-06-17 10:41:56 +09:00
commit 7402575d11
398 changed files with 23113 additions and 12190 deletions

View file

@ -148,9 +148,13 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundanc
}
for (size_t j=0; j<readBufLen; j++) {
if (readBuf[j]==0) {
viable=false;
logW("a zero?");
break;
}
if (readBuf[j]!='\r' && readBuf[j]!='\n' && readBuf[j]!=' ') {
viable=true;
break;
}
}

View file

@ -231,6 +231,11 @@ enum DivDispatchCmds {
DIV_CMD_HINT_ARP_TIME, // (value)
DIV_CMD_SNES_GLOBAL_VOL_LEFT,
DIV_CMD_SNES_GLOBAL_VOL_RIGHT,
DIV_CMD_NES_LINEAR_LENGTH,
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol
DIV_CMD_MAX

189
src/engine/effect.h Normal file
View file

@ -0,0 +1,189 @@
/**
* 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 _EFFECT_H
#define _EFFECT_H
#include <stdlib.h>
#include "../ta-utils.h"
class DivEngine;
class DivEffect {
protected:
DivEngine* parent;
public:
/**
* fill a buffer with sound data.
* @param in pointers to input buffers.
* @param out pointers to output buffers.
* @param len the amount of samples in input and output.
*/
virtual void acquire(float** in, float** out, size_t len);
/**
* reset the state of this effect.
*/
virtual void reset();
/**
* get the number of inputs this effect requests.
* @return number of inputs. SHALL NOT be less than zero.
*/
virtual int getInputCount();
/**
* get the number of outputs this effect provides.
* @return number of outputs. SHALL NOT be less than one.
*/
virtual int getOutputCount();
/**
* called when the sample rate changes.
* @param rate the new sample rate.
*/
virtual void rateChanged(double rate);
/**
* get the value of a parameter.
* @param param parameter ID.
* @return a String with the value.
* @throws std::out_of_range if the parameter ID is out of range.
*/
virtual String getParam(size_t param);
/**
* set the value of a parameter.
* @param param parameter ID.
* @param value the value.
* @return whether the parameter was set successfully.
*/
virtual bool setParam(size_t param, String value);
/**
* get a list of parameters.
* @return a C string with a list of parameters, or NULL if there are none.
* PARAMETER TYPES
*
* Parameter
* id:type:name:description:[...]
* type may be one of the following:
* - s: string
* - i: integer
* - I: integer slider
* - r: integer list (radio button)
* - R: integer list (combo box)
* - h: integer hex
* - f: float
* - F: float slider
* - d: double
* - D: double slider
* - b: boolean (checkbox)
* - t: boolean (toggle button)
* - x: X/Y integer
* - X: X/Y float
* - c: color (RGB)
* - C: color (RGBA)
* - B: button
*
* SameLine
* !s
*
* Separator
* ---
*
* Indent/Unindent
* > Indent
* < Unindent
*
* TreeNode
* >> Begin
* << End
*
* Tabs
* >TABBAR BeginTabBar
* >TAB:name Begin
* <TAB End
* <TABBAR EndTabBar
*
* Text
* TEXT:text (static text)
* TEXTF:id (dynamic text)
*
* NOTES
*
* use a new line to separate parameters.
* use `\:` if you need a colon.
*/
virtual const char* getParams();
/**
* get the number of parameters.
* @return count.
*/
virtual size_t getParamCount();
/**
* get a dynamic text.
* @param id the text ID.
* @return a String with the text.
* @throws std::out_of_range if the text ID is out of range.
*/
virtual String getDynamicText(size_t id);
/**
* load effect data.
* @param version effect data version. may be zero.
* @param data effect data. may be NULL.
* @param len effect data length. may be zero.
* @return whether loading was successful.
*/
virtual bool load(unsigned short version, const unsigned char* data, size_t len);
/**
* save effect data.
* @param version effect data version.
* @param len effect data length.
* @return a pointer to effect data. this must be de-allocated manually.
* may also return NULL if it can't save.
*/
virtual unsigned char* save(unsigned short* version, size_t* len);
/**
* initialize this DivEffect.
* @param parent the parent DivEngine.
* @param version effect data version. may be zero.
* @param data effect data. may be NULL.
* @param len effect data length. may be zero.
* @return whether initialization was successful.
*/
virtual bool init(DivEngine* parent, double rate, unsigned short version, const unsigned char* data, size_t len);
/**
* quit the DivEffect.
*/
virtual void quit();
virtual ~DivEffect();
};
// additional notes:
// - we don't have a GUI API yet, but that will be added when I make the plugin bridge.
#endif

View file

@ -0,0 +1,85 @@
/**
* 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 "../effect.h"
#include "../../ta-log.h"
#include <stdexcept>
void DivEffect::acquire(float** in, float** out, size_t len) {
}
void DivEffect::reset() {
}
int DivEffect::getInputCount() {
return 0;
}
int DivEffect::getOutputCount() {
return 0;
}
void DivEffect::rateChanged(double rate) {
}
String DivEffect::getParam(size_t param) {
throw std::out_of_range("param");
// unreachable
return "";
}
bool DivEffect::setParam(size_t param, String value) {
return false;
}
const char* DivEffect::getParams() {
return NULL;
}
size_t DivEffect::getParamCount() {
return 0;
}
String DivEffect::getDynamicText(size_t id) {
throw std::out_of_range("param");
// unreachable
return "";
}
bool DivEffect::load(unsigned short version, const unsigned char* data, size_t len) {
return false;
}
unsigned char* DivEffect::save(unsigned short* version, size_t* len) {
*len=0;
*version=0;
return NULL;
}
bool DivEffect::init(DivEngine* parent, double rate, unsigned short version, const unsigned char* data, size_t len) {
return false;
}
void DivEffect::quit() {
}
DivEffect::~DivEffect() {
}

View file

@ -0,0 +1,60 @@
/**
* 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 "dummy.h"
void DivEffectDummy::acquire(float** in, float** out, size_t len) {
memcpy(out[0],in[0],len*sizeof(float));
}
void DivEffectDummy::reset() {
}
int DivEffectDummy::getInputCount() {
return 1;
}
int DivEffectDummy::getOutputCount() {
return 1;
}
const char* DivEffectDummy::getParams() {
return NULL;
}
size_t DivEffectDummy::getParamCount() {
return 0;
}
bool DivEffectDummy::load(unsigned short version, const unsigned char* data, size_t len) {
return true;
}
unsigned char* DivEffectDummy::save(unsigned short* version, size_t* len) {
*len=0;
*version=0;
return NULL;
}
bool DivEffectDummy::init(DivEngine* parent, double rate, unsigned short version, const unsigned char* data, size_t len) {
return false;
}
void DivEffectDummy::quit() {
}

34
src/engine/effect/dummy.h Normal file
View file

@ -0,0 +1,34 @@
/**
* 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 "../effect.h"
class DivEffectDummy: public DivEffect {
public:
void acquire(float** in, float** out, size_t len);
void reset();
int getInputCount();
int getOutputCount();
const char* getParams();
size_t getParamCount();
bool load(unsigned short version, const unsigned char* data, size_t len);
unsigned char* save(unsigned short* version, size_t* len);
bool init(DivEngine* parent, double rate, unsigned short version, const unsigned char* data, size_t len);
void quit();
};

View file

@ -0,0 +1,95 @@
/**
* 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 "engine.h"
#include "effect/dummy.h"
void DivEffectContainer::preAcquire(size_t count) {
if (!count) return;
int inCount=effect->getInputCount();
if (inLen<count) {
for (int i=0; i<inCount; i++) {
if (in[i]!=NULL) {
delete[] in[i];
in[i]=new float[count];
}
}
inLen=count;
}
for (int i=0; i<inCount; i++) {
if (in[i]==NULL) {
in[i]=new float[count];
}
}
}
void DivEffectContainer::acquire(size_t count) {
if (!count) return;
int outCount=effect->getOutputCount();
if (outLen<count) {
for (int i=0; i<outCount; i++) {
if (out[i]!=NULL) {
delete[] out[i];
out[i]=new float[count];
}
}
outLen=count;
}
for (int i=0; i<outCount; i++) {
if (out[i]==NULL) {
out[i]=new float[count];
}
}
effect->acquire(in,out,count);
}
bool DivEffectContainer::init(DivEffectType effectType, DivEngine* eng, double rate, unsigned short version, const unsigned char* data, size_t len) {
switch (effectType) {
case DIV_EFFECT_DUMMY:
default:
effect=new DivEffectDummy;
}
return effect->init(eng,rate,version,data,len);
}
void DivEffectContainer::quit() {
effect->quit();
delete effect;
effect=NULL;
for (int i=0; i<DIV_MAX_OUTPUTS; i++) {
if (in[i]) {
delete[] in[i];
in[i]=NULL;
}
if (out[i]) {
delete[] out[i];
out[i]=NULL;
}
}
inLen=0;
outLen=0;
}

View file

@ -1444,9 +1444,6 @@ void DivEngine::initSongWithDesc(const char* description, bool inBase64, bool ol
// extra attributes
song.subsong[0]->hz=c.getDouble("tickRate",60.0);
if (song.subsong[0]->hz!=60.0) {
song.subsong[0]->customTempo=true;
}
}
void DivEngine::createNew(const char* description, String sysName, bool inBase64) {
@ -1572,6 +1569,35 @@ void DivEngine::changeSong(size_t songIndex) {
prevRow=0;
}
void DivEngine::moveAsset(std::vector<DivAssetDir>& dir, int before, int after) {
if (before<0 || after<0) return;
for (DivAssetDir& i: dir) {
for (size_t j=0; j<i.entries.size(); j++) {
// erase matching entry
if (i.entries[j]==before) {
i.entries[j]=after;
} else if (i.entries[j]==after) {
i.entries[j]=before;
}
}
}
}
void DivEngine::removeAsset(std::vector<DivAssetDir>& dir, int entry) {
if (entry<0) return;
for (DivAssetDir& i: dir) {
for (size_t j=0; j<i.entries.size(); j++) {
// erase matching entry
if (i.entries[j]==entry) {
i.entries.erase(i.entries.begin()+j);
j--;
} else if (i.entries[j]>entry) {
i.entries[j]--;
}
}
}
}
void DivEngine::checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries) {
bool* inAssetDir=new bool[entries];
memset(inAssetDir,0,entries*sizeof(bool));
@ -1584,9 +1610,16 @@ void DivEngine::checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries) {
j--;
continue;
}
// erase duplicate entry
if (inAssetDir[i.entries[j]]) {
i.entries.erase(i.entries.begin()+j);
j--;
continue;
}
// mark entry as present
inAssetDir[j]=true;
inAssetDir[i.entries[j]]=true;
}
}
@ -1599,15 +1632,14 @@ void DivEngine::checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries) {
}
}
// create unsorted directory if it doesn't exist
if (unsortedDir==NULL) {
dir.push_back(DivAssetDir(""));
unsortedDir=&(*dir.rbegin());
}
// add missing items to unsorted directory
for (size_t i=0; i<entries; i++) {
if (!inAssetDir[i]) {
// create unsorted directory if it doesn't exist
if (unsortedDir==NULL) {
dir.push_back(DivAssetDir(""));
unsortedDir=&(*dir.rbegin());
}
unsortedDir->entries.push_back(i);
}
}
@ -2034,11 +2066,17 @@ String DivEngine::getPlaybackDebugInfo() {
"divider: %f\n"
"cycles: %d\n"
"clockDrift: %f\n"
"midiClockCycles: %d\n"
"midiClockDrift: %f\n"
"midiTimeCycles: %d\n"
"midiTimeDrift: %f\n"
"changeOrd: %d\n"
"changePos: %d\n"
"totalSeconds: %d\n"
"totalTicks: %d\n"
"totalTicksR: %d\n"
"curMidiClock: %d\n"
"curMidiTime: %d\n"
"totalCmds: %d\n"
"lastCmds: %d\n"
"cmdsPerSecond: %d\n"
@ -2048,7 +2086,8 @@ String DivEngine::getPlaybackDebugInfo() {
"totalProcessed: %d\n"
"bufferPos: %d\n",
curOrder,prevOrder,curRow,prevRow,ticks,subticks,totalLoops,lastLoopPos,nextSpeed,divider,cycles,clockDrift,
changeOrd,changePos,totalSeconds,totalTicks,totalTicksR,totalCmds,lastCmds,cmdsPerSecond,globalPitch,
midiClockCycles,midiClockDrift,midiTimeCycles,midiTimeDrift,changeOrd,changePos,totalSeconds,totalTicks,
totalTicksR,curMidiClock,curMidiTime,totalCmds,lastCmds,cmdsPerSecond,globalPitch,
(int)extValue,(int)tempoAccum,(int)totalProcessed,(int)bufferPos
);
}
@ -2165,16 +2204,27 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
prevOrder=0;
prevRow=0;
stepPlay=0;
int prevDrift;
if (curSubSong!=NULL) curSubSong->arpLen=1;
int prevDrift, prevMidiClockDrift, prevMidiTimeDrift;
prevDrift=clockDrift;
prevMidiClockDrift=midiClockDrift;
prevMidiTimeDrift=midiTimeDrift;
clockDrift=0;
cycles=0;
midiClockCycles=0;
midiClockDrift=0;
midiTimeCycles=0;
midiTimeDrift=0;
if (!preserveDrift) {
ticks=1;
tempoAccum=0;
totalTicks=0;
totalSeconds=0;
totalTicksR=0;
curMidiClock=0;
curMidiTime=0;
curMidiTimeCode=0;
curMidiTimePiece=0;
totalLoops=0;
lastLoopPos=-1;
}
@ -2191,6 +2241,10 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
skipping=false;
return;
}
if (!preserveDrift) {
runMidiClock(cycles);
runMidiTime(cycles);
}
}
int oldOrder=curOrder;
while (playing && (curRow<goalRow || ticks>1)) {
@ -2198,6 +2252,10 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
skipping=false;
return;
}
if (!preserveDrift) {
runMidiClock(cycles);
runMidiTime(cycles);
}
if (oldOrder!=curOrder) break;
if (ticks-((tempoAccum+curSubSong->virtualTempoN)/MAX(1,curSubSong->virtualTempoD))<1 && curRow>=goalRow) break;
}
@ -2211,9 +2269,22 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
repeatPattern=oldRepeatPattern;
if (preserveDrift) {
clockDrift=prevDrift;
midiClockDrift=prevMidiClockDrift;
midiTimeDrift=prevMidiTimeDrift;
} else {
clockDrift=0;
cycles=0;
midiClockCycles=0;
midiClockDrift=0;
midiTimeCycles=0;
midiTimeDrift=0;
if (curMidiTime>0) {
curMidiTime--;
}
if (curMidiClock>0) {
curMidiClock--;
}
curMidiTimePiece=0;
}
if (!preserveDrift) {
ticks=1;
@ -2382,9 +2453,74 @@ void DivEngine::play() {
for (int i=0; i<DIV_MAX_CHANS; i++) {
keyHit[i]=false;
}
curMidiTimePiece=0;
if (output) if (!skipping && output->midiOut!=NULL) {
int pos=totalTicksR/6;
output->midiOut->send(TAMidiMessage(TA_MIDI_POSITION,(pos>>7)&0x7f,pos&0x7f));
if (midiOutClock) {
output->midiOut->send(TAMidiMessage(TA_MIDI_POSITION,(curMidiClock>>7)&0x7f,curMidiClock&0x7f));
}
if (midiOutTime) {
TAMidiMessage msg;
msg.type=TA_MIDI_SYSEX;
msg.sysExData.reset(new unsigned char[10],std::default_delete<unsigned char[]>());
msg.sysExLen=10;
unsigned char* msgData=msg.sysExData.get();
int actualTime=curMidiTime;
int timeRate=midiOutTimeRate;
int drop=0;
if (timeRate<1 || timeRate>4) {
if (curSubSong->hz>=47.98 && curSubSong->hz<=48.02) {
timeRate=1;
} else if (curSubSong->hz>=49.98 && curSubSong->hz<=50.02) {
timeRate=2;
} else if (curSubSong->hz>=59.9 && curSubSong->hz<=60.11) {
timeRate=4;
} else {
timeRate=4;
}
}
switch (timeRate) {
case 1: // 24
msgData[5]=(actualTime/(60*60*24))%24;
msgData[6]=(actualTime/(60*24))%60;
msgData[7]=(actualTime/24)%60;
msgData[8]=actualTime%24;
break;
case 2: // 25
msgData[5]=(actualTime/(60*60*25))%24;
msgData[6]=(actualTime/(60*25))%60;
msgData[7]=(actualTime/25)%60;
msgData[8]=actualTime%25;
break;
case 3: // 29.97 (NTSC drop)
// drop
drop=((actualTime/(30*60))-(actualTime/(30*600)))*2;
actualTime+=drop;
msgData[5]=(actualTime/(60*60*30))%24;
msgData[6]=(actualTime/(60*30))%60;
msgData[7]=(actualTime/30)%60;
msgData[8]=actualTime%30;
break;
case 4: // 30 (NTSC non-drop)
default:
msgData[5]=(actualTime/(60*60*30))%24;
msgData[6]=(actualTime/(60*30))%60;
msgData[7]=(actualTime/30)%60;
msgData[8]=actualTime%30;
break;
}
msgData[5]|=(timeRate-1)<<5;
msgData[0]=0xf0;
msgData[1]=0x7f;
msgData[2]=0x7f;
msgData[3]=0x01;
msgData[4]=0x01;
msgData[9]=0xf7;
output->midiOut->send(msg);
}
output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_PLAY,0,0));
}
BUSY_END;
@ -2557,16 +2693,7 @@ void DivEngine::reset() {
elapsedBars=0;
elapsedBeats=0;
nextSpeed=speeds.val[0];
divider=60;
if (curSubSong->customTempo) {
divider=curSubSong->hz;
} else {
if (curSubSong->pal) {
divider=60;
} else {
divider=50;
}
}
divider=curSubSong->hz;
globalPitch=0;
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->reset();
@ -2781,14 +2908,7 @@ const DivGroovePattern& DivEngine::getSpeeds() {
}
float DivEngine::getHz() {
if (curSubSong->customTempo) {
return curSubSong->hz;
} else if (curSubSong->pal) {
return 60.0;
} else {
return 50.0;
}
return 60.0;
return curSubSong->hz;
}
float DivEngine::getCurHz() {
@ -2929,6 +3049,7 @@ int DivEngine::addInstrument(int refChan, DivInstrumentType fallbackType) {
saveLock.lock();
song.ins.push_back(ins);
song.insLen=insCount+1;
checkAssetDir(song.insDir,song.ins.size());
saveLock.unlock();
BUSY_END;
return insCount;
@ -2943,6 +3064,7 @@ int DivEngine::addInstrumentPtr(DivInstrument* which) {
saveLock.lock();
song.ins.push_back(which);
song.insLen=song.ins.size();
checkAssetDir(song.insDir,song.ins.size());
saveLock.unlock();
BUSY_END;
return song.insLen;
@ -2979,6 +3101,8 @@ void DivEngine::delInstrument(int index) {
}
}
}
removeAsset(song.insDir,index);
checkAssetDir(song.insDir,song.ins.size());
}
saveLock.unlock();
BUSY_END;
@ -2995,6 +3119,7 @@ int DivEngine::addWave() {
int waveCount=(int)song.wave.size();
song.wave.push_back(wave);
song.waveLen=waveCount+1;
checkAssetDir(song.waveDir,song.wave.size());
saveLock.unlock();
BUSY_END;
return waveCount;
@ -3011,6 +3136,7 @@ int DivEngine::addWavePtr(DivWavetable* which) {
int waveCount=(int)song.wave.size();
song.wave.push_back(which);
song.waveLen=waveCount+1;
checkAssetDir(song.waveDir,song.wave.size());
saveLock.unlock();
BUSY_END;
return song.waveLen;
@ -3090,15 +3216,17 @@ DivWavetable* DivEngine::waveFromFile(const char* path, bool addRaw) {
// read as .dmw
reader.seek(0,SEEK_SET);
int len=reader.readI();
logD("wave length %d",len);
if (len<=0 || len>256) {
throw EndOfFileException(&reader,reader.size());
}
wave->len=len;
wave->max=(unsigned char)reader.readC();
if (wave->max==255) { // new wavetable format
unsigned char waveVersion=reader.readC();
logI("reading modern .dmw...");
logD("wave version %d",waveVersion);
wave->max=reader.readC();
wave->max=(unsigned char)reader.readC();
for (int i=0; i<len; i++) {
wave->data[i]=reader.readI();
}
@ -3163,6 +3291,8 @@ void DivEngine::delWave(int index) {
delete song.wave[index];
song.wave.erase(song.wave.begin()+index);
song.waveLen=song.wave.size();
removeAsset(song.waveDir,index);
checkAssetDir(song.waveDir,song.wave.size());
}
saveLock.unlock();
BUSY_END;
@ -3183,6 +3313,7 @@ int DivEngine::addSample() {
sPreview.sample=-1;
sPreview.pos=0;
sPreview.dir=false;
checkAssetDir(song.sampleDir,song.sample.size());
saveLock.unlock();
renderSamples();
BUSY_END;
@ -3200,6 +3331,7 @@ int DivEngine::addSamplePtr(DivSample* which) {
saveLock.lock();
song.sample.push_back(which);
song.sampleLen=sampleCount+1;
checkAssetDir(song.sampleDir,song.sample.size());
saveLock.unlock();
renderSamples();
BUSY_END;
@ -3669,6 +3801,8 @@ void DivEngine::delSample(int index) {
delete song.sample[index];
song.sample.erase(song.sample.begin()+index);
song.sampleLen=song.sample.size();
removeAsset(song.sampleDir,index);
checkAssetDir(song.sampleDir,song.sample.size());
renderSamples();
}
saveLock.unlock();
@ -3867,6 +4001,7 @@ bool DivEngine::moveInsUp(int which) {
saveLock.lock();
song.ins[which]=song.ins[which-1];
song.ins[which-1]=prev;
moveAsset(song.insDir,which,which-1);
exchangeIns(which,which-1);
saveLock.unlock();
BUSY_END;
@ -3880,6 +4015,7 @@ bool DivEngine::moveWaveUp(int which) {
saveLock.lock();
song.wave[which]=song.wave[which-1];
song.wave[which-1]=prev;
moveAsset(song.waveDir,which,which-1);
saveLock.unlock();
BUSY_END;
return true;
@ -3895,6 +4031,7 @@ bool DivEngine::moveSampleUp(int which) {
saveLock.lock();
song.sample[which]=song.sample[which-1];
song.sample[which-1]=prev;
moveAsset(song.sampleDir,which,which-1);
saveLock.unlock();
renderSamples();
BUSY_END;
@ -3909,6 +4046,7 @@ bool DivEngine::moveInsDown(int which) {
song.ins[which]=song.ins[which+1];
song.ins[which+1]=prev;
exchangeIns(which,which+1);
moveAsset(song.insDir,which,which+1);
saveLock.unlock();
BUSY_END;
return true;
@ -3921,6 +4059,7 @@ bool DivEngine::moveWaveDown(int which) {
saveLock.lock();
song.wave[which]=song.wave[which+1];
song.wave[which+1]=prev;
moveAsset(song.waveDir,which,which+1);
saveLock.unlock();
BUSY_END;
return true;
@ -3936,6 +4075,7 @@ bool DivEngine::moveSampleDown(int which) {
saveLock.lock();
song.sample[which]=song.sample[which+1];
song.sample[which+1]=prev;
moveAsset(song.sampleDir,which,which+1);
saveLock.unlock();
renderSamples();
BUSY_END;
@ -3980,6 +4120,10 @@ void DivEngine::autoPatchbayP() {
BUSY_END;
}
void DivEngine::recalcPatchbay() {
}
bool DivEngine::patchConnect(unsigned int src, unsigned int dest) {
unsigned int armed=(src<<16)|(dest&0xffff);
for (unsigned int i: song.patchbay) {
@ -4191,23 +4335,11 @@ void DivEngine::updateSysFlags(int system, bool restart) {
BUSY_END;
}
void DivEngine::setSongRate(float hz, bool pal) {
void DivEngine::setSongRate(float hz) {
BUSY_BEGIN;
saveLock.lock();
curSubSong->pal=!pal;
curSubSong->hz=hz;
// what?
curSubSong->customTempo=true;
divider=60;
if (curSubSong->customTempo) {
divider=curSubSong->hz;
} else {
if (curSubSong->pal) {
divider=60;
} else {
divider=50;
}
}
divider=curSubSong->hz;
saveLock.unlock();
BUSY_END;
}
@ -4365,6 +4497,10 @@ void DivEngine::quitDispatch() {
}
cycles=0;
clockDrift=0;
midiClockCycles=0;
midiClockDrift=0;
midiTimeCycles=0;
midiTimeDrift=0;
chans=0;
playing=false;
curSpeed=0;
@ -4381,6 +4517,10 @@ void DivEngine::quitDispatch() {
totalTicks=0;
totalSeconds=0;
totalTicksR=0;
curMidiClock=0;
curMidiTime=0;
curMidiTimeCode=0;
curMidiTimePiece=0;
totalCmds=0;
lastCmds=0;
cmdsPerSecond=0;
@ -4407,6 +4547,8 @@ bool DivEngine::initAudioBackend() {
lowLatency=getConfInt("lowLatency",0);
metroVol=(float)(getConfInt("metroVol",100))/100.0f;
midiOutClock=getConfInt("midiOutClock",0);
midiOutTime=getConfInt("midiOutTime",0);
midiOutTimeRate=getConfInt("midiOutTimeRate",0);
midiOutProgramChange = getConfInt("midiOutProgramChange",0);
midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE);
if (metroVol<0.0f) metroVol=0.0f;

View file

@ -23,6 +23,7 @@
#include "instrument.h"
#include "song.h"
#include "dispatch.h"
#include "effect.h"
#include "export.h"
#include "dataErrors.h"
#include "safeWriter.h"
@ -53,8 +54,8 @@
#define EXTERN_BUSY_BEGIN_SOFT e->softLocked=true; e->isBusy.lock();
#define EXTERN_BUSY_END e->isBusy.unlock(); e->softLocked=false;
#define DIV_VERSION "dev153"
#define DIV_ENGINE_VERSION 153
#define DIV_VERSION "dev159"
#define DIV_ENGINE_VERSION 159
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -222,6 +223,25 @@ struct DivDispatchContainer {
}
};
struct DivEffectContainer {
DivEffect* effect;
float* in[DIV_MAX_OUTPUTS];
float* out[DIV_MAX_OUTPUTS];
size_t inLen, outLen;
void preAcquire(size_t count);
void acquire(size_t count);
bool init(DivEffectType effectType, DivEngine* eng, double rate, unsigned short version, const unsigned char* data, size_t len);
void quit();
DivEffectContainer():
effect(NULL),
inLen(0),
outLen(0) {
memset(in,0,DIV_MAX_OUTPUTS*sizeof(float*));
memset(out,0,DIV_MAX_OUTPUTS*sizeof(float*));
}
};
typedef int EffectValConversion(unsigned char,unsigned char);
struct EffectHandler {
@ -369,8 +389,10 @@ class DivEngine {
bool systemsRegistered;
bool hasLoadedSomething;
bool midiOutClock;
bool midiOutTime;
bool midiOutProgramChange;
int midiOutMode;
int midiOutTimeRate;
int softLockCount;
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats, curSpeed;
size_t curSubSongIndex;
@ -378,8 +400,13 @@ class DivEngine {
double divider;
int cycles;
double clockDrift;
int midiClockCycles;
double midiClockDrift;
int midiTimeCycles;
double midiTimeDrift;
int stepPlay;
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, curMidiClock, curMidiTime, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
int curMidiTimePiece, curMidiTimeCode;
unsigned char extValue, pendingMetroTick;
DivGroovePattern speeds;
short tempoAccum;
@ -404,6 +431,7 @@ class DivEngine {
std::vector<String> midiOuts;
std::vector<DivCommand> cmdStream;
std::vector<DivInstrumentType> possibleInsTypes;
std::vector<DivEffectContainer> effectInst;
static DivSysDef* sysDefs[DIV_MAX_CHIP_DEFS];
static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS];
static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS];
@ -434,6 +462,7 @@ class DivEngine {
short tremTable[128];
int reversePitchTable[4096];
int pitchTable[4096];
short effectSlotMap[4096];
char c163NameCS[1024];
int midiBaseChan;
bool midiPoly;
@ -468,6 +497,8 @@ class DivEngine {
void recalcChans();
void reset();
void playSub(bool preserveDrift, int goalRow=0);
void runMidiClock(int totalCycles=1);
void runMidiTime(int totalCycles=1);
void testFunction();
@ -505,11 +536,21 @@ class DivEngine {
void swapChannels(int src, int dest);
void stompChannel(int ch);
// recalculate patchbay (UNSAFE)
void recalcPatchbay();
// change song (UNSAFE)
void changeSong(size_t songIndex);
// check whether an asset directory is complete
void checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries);
// move an asset
void moveAsset(std::vector<DivAssetDir>& dir, int before, int after);
// remove an asset
void removeAsset(std::vector<DivAssetDir>& dir, int entry);
// read/write asset dir
void putAssetDirData(SafeWriter* w, std::vector<DivAssetDir>& dir);
DivDataErrors readAssetDirData(SafeReader& reader, std::vector<DivAssetDir>& dir);
// add every export method here
friend class DivROMExport;
@ -550,7 +591,7 @@ class DivEngine {
SafeWriter* saveDMF(unsigned char version);
// save as .fur.
// if notPrimary is true then the song will not be altered
SafeWriter* saveFur(bool notPrimary=false);
SafeWriter* saveFur(bool notPrimary=false, bool newPatternFormat=true);
// build a ROM file (TODO).
// specify system to build ROM for.
std::vector<DivROMExportOutput> buildROM(DivROMExportOptions sys);
@ -588,6 +629,8 @@ class DivEngine {
// convert old flags
static void convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivSystem sys);
// check whether an asset directory is complete (UNSAFE)
void checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries);
// benchmark (returns time in seconds)
double benchmarkPlayback();
@ -911,7 +954,7 @@ class DivEngine {
void updateSysFlags(int system, bool restart);
// set Hz
void setSongRate(float hz, bool pal);
void setSongRate(float hz);
// set remaining loops. -1 means loop forever.
void setLoops(int loops);
@ -1031,6 +1074,12 @@ class DivEngine {
// move system
bool swapSystem(int src, int dest, bool preserveOrder=true);
// add effect
bool addEffect(DivEffectType which);
// remove effect
bool removeEffect(int index);
// write to register on system
void poke(int sys, unsigned int addr, unsigned short val);
@ -1124,8 +1173,10 @@ class DivEngine {
systemsRegistered(false),
hasLoadedSomething(false),
midiOutClock(false),
midiOutTime(false),
midiOutProgramChange(false),
midiOutMode(DIV_MIDI_MODE_NOTE),
midiOutTimeRate(0),
softLockCount(0),
subticks(0),
ticks(0),
@ -1146,16 +1197,24 @@ class DivEngine {
divider(60),
cycles(0),
clockDrift(0),
midiClockCycles(0),
midiClockDrift(0),
midiTimeCycles(0),
midiTimeDrift(0),
stepPlay(0),
changeOrd(-1),
changePos(0),
totalSeconds(0),
totalTicks(0),
totalTicksR(0),
curMidiClock(0),
curMidiTime(0),
totalCmds(0),
lastCmds(0),
cmdsPerSecond(0),
globalPitch(0),
curMidiTimePiece(0),
curMidiTimeCode(0),
extValue(0),
pendingMetroTick(0),
tempoAccum(0),
@ -1203,6 +1262,7 @@ class DivEngine {
memset(tremTable,0,128*sizeof(short));
memset(reversePitchTable,0,4096*sizeof(int));
memset(pitchTable,0,4096*sizeof(int));
memset(effectSlotMap,-1,4096*sizeof(short));
memset(sysDefs,0,DIV_MAX_CHIP_DEFS*sizeof(void*));
memset(walked,0,8192);
memset(oscBuf,0,DIV_MAX_OUTPUTS*(sizeof(float*)));

View file

@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "dataErrors.h"
#include "engine.h"
#include "../ta-log.h"
#include "instrument.h"
@ -219,20 +220,22 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.subsong[0]->hilightB=reader.readC();
}
bool customTempo=false;
ds.subsong[0]->timeBase=reader.readC();
ds.subsong[0]->speeds.len=2;
ds.subsong[0]->speeds.val[0]=reader.readC();
if (ds.version>0x07) {
ds.subsong[0]->speeds.val[1]=reader.readC();
ds.subsong[0]->pal=reader.readC();
ds.subsong[0]->hz=(ds.subsong[0]->pal)?60:50;
ds.subsong[0]->customTempo=reader.readC();
bool pal=reader.readC();
ds.subsong[0]->hz=pal?60:50;
customTempo=reader.readC();
} else {
ds.subsong[0]->speeds.len=1;
}
if (ds.version>0x0a) {
String hz=reader.readString(3);
if (ds.subsong[0]->customTempo) {
if (customTempo) {
try {
ds.subsong[0]->hz=std::stoi(hz);
} catch (std::exception& e) {
@ -303,7 +306,6 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.subsong[0]->hz=248;
break;
}
ds.subsong[0]->customTempo=true;
ds.subsong[0]->timeBase=0;
addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system.");
}
@ -1040,6 +1042,11 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.systemFlags[0].set("noEasyNoise",true);
}
// NES PCM
if (ds.system[0]==DIV_SYSTEM_NES) {
ds.systemFlags[0].set("dpcmMode",false);
}
ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0));
if (active) quitDispatch();
@ -1644,18 +1651,58 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS
}
}
short newFormatNotes[180]={
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -5
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -4
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -3
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -2
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -1
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 0
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 1
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 2
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 3
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 4
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 5
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 6
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 7
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 8
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 // 9
};
short newFormatOctaves[180]={
250, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, // -5
251, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, // -4
252, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, // -3
253, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, // -2
254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // -1
255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 2
2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 3
3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 4
4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 5
5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, // 6
6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 7
7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 8
8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 9
};
bool DivEngine::loadFur(unsigned char* file, size_t len) {
unsigned int insPtr[256];
unsigned int wavePtr[256];
unsigned int samplePtr[256];
unsigned int subSongPtr[256];
unsigned int sysFlagsPtr[DIV_MAX_CHIPS];
std::vector<int> patPtr;
unsigned int assetDirPtr[3];
std::vector<unsigned int> patPtr;
int numberOfSubSongs=0;
char magic[5];
memset(magic,0,5);
SafeReader reader=SafeReader(file,len);
warnings="";
assetDirPtr[0]=0;
assetDirPtr[1]=0;
assetDirPtr[2]=0;
try {
DivSong ds;
DivSubSong* subSong=ds.subsong[0];
@ -1787,6 +1834,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<138) {
ds.brokenPortaLegato=true;
}
if (ds.version<155) {
ds.brokenFMOff=true;
}
ds.isDMF=false;
reader.readS(); // reserved
@ -1815,8 +1865,6 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
subSong->speeds.val[1]=reader.readC();
subSong->arpLen=reader.readC();
subSong->hz=reader.readF();
subSong->pal=(subSong->hz>=53);
subSong->customTempo=true;
subSong->patLen=reader.readS();
subSong->ordersLen=reader.readS();
@ -2295,7 +2343,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version>=138) {
ds.brokenPortaLegato=reader.readC();
for (int i=0; i<7; i++) {
if (ds.version>=155) {
ds.brokenFMOff=reader.readC();
} else {
reader.readC();
}
for (int i=0; i<6; i++) {
reader.readC();
}
}
@ -2319,6 +2372,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
}
}
if (ds.version>=156) {
assetDirPtr[0]=reader.readI();
assetDirPtr[1]=reader.readI();
assetDirPtr[2]=reader.readI();
}
// read system flags
if (ds.version>=119) {
logD("reading chip flags...");
@ -2353,6 +2412,53 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
}
}
// read asset directories
if (ds.version>=156) {
logD("reading asset directories...");
if (!reader.seek(assetDirPtr[0],SEEK_SET)) {
logE("couldn't seek to ins dir!");
lastError=fmt::sprintf("couldn't read instrument directory");
ds.unload();
delete[] file;
return false;
}
if (readAssetDirData(reader,ds.insDir)!=DIV_DATA_SUCCESS) {
lastError="invalid instrument directory data!";
ds.unload();
delete[] file;
return false;
}
if (!reader.seek(assetDirPtr[1],SEEK_SET)) {
logE("couldn't seek to wave dir!");
lastError=fmt::sprintf("couldn't read wavetable directory");
ds.unload();
delete[] file;
return false;
}
if (readAssetDirData(reader,ds.waveDir)!=DIV_DATA_SUCCESS) {
lastError="invalid wavetable directory data!";
ds.unload();
delete[] file;
return false;
}
if (!reader.seek(assetDirPtr[2],SEEK_SET)) {
logE("couldn't seek to sample dir!");
lastError=fmt::sprintf("couldn't read sample directory");
ds.unload();
delete[] file;
return false;
}
if (readAssetDirData(reader,ds.sampleDir)!=DIV_DATA_SUCCESS) {
lastError="invalid sample directory data!";
ds.unload();
delete[] file;
return false;
}
}
// read subsongs
if (ds.version>=95) {
for (int i=0; i<numberOfSubSongs; i++) {
@ -2382,8 +2488,6 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
subSong->speeds.val[1]=reader.readC();
subSong->arpLen=reader.readC();
subSong->hz=reader.readF();
subSong->pal=(subSong->hz>=53);
subSong->customTempo=true;
subSong->patLen=reader.readS();
subSong->ordersLen=reader.readS();
@ -2510,7 +2614,8 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
}
// read patterns
for (int i: patPtr) {
for (unsigned int i: patPtr) {
bool isNewFormat=false;
if (!reader.seek(i,SEEK_SET)) {
logE("couldn't seek to pattern in %x!",i);
lastError=fmt::sprintf("couldn't seek to pattern in %x!",i);
@ -2521,62 +2626,151 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
reader.read(magic,4);
logD("reading pattern in %x...",i);
if (strcmp(magic,"PATR")!=0) {
logE("%x: invalid pattern header!",i);
lastError="invalid pattern header!";
ds.unload();
delete[] file;
return false;
if (strcmp(magic,"PATN")!=0 || ds.version<157) {
logE("%x: invalid pattern header!",i);
lastError="invalid pattern header!";
ds.unload();
delete[] file;
return false;
} else {
isNewFormat=true;
}
}
reader.readI();
int chan=reader.readS();
int index=reader.readS();
int subs=0;
if (ds.version>=95) {
subs=reader.readS();
} else {
reader.readS();
}
reader.readS();
if (isNewFormat) {
int subs=(unsigned char)reader.readC();
int chan=(unsigned char)reader.readC();
int index=reader.readS();
logD("- %d, %d, %d",subs,chan,index);
logD("- %d, %d, %d (new)",subs,chan,index);
if (chan<0 || chan>=tchans) {
logE("pattern channel out of range!",i);
lastError="pattern channel out of range!";
ds.unload();
delete[] file;
return false;
}
if (index<0 || index>(DIV_MAX_PATTERNS-1)) {
logE("pattern index out of range!",i);
lastError="pattern index out of range!";
ds.unload();
delete[] file;
return false;
}
if (subs<0 || subs>=(int)ds.subsong.size()) {
logE("pattern subsong out of range!",i);
lastError="pattern subsong out of range!";
ds.unload();
delete[] file;
return false;
}
DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true);
for (int j=0; j<ds.subsong[subs]->patLen; j++) {
pat->data[j][0]=reader.readS();
pat->data[j][1]=reader.readS();
pat->data[j][2]=reader.readS();
pat->data[j][3]=reader.readS();
for (int k=0; k<ds.subsong[subs]->pat[chan].effectCols; k++) {
pat->data[j][4+(k<<1)]=reader.readS();
pat->data[j][5+(k<<1)]=reader.readS();
if (chan<0 || chan>=tchans) {
logE("pattern channel out of range!",i);
lastError="pattern channel out of range!";
ds.unload();
delete[] file;
return false;
}
if (index<0 || index>(DIV_MAX_PATTERNS-1)) {
logE("pattern index out of range!",i);
lastError="pattern index out of range!";
ds.unload();
delete[] file;
return false;
}
if (subs<0 || subs>=(int)ds.subsong.size()) {
logE("pattern subsong out of range!",i);
lastError="pattern subsong out of range!";
ds.unload();
delete[] file;
return false;
}
}
if (ds.version>=51) {
DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true);
pat->name=reader.readString();
// read new pattern
for (int j=0; j<ds.subsong[subs]->patLen; j++) {
unsigned char mask=reader.readC();
unsigned short effectMask=0;
if (mask==0xff) break;
if (mask&128) {
j+=(mask&127)+1;
continue;
}
if (mask&32) {
effectMask|=(unsigned char)reader.readC();
}
if (mask&64) {
effectMask|=((unsigned short)reader.readC()&0xff)<<8;
}
if (mask&8) effectMask|=1;
if (mask&16) effectMask|=2;
if (mask&1) { // note
unsigned char note=reader.readC();
if (note==180) {
pat->data[j][0]=100;
pat->data[j][1]=0;
} else if (note==181) {
pat->data[j][0]=101;
pat->data[j][1]=0;
} else if (note==182) {
pat->data[j][0]=102;
pat->data[j][1]=0;
} else if (note<180) {
pat->data[j][0]=newFormatNotes[note];
pat->data[j][1]=newFormatOctaves[note];
} else {
pat->data[j][0]=0;
pat->data[j][1]=0;
}
}
if (mask&2) { // instrument
pat->data[j][2]=(unsigned char)reader.readC();
}
if (mask&4) { // volume
pat->data[j][3]=(unsigned char)reader.readC();
}
for (unsigned char k=0; k<16; k++) {
if (effectMask&(1<<k)) {
pat->data[j][4+k]=(unsigned char)reader.readC();
}
}
}
} else {
int chan=reader.readS();
int index=reader.readS();
int subs=0;
if (ds.version>=95) {
subs=reader.readS();
} else {
reader.readS();
}
reader.readS();
logD("- %d, %d, %d (old)",subs,chan,index);
if (chan<0 || chan>=tchans) {
logE("pattern channel out of range!",i);
lastError="pattern channel out of range!";
ds.unload();
delete[] file;
return false;
}
if (index<0 || index>(DIV_MAX_PATTERNS-1)) {
logE("pattern index out of range!",i);
lastError="pattern index out of range!";
ds.unload();
delete[] file;
return false;
}
if (subs<0 || subs>=(int)ds.subsong.size()) {
logE("pattern subsong out of range!",i);
lastError="pattern subsong out of range!";
ds.unload();
delete[] file;
return false;
}
DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true);
for (int j=0; j<ds.subsong[subs]->patLen; j++) {
pat->data[j][0]=reader.readS();
pat->data[j][1]=reader.readS();
pat->data[j][2]=reader.readS();
pat->data[j][3]=reader.readS();
for (int k=0; k<ds.subsong[subs]->pat[chan].effectCols; k++) {
pat->data[j][4+(k<<1)]=reader.readS();
pat->data[j][5+(k<<1)]=reader.readS();
}
}
if (ds.version>=51) {
pat->name=reader.readString();
}
}
}
@ -2724,6 +2918,15 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
}
}
// NES PCM compat
if (ds.version<154) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_NES) {
ds.systemFlags[i].set("dpcmMode",false);
}
}
}
if (active) quitDispatch();
BUSY_BEGIN_SOFT;
saveLock.lock();
@ -3116,9 +3319,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
ds.subsong[0]->pat[ch].effectCols=fxCols;
}
ds.subsong[0]->pal=false;
ds.subsong[0]->hz=50;
ds.subsong[0]->customTempo=false;
ds.systemLen=(chCount+3)/4;
for(int i=0; i<ds.systemLen; i++) {
ds.system[i]=DIV_SYSTEM_AMIGA;
@ -3341,7 +3542,6 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
ds.subsong[0]->speeds.val[0]=(unsigned char)reader.readC();
ds.subsong[0]->hz=((double)reader.readC())/2.5;
ds.subsong[0]->customTempo=true;
unsigned char masterVol=reader.readC();
@ -3787,8 +3987,6 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
ds.subsong[0]->ordersLen=seqLen;
ds.subsong[0]->patLen=32;
ds.subsong[0]->hz=50;
ds.subsong[0]->pal=true;
ds.subsong[0]->customTempo=true;
ds.subsong[0]->pat[3].effectCols=3;
ds.subsong[0]->speeds.val[0]=3;
ds.subsong[0]->speeds.len=1;
@ -4821,7 +5019,57 @@ struct PatToWrite {
pat(p) {}
};
SafeWriter* DivEngine::saveFur(bool notPrimary) {
void DivEngine::putAssetDirData(SafeWriter* w, std::vector<DivAssetDir>& dir) {
size_t blockStartSeek, blockEndSeek;
w->write("ADIR",4);
blockStartSeek=w->tell();
w->writeI(0);
w->writeI(dir.size());
for (DivAssetDir& i: dir) {
w->writeString(i.name,false);
w->writeS(i.entries.size());
for (int j: i.entries) {
w->writeC(j);
}
}
blockEndSeek=w->tell();
w->seek(blockStartSeek,SEEK_SET);
w->writeI(blockEndSeek-blockStartSeek-4);
w->seek(0,SEEK_END);
}
DivDataErrors DivEngine::readAssetDirData(SafeReader& reader, std::vector<DivAssetDir>& dir) {
char magic[4];
reader.read(magic,4);
if (memcmp(magic,"ADIR",4)!=0) {
logV("header is invalid: %c%c%c%c",magic[0],magic[1],magic[2],magic[3]);
return DIV_DATA_INVALID_HEADER;
}
reader.readI(); // reserved
unsigned int numDirs=reader.readI();
for (unsigned int i=0; i<numDirs; i++) {
DivAssetDir d;
d.name=reader.readString();
unsigned short numEntries=reader.readS();
for (unsigned short j=0; j<numEntries; j++) {
d.entries.push_back(((unsigned char)reader.readC()));
}
dir.push_back(d);
}
return DIV_DATA_SUCCESS;
}
SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) {
saveLock.lock();
std::vector<int> subSongPtr;
std::vector<int> sysFlagsPtr;
@ -4829,7 +5077,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
std::vector<int> wavePtr;
std::vector<int> samplePtr;
std::vector<int> patPtr;
size_t ptrSeek, subSongPtrSeek, sysFlagsPtrSeek, blockStartSeek, blockEndSeek;
int assetDirPtr[3];
size_t ptrSeek, subSongPtrSeek, sysFlagsPtrSeek, blockStartSeek, blockEndSeek, assetDirPtrSeek;
size_t subSongIndex=0;
DivSubSong* subSong=song.subsong[subSongIndex];
warnings="";
@ -5128,6 +5377,12 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
}
}
// asset dir pointers (we'll seek here later)
assetDirPtrSeek=w->tell();
w->writeI(0);
w->writeI(0);
w->writeI(0);
blockEndSeek=w->tell();
w->seek(blockStartSeek,SEEK_SET);
w->writeI(blockEndSeek-blockStartSeek-4);
@ -5215,6 +5470,14 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->seek(0,SEEK_END);
}
/// ASSET DIRECTORIES
assetDirPtr[0]=w->tell();
putAssetDirData(w,song.insDir);
assetDirPtr[1]=w->tell();
putAssetDirData(w,song.waveDir);
assetDirPtr[2]=w->tell();
putAssetDirData(w,song.sampleDir);
/// INSTRUMENT
for (int i=0; i<song.insLen; i++) {
DivInstrument* ins=song.ins[i];
@ -5240,31 +5503,133 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
for (PatToWrite& i: patsToWrite) {
DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false);
patPtr.push_back(w->tell());
w->write("PATR",4);
blockStartSeek=w->tell();
w->writeI(0);
w->writeS(i.chan);
w->writeS(i.pat);
w->writeS(i.subsong);
if (newPatternFormat) {
w->write("PATN",4);
blockStartSeek=w->tell();
w->writeI(0);
w->writeS(0); // reserved
w->writeC(i.subsong);
w->writeC(i.chan);
w->writeS(i.pat);
w->writeString(pat->name,false);
for (int j=0; j<song.subsong[i.subsong]->patLen; j++) {
w->writeS(pat->data[j][0]); // note
w->writeS(pat->data[j][1]); // octave
w->writeS(pat->data[j][2]); // instrument
w->writeS(pat->data[j][3]); // volume
#ifdef TA_BIG_ENDIAN
for (int k=0; k<song.subsong[i.subsong]->pat[i.chan].effectCols*2; k++) {
w->writeS(pat->data[j][4+k]);
unsigned char emptyRows=0;
for (int j=0; j<song.subsong[i.subsong]->patLen; j++) {
unsigned char mask=0;
unsigned char finalNote=255;
unsigned short effectMask=0;
if (pat->data[j][0]==100) {
finalNote=180;
} else if (pat->data[j][0]==101) { // note release
finalNote=181;
} else if (pat->data[j][0]==102) { // macro release
finalNote=182;
} else if (pat->data[j][1]==0 && pat->data[j][0]==0) {
finalNote=255;
} else {
int seek=(pat->data[j][0]+(signed char)pat->data[j][1]*12)+60;
if (seek<0 || seek>=180) {
finalNote=255;
} else {
finalNote=seek;
}
}
if (finalNote!=255) mask|=1; // note
if (pat->data[j][2]!=-1) mask|=2; // instrument
if (pat->data[j][3]!=-1) mask|=4; // volume
for (int k=0; k<song.subsong[i.subsong]->pat[i.chan].effectCols*2; k+=2) {
if (k==0) {
if (pat->data[j][4+k]!=-1) mask|=8;
if (pat->data[j][5+k]!=-1) mask|=16;
} else if (k<8) {
if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=32;
} else {
if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=64;
}
if (pat->data[j][4+k]!=-1) effectMask|=(1<<k);
if (pat->data[j][5+k]!=-1) effectMask|=(2<<k);
}
if (mask==0) {
emptyRows++;
if (emptyRows>127) {
w->writeC(128|(emptyRows-2));
emptyRows=0;
}
} else {
if (emptyRows>1) {
w->writeC(128|(emptyRows-2));
emptyRows=0;
} else if (emptyRows) {
w->writeC(0);
emptyRows=0;
}
w->writeC(mask);
if (mask&32) w->writeC(effectMask&0xff);
if (mask&64) w->writeC((effectMask>>8)&0xff);
if (mask&1) w->writeC(finalNote);
if (mask&2) w->writeC(pat->data[j][2]);
if (mask&4) w->writeC(pat->data[j][3]);
if (mask&8) w->writeC(pat->data[j][4]);
if (mask&16) w->writeC(pat->data[j][5]);
if (mask&32) {
if (effectMask&4) w->writeC(pat->data[j][6]);
if (effectMask&8) w->writeC(pat->data[j][7]);
if (effectMask&16) w->writeC(pat->data[j][8]);
if (effectMask&32) w->writeC(pat->data[j][9]);
if (effectMask&64) w->writeC(pat->data[j][10]);
if (effectMask&128) w->writeC(pat->data[j][11]);
}
if (mask&64) {
if (effectMask&256) w->writeC(pat->data[j][12]);
if (effectMask&512) w->writeC(pat->data[j][13]);
if (effectMask&1024) w->writeC(pat->data[j][14]);
if (effectMask&2048) w->writeC(pat->data[j][15]);
if (effectMask&4096) w->writeC(pat->data[j][16]);
if (effectMask&8192) w->writeC(pat->data[j][17]);
if (effectMask&16384) w->writeC(pat->data[j][18]);
if (effectMask&32768) w->writeC(pat->data[j][19]);
}
}
}
#else
w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects
#endif
}
w->writeString(pat->name,false);
// stop
w->writeC(0xff);
} else {
w->write("PATR",4);
blockStartSeek=w->tell();
w->writeI(0);
w->writeS(i.chan);
w->writeS(i.pat);
w->writeS(i.subsong);
w->writeS(0); // reserved
for (int j=0; j<song.subsong[i.subsong]->patLen; j++) {
w->writeS(pat->data[j][0]); // note
w->writeS(pat->data[j][1]); // octave
w->writeS(pat->data[j][2]); // instrument
w->writeS(pat->data[j][3]); // volume
#ifdef TA_BIG_ENDIAN
for (int k=0; k<song.subsong[i.subsong]->pat[i.chan].effectCols*2; k++) {
w->writeS(pat->data[j][4+k]);
}
#else
w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects
#endif
}
w->writeString(pat->name,false);
}
blockEndSeek=w->tell();
w->seek(blockStartSeek,SEEK_SET);
@ -5306,6 +5671,12 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeI(sysFlagsPtr[i]);
}
// asset dir pointers
w->seek(assetDirPtrSeek,SEEK_SET);
for (size_t i=0; i<3; i++) {
w->writeI(assetDirPtr[i]);
}
saveLock.unlock();
return w;
}
@ -5440,12 +5811,14 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
w->writeString(song.author,true);
w->writeC(curSubSong->hilightA);
w->writeC(curSubSong->hilightB);
int intHz=curSubSong->hz;
w->writeC(curSubSong->timeBase);
w->writeC(curSubSong->speeds.val[0]);
w->writeC((curSubSong->speeds.len>=2)?curSubSong->speeds.val[1]:curSubSong->speeds.val[0]);
w->writeC(curSubSong->pal);
w->writeC(curSubSong->customTempo);
w->writeC((intHz<=53)?1:0);
w->writeC((intHz!=60 && intHz!=50));
char customHz[4];
memset(customHz,0,4);
snprintf(customHz,4,"%d",(int)curSubSong->hz);

View file

@ -168,7 +168,7 @@ void DivPlatformAmiga::acquire(short** buf, size_t len) {
outL+=(output*sep2)>>7;
outR+=(output*sep1)>>7;
}
oscBuf[i]->data[oscBuf[i]->needle++]=(amiga.nextOut[i]*MIN(64,amiga.audVol[i]))<<2;
oscBuf[i]->data[oscBuf[i]->needle++]=(amiga.nextOut[i]*MIN(64,amiga.audVol[i]))<<1;
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=0;
}

View file

@ -76,7 +76,7 @@ void DivPlatformArcade::acquire_nuked(short** buf, size_t len) {
}
for (int i=0; i<8; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i];
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]>>1;
}
if (o[0]<-32768) o[0]=-32768;
@ -111,7 +111,7 @@ void DivPlatformArcade::acquire_ymfm(short** buf, size_t len) {
fm_ymfm->generate(&out_ymfm);
for (int i=0; i<8; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1));
oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))>>1;
}
os[0]=out_ymfm.data[0];

View file

@ -120,7 +120,7 @@ void DivPlatformAY8910::runDAC() {
bool end=false;
bool changed=false;
int prevOut=chan[i].dac.out;
while (chan[i].dac.period>rate && !end) {
while (chan[i].dac.period>dacRate && !end) {
DivSample* s=parent->getSample(chan[i].dac.sample);
if (s->samples<=0) {
chan[i].dac.sample=-1;
@ -143,7 +143,7 @@ void DivPlatformAY8910::runDAC() {
end=true;
break;
}
chan[i].dac.period-=rate;
chan[i].dac.period-=dacRate;
}
if (changed && !end) {
if (!isMuted[i]) {
@ -187,9 +187,9 @@ void DivPlatformAY8910::acquire(short** buf, size_t len) {
buf[0][i]=ayBuf[0][0];
buf[1][i]=buf[0][i];
oscBuf[0]->data[oscBuf[0]->needle++]=sunsoftVolTable[31-(ay->lastIndx&31)]>>3;
oscBuf[1]->data[oscBuf[1]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>5)&31)]>>3;
oscBuf[2]->data[oscBuf[2]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>10)&31)]>>3;
oscBuf[0]->data[oscBuf[0]->needle++]=sunsoftVolTable[31-(ay->lastIndx&31)]<<2;
oscBuf[1]->data[oscBuf[1]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>5)&31)]<<2;
oscBuf[2]->data[oscBuf[2]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>10)&31)]<<2;
}
} else {
for (size_t i=0; i<len; i++) {
@ -205,9 +205,9 @@ void DivPlatformAY8910::acquire(short** buf, size_t len) {
buf[1][i]=buf[0][i];
}
oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<2;
oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<2;
oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<2;
oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<1;
oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<1;
oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<1;
}
}
}
@ -797,6 +797,7 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) {
chipClock=extClock;
rate=chipClock/extDiv;
clockSel=false;
dacRate=chipClock/dacRateDiv;
} else {
clockSel=flags.getBool("halfClock",false);
switch (flags.getInt("clockSel",0)) {
@ -851,6 +852,7 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) {
}
CHECK_CUSTOM_CLOCK;
rate=chipClock/8;
dacRate=rate;
}
for (int i=0; i<3; i++) {
oscBuf[i]->rate=rate;

View file

@ -104,7 +104,9 @@ class DivPlatformAY8910: public DivDispatch {
bool extMode;
unsigned int extClock;
int dacRate;
unsigned char extDiv;
unsigned char dacRateDiv;
bool stereo, sunsoft, intellivision, clockSel;
bool ioPortA, ioPortB;
@ -119,7 +121,6 @@ class DivPlatformAY8910: public DivDispatch {
short* ayBuf[3];
size_t ayBufLen;
void runDAC();
void checkWrites();
void updateOutSel(bool immediate=false);
@ -127,6 +128,7 @@ class DivPlatformAY8910: public DivDispatch {
friend void putDispatchChan(void*,int,int);
public:
void runDAC();
void setExtClockDiv(unsigned int eclk=COLOR_NTSC, unsigned char ediv=8);
void acquire(short** buf, size_t len);
int dispatch(DivCommand c);
@ -151,10 +153,11 @@ class DivPlatformAY8910: public DivDispatch {
const char** getRegisterSheet();
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
DivPlatformAY8910(bool useExtMode=false, unsigned int eclk=COLOR_NTSC, unsigned char ediv=8):
DivPlatformAY8910(bool useExtMode=false, unsigned int eclk=COLOR_NTSC, unsigned char ediv=8, unsigned char ddiv=24):
DivDispatch(),
extMode(useExtMode),
extClock(eclk),
extDiv(ediv) {}
extDiv(ediv),
dacRateDiv(ddiv) {}
};
#endif

View file

@ -186,9 +186,9 @@ void DivPlatformAY8930::acquire(short** buf, size_t len) {
buf[1][i]=buf[0][i];
}
oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<2;
oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<2;
oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<2;
oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<1;
oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<1;
oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<1;
}
}

View file

@ -55,7 +55,7 @@ void DivPlatformBubSysWSG::acquire(short** buf, size_t len) {
chanOut=chan[i].waveROM[k005289.addr(i)]*(regPool[2+i]&0xf);
out+=chanOut;
if (writeOscBuf==0) {
oscBuf[i]->data[oscBuf[i]->needle++]=chanOut<<7;
oscBuf[i]->data[oscBuf[i]->needle++]=chanOut<<6;
}
}
}

View file

@ -80,18 +80,18 @@ void DivPlatformC64::acquire(short** buf, size_t len) {
sid_fp.clock(4,&buf[0][i]);
if (++writeOscBuf>=4) {
writeOscBuf=0;
oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp.lastChanOut[0]-dcOff)>>5;
oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp.lastChanOut[1]-dcOff)>>5;
oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp.lastChanOut[2]-dcOff)>>5;
oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp.lastChanOut[0]-dcOff)>>6;
oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp.lastChanOut[1]-dcOff)>>6;
oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp.lastChanOut[2]-dcOff)>>6;
}
} else {
sid.clock();
buf[0][i]=sid.output();
if (++writeOscBuf>=16) {
writeOscBuf=0;
oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>5;
oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>5;
oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>5;
oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>6;
oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>6;
oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>6;
}
}
}
@ -105,6 +105,7 @@ void DivPlatformC64::updateFilter() {
}
void DivPlatformC64::tick(bool sysTick) {
bool willUpdateFilter=false;
for (int i=0; i<3; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
@ -117,10 +118,10 @@ void DivPlatformC64::tick(bool sysTick) {
if (filtCut>2047) filtCut=2047;
if (filtCut<0) filtCut=0;
}
updateFilter();
willUpdateFilter=true;
} else {
vol=MIN(15,chan[i].std.vol.val);
updateFilter();
willUpdateFilter=true;
}
}
if (NEW_ARP_STRAT) {
@ -156,11 +157,11 @@ void DivPlatformC64::tick(bool sysTick) {
}
if (chan[i].std.ex1.had) {
filtControl=chan[i].std.ex1.val&15;
updateFilter();
willUpdateFilter=true;
}
if (chan[i].std.ex2.had) {
filtRes=chan[i].std.ex2.val&15;
updateFilter();
willUpdateFilter=true;
}
if (chan[i].std.ex3.had) {
chan[i].sync=chan[i].std.ex3.val&1;
@ -207,6 +208,7 @@ void DivPlatformC64::tick(bool sysTick) {
chan[i].freqChanged=false;
}
}
if (willUpdateFilter) updateFilter();
}
int DivPlatformC64::dispatch(DivCommand c) {

View file

@ -32,7 +32,7 @@ void DivPlatformDummy::acquire(short** buf, size_t len) {
if (chan[j].active) {
if (!isMuted[j]) {
chanOut=(((signed short)chan[j].pos)*chan[j].amp*chan[j].vol)>>12;
oscBuf[j]->data[oscBuf[j]->needle++]=chanOut;
oscBuf[j]->data[oscBuf[j]->needle++]=chanOut>>1;
out+=chanOut;
} else {
oscBuf[j]->data[oscBuf[j]->needle++]=0;

View file

@ -168,7 +168,7 @@ void DivPlatformES5506::acquire(short** buf, size_t len) {
buf[(o<<1)|1][h]=es5506.rout(o);
}
for (int i=chanMax; i>=0; i--) {
oscBuf[i]->data[oscBuf[i]->needle++]=(es5506.voice_lout(i)+es5506.voice_rout(i))>>5;
oscBuf[i]->data[oscBuf[i]->needle++]=(es5506.voice_lout(i)+es5506.voice_rout(i))>>6;
}
}
}

View file

@ -64,7 +64,7 @@ void DivPlatformFDS::acquire_puNES(short* buf, size_t len) {
buf[i]=sample;
if (++writeOscBuf>=32) {
writeOscBuf=0;
oscBuf->data[oscBuf->needle++]=sample<<1;
oscBuf->data[oscBuf->needle++]=sample;
}
}
}
@ -80,7 +80,7 @@ void DivPlatformFDS::acquire_NSFPlay(short* buf, size_t len) {
buf[i]=sample;
if (++writeOscBuf>=32) {
writeOscBuf=0;
oscBuf->data[oscBuf->needle++]=sample<<1;
oscBuf->data[oscBuf->needle++]=sample;
}
}
}

View file

@ -75,7 +75,7 @@ void DivPlatformGA20::acquire(short** buf, size_t len) {
ga20.sound_stream_update(buffer, 1);
buf[0][h]=(signed int)(ga20Buf[0][h]+ga20Buf[1][h]+ga20Buf[2][h]+ga20Buf[3][h])>>2;
for (int i=0; i<4; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=ga20Buf[i][h];
oscBuf[i]->data[oscBuf[i]->needle++]=ga20Buf[i][h]>>1;
}
}
}

View file

@ -74,7 +74,7 @@ void DivPlatformGB::acquire(short** buf, size_t len) {
buf[1][i]=gb->apu_output.final_sample.right;
for (int i=0; i<4; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(gb->apu_output.current_sample[i].left+gb->apu_output.current_sample[i].right)<<6;
oscBuf[i]->data[oscBuf[i]->needle++]=(gb->apu_output.current_sample[i].left+gb->apu_output.current_sample[i].right)<<5;
}
}
}
@ -82,8 +82,12 @@ void DivPlatformGB::acquire(short** buf, size_t len) {
void DivPlatformGB::updateWave() {
rWrite(0x1a,0);
for (int i=0; i<16; i++) {
int nibble1=15-ws.output[((i<<1)+antiClickWavePos-1)&31];
int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos-1)&31];
int nibble1=ws.output[((i<<1)+antiClickWavePos)&31];
int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&31];
if (invertWave) {
nibble1^=15;
nibble2^=15;
}
rWrite(0x30+i,(nibble1<<4)|nibble2);
}
antiClickWavePos&=31;
@ -658,6 +662,7 @@ void DivPlatformGB::setFlags(const DivConfig& flags) {
model=GB_MODEL_AGB;
break;
}
invertWave=flags.getBool("invertWave",true);
enoughAlready=flags.getBool("enoughAlready",false);
}

View file

@ -57,6 +57,7 @@ class DivPlatformGB: public DivDispatch {
DivDispatchOscBuffer* oscBuf[4];
bool isMuted[4];
bool antiClickEnabled;
bool invertWave;
bool enoughAlready;
unsigned char lastPan;
DivWaveSynth ws;

View file

@ -184,16 +184,18 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) {
if (i==5) {
if (fm.dacen) {
if (softPCM) {
oscBuf[5]->data[oscBuf[5]->needle++]=chan[5].dacOutput<<7;
oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<7;
oscBuf[5]->data[oscBuf[5]->needle++]=chan[5].dacOutput<<6;
oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<6;
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=fm.dacdata<<7;
oscBuf[i]->data[oscBuf[i]->needle++]=fm.dacdata<<6;
oscBuf[6]->data[oscBuf[6]->needle++]=0;
}
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]<<(chipType==2?0:7);
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fm.ch_out[i]<<(chipType==2?1:6),-32768,32767);
oscBuf[6]->data[oscBuf[6]->needle++]=0;
}
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]<<(chipType==2?0:7);
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fm.ch_out[i]<<(chipType==2?1:6),-32768,32767);
}
}
@ -241,19 +243,21 @@ void DivPlatformGenesis::acquire_ymfm(short** buf, size_t len) {
//OPN2_Write(&fm,0,0);
for (int i=0; i<6; i++) {
int chOut=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))<<6;
int chOut=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))<<5;
if (chOut<-32768) chOut=-32768;
if (chOut>32767) chOut=32767;
if (i==5) {
if (fm_ymfm->debug_dac_enable()) {
if (softPCM) {
oscBuf[5]->data[oscBuf[5]->needle++]=chan[5].dacOutput<<7;
oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<7;
oscBuf[5]->data[oscBuf[5]->needle++]=chan[5].dacOutput<<6;
oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<6;
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=fm_ymfm->debug_dac_data()<<7;
oscBuf[i]->data[oscBuf[i]->needle++]=fm_ymfm->debug_dac_data()<<6;
oscBuf[6]->data[oscBuf[6]->needle++]=0;
}
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=chOut;
oscBuf[6]->data[oscBuf[6]->needle++]=0;
}
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=chOut;

View file

@ -79,14 +79,14 @@ void DivPlatformK007232::acquire(short** buf, size_t len) {
buf[0][h]=(lout[0]+lout[1])<<4;
buf[1][h]=(rout[0]+rout[1])<<4;
for (int i=0; i<2; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(lout[i]+rout[i])<<4;
oscBuf[i]->data[oscBuf[i]->needle++]=(lout[i]+rout[i])<<3;
}
} else {
const unsigned char vol=regPool[0xc];
const signed int out[2]={(k007232.output(0)*(vol&0xf)),(k007232.output(1)*((vol>>4)&0xf))};
buf[0][h]=(out[0]+out[1])<<4;
for (int i=0; i<2; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=out[i]<<5;
oscBuf[i]->data[oscBuf[i]->needle++]=out[i]<<4;
}
}
}

View file

@ -81,7 +81,7 @@ void DivPlatformK053260::acquire(short** buf, size_t len) {
buf[1][i]=rout;
for (int i=0; i<4; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(k053260.voice_out(i,0)+k053260.voice_out(i,1))>>1;
oscBuf[i]->data[oscBuf[i]->needle++]=(k053260.voice_out(i,0)+k053260.voice_out(i,1))>>2;
}
}
}

View file

@ -85,9 +85,9 @@ void DivPlatformMMC5::acquire(short** buf, size_t len) {
if (++writeOscBuf>=32) {
writeOscBuf=0;
oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:((mmc5->S3.output*10)<<7);
oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:((mmc5->S4.output*10)<<7);
oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:((mmc5->pcm.output*2)<<6);
oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:((mmc5->S3.output*10)<<6);
oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:((mmc5->S4.output*10)<<6);
oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:((mmc5->pcm.output*2)<<5);
}
}
}

View file

@ -60,7 +60,7 @@ void DivPlatformMSM5232::acquire(short** buf, size_t len) {
((regPool[12+(i>>4)]&2)?((msm->vo8[i]*partVolume[2+(i&4)])>>8):0)+
((regPool[12+(i>>4)]&4)?((msm->vo4[i]*partVolume[1+(i&4)])>>8):0)+
((regPool[12+(i>>4)]&8)?((msm->vo2[i]*partVolume[i&4])>>8):0)
)<<3;
)<<2;
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(o,-32768,32767);
}

View file

@ -84,7 +84,7 @@ void DivPlatformMSM6258::acquire(short** buf, size_t len) {
} else {
buf[0][h]=(msmPan&2)?msmOut:0;
buf[1][h]=(msmPan&1)?msmOut:0;
oscBuf[0]->data[oscBuf[0]->needle++]=msmPan?msmOut:0;
oscBuf[0]->data[oscBuf[0]->needle++]=msmPan?(msmOut>>1):0;
}
}
}

View file

@ -79,9 +79,8 @@ void DivPlatformMSM6295::acquire(short** buf, size_t len) {
if (++updateOsc>=22) {
updateOsc=0;
// TODO: per-channel osc
for (int i=0; i<4; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=msm.voice_out(i)<<6;
oscBuf[i]->data[oscBuf[i]->needle++]=msm.voice_out(i)<<5;
}
}
}

View file

@ -118,7 +118,7 @@ void DivPlatformN163::acquire(short** buf, size_t len) {
buf[0][i]=out;
if (n163.voice_cycle()==0x78) for (int i=0; i<8; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=n163.voice_out(i)<<7;
oscBuf[i]->data[oscBuf[i]->needle++]=n163.voice_out(i)<<6;
}
// command queue

View file

@ -177,7 +177,7 @@ void DivPlatformNamcoWSG::acquire(short** buf, size_t len) {
};
namco->sound_stream_update(bufC,1);
for (int i=0; i<chans; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=namco->m_channel_list[i].last_out*chans;
oscBuf[i]->data[oscBuf[i]->needle++]=(namco->m_channel_list[i].last_out*chans)>>1;
}
}
}

View file

@ -115,11 +115,11 @@ void DivPlatformNES::acquire_puNES(short** buf, size_t len) {
buf[0][i]=sample;
if (++writeOscBuf>=32) {
writeOscBuf=0;
oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:(nes->S1.output<<11);
oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:(nes->S2.output<<11);
oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:(nes->TR.output<<11);
oscBuf[3]->data[oscBuf[3]->needle++]=isMuted[3]?0:(nes->NS.output<<11);
oscBuf[4]->data[oscBuf[4]->needle++]=isMuted[4]?0:(nes->DMC.output<<8);
oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:(nes->S1.output<<10);
oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:(nes->S2.output<<10);
oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:(nes->TR.output<<10);
oscBuf[3]->data[oscBuf[3]->needle++]=isMuted[3]?0:(nes->NS.output<<10);
oscBuf[4]->data[oscBuf[4]->needle++]=isMuted[4]?0:(nes->DMC.output<<7);
}
}
}
@ -142,11 +142,11 @@ void DivPlatformNES::acquire_NSFPlay(short** buf, size_t len) {
buf[0][i]=sample;
if (++writeOscBuf>=32) {
writeOscBuf=0;
oscBuf[0]->data[oscBuf[0]->needle++]=nes1_NP->out[0]<<11;
oscBuf[1]->data[oscBuf[1]->needle++]=nes1_NP->out[1]<<11;
oscBuf[2]->data[oscBuf[2]->needle++]=nes2_NP->out[0]<<11;
oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<11;
oscBuf[4]->data[oscBuf[4]->needle++]=nes2_NP->out[2]<<8;
oscBuf[0]->data[oscBuf[0]->needle++]=nes1_NP->out[0]<<10;
oscBuf[1]->data[oscBuf[1]->needle++]=nes1_NP->out[1]<<10;
oscBuf[2]->data[oscBuf[2]->needle++]=nes2_NP->out[0]<<10;
oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<10;
oscBuf[4]->data[oscBuf[4]->needle++]=nes2_NP->out[2]<<7;
}
}
}
@ -211,7 +211,7 @@ void DivPlatformNES::tick(bool sysTick) {
chan[i].outVol=VOL_SCALE_LINEAR_BROKEN(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15);
if (chan[i].outVol<0) chan[i].outVol=0;
if (i==2) { // triangle
rWrite(0x4000+i*4,(chan[i].outVol==0)?0:255);
rWrite(0x4000+i*4,(chan[i].outVol==0)?0:linearCount);
chan[i].freqChanged=true;
} else {
rWrite(0x4000+i*4,(chan[i].envMode<<4)|chan[i].outVol|((chan[i].duty&3)<<6));
@ -262,7 +262,7 @@ void DivPlatformNES::tick(bool sysTick) {
//rWrite(16+i*5,chan[i].sweep);
}
}
if (i<2) if (chan[i].std.phaseReset.had) {
if (i<3) if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].freqChanged=true;
chan[i].prevFreq=-1;
@ -337,14 +337,22 @@ void DivPlatformNES::tick(bool sysTick) {
goingToLoop=parent->getSample(dacSample)->isLoopable();
// write DPCM
rWrite(0x4015,15);
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
if (nextDPCMFreq>=0) {
rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0));
nextDPCMFreq=-1;
} else {
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
}
rWrite(0x4012,(dpcmAddr>>6)&0xff);
rWrite(0x4013,dpcmLen&0xff);
rWrite(0x4015,31);
dpcmBank=dpcmAddr>>14;
}
} else {
if (dpcmMode) {
if (nextDPCMFreq>=0) {
rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0));
nextDPCMFreq=-1;
} else {
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
}
}
@ -353,6 +361,8 @@ void DivPlatformNES::tick(bool sysTick) {
if (chan[4].keyOn) chan[4].keyOn=false;
chan[4].freqChanged=false;
}
nextDPCMFreq=-1;
}
int DivPlatformNES::dispatch(DivCommand c) {
@ -401,12 +411,17 @@ int DivPlatformNES::dispatch(DivCommand c) {
chan[c.chan].furnaceDac=false;
if (dpcmMode && !skipRegisterWrites) {
unsigned int dpcmAddr=sampleOffDPCM[dacSample];
unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4;
unsigned int dpcmLen=parent->getSample(dacSample)->lengthDPCM>>4;
if (dpcmLen>255) dpcmLen=255;
goingToLoop=parent->getSample(dacSample)->isLoopable();
// write DPCM
rWrite(0x4015,15);
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
if (nextDPCMFreq>=0) {
rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0));
nextDPCMFreq=-1;
} else {
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
}
rWrite(0x4012,(dpcmAddr>>6)&0xff);
rWrite(0x4013,dpcmLen&0xff);
rWrite(0x4015,31);
@ -434,7 +449,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (c.chan==2) {
rWrite(0x4000+c.chan*4,0xff);
rWrite(0x4000+c.chan*4,linearCount);
} else if (!parent->song.brokenOutVol2) {
rWrite(0x4000+c.chan*4,(chan[c.chan].envMode<<4)|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
}
@ -466,7 +481,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
}
if (chan[c.chan].active) {
if (c.chan==2) {
rWrite(0x4000+c.chan*4,0xff);
rWrite(0x4000+c.chan*4,linearCount);
} else {
rWrite(0x4000+c.chan*4,(chan[c.chan].envMode<<4)|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
}
@ -542,6 +557,16 @@ int DivPlatformNES::dispatch(DivCommand c) {
countMode=c.value;
rWrite(0x4017,countMode?0x80:0);
break;
case DIV_CMD_NES_LINEAR_LENGTH:
if (c.chan==2) {
linearCount=c.value;
if (chan[c.chan].active) {
rWrite(0x4000+c.chan*4,(chan[c.chan].outVol==0)?0:linearCount);
chan[c.chan].freqChanged=true;
chan[c.chan].prevFreq=-1;
}
}
break;
case DIV_CMD_NES_DMC:
rWrite(0x4011,c.value&0x7f);
break;
@ -555,6 +580,14 @@ int DivPlatformNES::dispatch(DivCommand c) {
rWrite(0x4013,0);
rWrite(0x4015,31);
break;
case DIV_CMD_SAMPLE_FREQ: {
bool goingToLoop=parent->getSample(dacSample)->isLoopable();
if (dpcmMode) {
nextDPCMFreq=c.value&15;
rWrite(0x4010,(c.value&15)|(goingToLoop?0x40:0));
}
break;
}
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
@ -655,9 +688,11 @@ void DivPlatformNES::reset() {
dacSample=-1;
sampleBank=0;
dpcmBank=0;
dpcmMode=false;
dpcmMode=dpcmModeDefault;
goingToLoop=false;
countMode=false;
nextDPCMFreq=-1;
linearCount=255;
if (useNP) {
nes1_NP->Reset();
@ -709,6 +744,8 @@ void DivPlatformNES::setFlags(const DivConfig& flags) {
for (int i=0; i<5; i++) {
oscBuf[i]->rate=rate/32;
}
dpcmModeDefault=flags.getBool("dpcmMode",true);
}
void DivPlatformNES::notifyInsDeletion(void* ins) {

View file

@ -52,7 +52,10 @@ class DivPlatformNES: public DivDispatch {
unsigned char sampleBank;
unsigned char writeOscBuf;
unsigned char apuType;
unsigned char linearCount;
signed char nextDPCMFreq;
bool dpcmMode;
bool dpcmModeDefault;
bool dacAntiClickOn;
bool useNP;
bool goingToLoop;

View file

@ -211,7 +211,7 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) {
if (!isMuted[adpcmChan]) {
os[0]-=aOut.data[0]>>3;
os[1]-=aOut.data[0]>>3;
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=aOut.data[0];
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=aOut.data[0]>>1;
} else {
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0;
}
@ -234,14 +234,13 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) {
if (fm.channel[i].out[3]!=NULL) {
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[3];
}
oscBuf[i]->data[oscBuf[i]->needle]<<=1;
oscBuf[i]->needle++;
}
// special
oscBuf[melodicChans+1]->data[oscBuf[melodicChans+1]->needle++]=fm.slot[16].out*6;
oscBuf[melodicChans+2]->data[oscBuf[melodicChans+2]->needle++]=fm.slot[14].out*6;
oscBuf[melodicChans+3]->data[oscBuf[melodicChans+3]->needle++]=fm.slot[17].out*6;
oscBuf[melodicChans+4]->data[oscBuf[melodicChans+4]->needle++]=fm.slot[13].out*6;
oscBuf[melodicChans+1]->data[oscBuf[melodicChans+1]->needle++]=fm.slot[16].out*3;
oscBuf[melodicChans+2]->data[oscBuf[melodicChans+2]->needle++]=fm.slot[14].out*3;
oscBuf[melodicChans+3]->data[oscBuf[melodicChans+3]->needle++]=fm.slot[17].out*3;
oscBuf[melodicChans+4]->data[oscBuf[melodicChans+4]->needle++]=fm.slot[13].out*3;
} else {
for (int i=0; i<chans; i++) {
unsigned char ch=outChanMap[i];
@ -259,7 +258,6 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) {
if (fm.channel[i].out[3]!=NULL) {
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[3];
}
oscBuf[i]->data[oscBuf[i]->needle]<<=1;
oscBuf[i]->needle++;
}
}

View file

@ -68,7 +68,7 @@ void DivPlatformOPLL::acquire_nuked(short** buf, size_t len) {
unsigned char nextOut=cycleMapOPLL[fm.cycles];
if ((nextOut>=6 && properDrums) || !isMuted[nextOut]) {
os+=(o[0]+o[1]);
if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<6;
if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<5;
} else {
if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=0;
}
@ -76,7 +76,7 @@ void DivPlatformOPLL::acquire_nuked(short** buf, size_t len) {
if (!(vrc7 || (fm.rm_enable&0x20))) for (int i=0; i<9; i++) {
unsigned char ch=visMapOPLL[i];
if ((i>=6 && properDrums) || !isMuted[ch]) {
oscBuf[ch]->data[oscBuf[ch]->needle++]=(fm.output_ch[i])<<6;
oscBuf[ch]->data[oscBuf[ch]->needle++]=(fm.output_ch[i])<<5;
} else {
oscBuf[ch]->data[oscBuf[ch]->needle++]=0;
}
@ -101,8 +101,17 @@ void DivPlatformOPLL::tick(bool sysTick) {
if (chan[i].std.vol.had) {
chan[i].outVol=VOL_SCALE_LOG_BROKEN(chan[i].vol,MIN(15,chan[i].std.vol.val),15);
if (i<9) {
rWrite(0x30+i,((15-VOL_SCALE_LOG_BROKEN(chan[i].outVol,15-chan[i].state.op[1].tl,15))&15)|(chan[i].state.opllPreset<<4));
if (i>=6 && properDrums) {
drumVol[i-6]=15-chan[i].outVol;
rWrite(0x36,drumVol[0]);
rWrite(0x37,drumVol[1]|(drumVol[4]<<4));
rWrite(0x38,drumVol[3]|(drumVol[2]<<4));
break;
} else if (i<6 || !drums) {
if (i<9) {
rWrite(0x30+i,((15-VOL_SCALE_LOG_BROKEN(chan[i].outVol,15-chan[i].state.op[1].tl,15))&15)|(chan[i].state.opllPreset<<4));
}
}
}
@ -409,10 +418,6 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
case 8: case 9:
chan[c.chan].fixedFreq=(chan[c.chan].state.tomTopFreq&511)<<(chan[c.chan].state.tomTopFreq>>9);
break;
default:
chan[c.chan].fixedFreq=0;
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
break;
}
} else {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);

View file

@ -101,7 +101,7 @@ void DivPlatformPCE::acquire(short** buf, size_t len) {
pce->ResetTS(0);
for (int i=0; i<6; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP((pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1,-32768,32767);
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1],-32768,32767);
}
tempL[0]=(tempL[0]>>1)+(tempL[0]>>2);

View file

@ -42,12 +42,65 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
while (chan[0].audSub>=0x10000) {
chan[0].audSub-=0x10000;
chan[0].audPos+=((!chan[0].useWave) && chan[0].audDir)?-1:1;
if (chan[0].audPos>=(int)chan[0].audLen) {
chan[0].audPos%=chan[0].audLen;
chan[0].audDir=false;
}
chan[0].audDat[0]=chan[0].audDat[1];
chan[0].audDat[1]=chan[0].audDat[2];
chan[0].audDat[2]=chan[0].audDat[3];
chan[0].audDat[3]=chan[0].audDat[4];
chan[0].audDat[4]=chan[0].audDat[5];
chan[0].audDat[5]=chan[0].audDat[6];
chan[0].audDat[6]=chan[0].audDat[7];
chan[0].audDat[7]=(chan[0].ws.output[chan[0].audPos]-0x80)<<8;
}
if (chan[0].audPos>=(int)chan[0].audLen) {
chan[0].audPos%=chan[0].audLen;
chan[0].audDir=false;
const short s0=chan[0].audDat[0];
const short s1=chan[0].audDat[1];
const short s2=chan[0].audDat[2];
const short s3=chan[0].audDat[3];
const short s4=chan[0].audDat[4];
const short s5=chan[0].audDat[5];
const short s6=chan[0].audDat[6];
const short s7=chan[0].audDat[7];
switch (interp) {
case 1: // linear
output=s6+((s7-s6)*(chan[0].audSub&0xffff)>>16);
break;
case 2: { // cubic
float* cubicTable=DivFilterTables::getCubicTable();
float* t=&cubicTable[((chan[0].audSub&0xffff)>>6)<<2];
float result=(float)s4*t[0]+(float)s5*t[1]+(float)s6*t[2]+(float)s7*t[3];
if (result<-32768) result=-32768;
if (result>32767) result=32767;
output=result;
break;
}
case 3: { // sinc
float* sincTable=DivFilterTables::getSincTable8();
float* t1=&sincTable[(8191-((chan[0].audSub&0xffff)>>3))<<2];
float* t2=&sincTable[((chan[0].audSub&0xffff)>>3)<<2];
float result=(
s0*t2[3]+
s1*t2[2]+
s2*t2[1]+
s3*t2[0]+
s4*t1[0]+
s5*t1[1]+
s6*t1[2]+
s7*t1[3]
);
if (result<-32768) result=-32768;
if (result>32767) result=32767;
output=result;
break;
}
default: // none
output=s7;
break;
}
output=(chan[0].ws.output[chan[0].audPos]-0x80)<<8;
} else {
DivSample* s=parent->getSample(chan[0].sample);
if (s->samples>0) {
@ -176,7 +229,7 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
} else {
output=output*chan[0].vol*chan[0].envVol/16384;
}
oscBuf->data[oscBuf->needle++]=output;
oscBuf->data[oscBuf->needle++]=output>>1;
if (outStereo) {
buf[0][h]=((output*chan[0].panL)>>(depthScale+8))<<depthScale;
buf[1][h]=((output*chan[0].panR)>>(depthScale+8))<<depthScale;

View file

@ -366,8 +366,10 @@ void DivPlatformPCSpeaker::tick(bool sysTick) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>65535) chan[i].freq=65535;
if (chan[i].keyOn) {
on=true;
if (!chan[i].std.vol.had) {
if (chan[i].keyOn) {
on=true;
}
}
if (chan[i].keyOff) {
on=false;

View file

@ -85,10 +85,10 @@ void DivPlatformPOKEY::acquireMZ(short* buf, size_t len) {
if (++oscBufDelay>=14) {
oscBufDelay=0;
oscBuf[0]->data[oscBuf[0]->needle++]=pokey.outvol_0<<11;
oscBuf[1]->data[oscBuf[1]->needle++]=pokey.outvol_1<<11;
oscBuf[2]->data[oscBuf[2]->needle++]=pokey.outvol_2<<11;
oscBuf[3]->data[oscBuf[3]->needle++]=pokey.outvol_3<<11;
oscBuf[0]->data[oscBuf[0]->needle++]=pokey.outvol_0<<10;
oscBuf[1]->data[oscBuf[1]->needle++]=pokey.outvol_1<<10;
oscBuf[2]->data[oscBuf[2]->needle++]=pokey.outvol_2<<10;
oscBuf[3]->data[oscBuf[3]->needle++]=pokey.outvol_3<<10;
}
}
}
@ -156,7 +156,7 @@ void DivPlatformPOKEY::tick(bool sysTick) {
for (int i=0; i<4; i++) {
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,parent->song.linearPitch?chan[i].pitch:0,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,parent->song.linearPitch?chan[i].pitch2:0,chipClock,CHIP_DIVIDER);
if ((i==0 && !(audctl&64)) || (i==2 && !(audctl&32)) || i==1 || i==3) {
chan[i].freq/=7;
@ -194,6 +194,11 @@ void DivPlatformPOKEY::tick(bool sysTick) {
chan[i].freq>>=2;
}
// non-linear pitch
if (parent->song.linearPitch==0) {
chan[i].freq-=chan[i].pitch;
}
if (--chan[i].freq<0) chan[i].freq=0;
// snap buzz periods

View file

@ -272,7 +272,7 @@ void DivPlatformQSound::acquire(short** buf, size_t len) {
buf[1][h]=chip.out[1];
for (int i=0; i<19; i++) {
int data=chip.voice_output[i]<<2;
int data=chip.voice_output[i]<<1;
if (data<-32768) data=-32768;
if (data>32767) data=32767;
oscBuf[i]->data[oscBuf[i]->needle++]=data;
@ -319,7 +319,7 @@ void DivPlatformQSound::tick(bool sysTick) {
if (length > 65536 - 16) {
length = 65536 - 16;
}
if (loopStart == -1 || loopStart >= length) {
if (!s->isLoopable()) {
if (i<16) {
qsound_end = offPCM[chan[i].sample] + length + 15;
} else {
@ -466,6 +466,7 @@ int DivPlatformQSound::dispatch(DivCommand c) {
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].keyOff=false;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
@ -609,6 +610,7 @@ void DivPlatformQSound::forceIns() {
for (int i=0; i<19; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
chan[i].keyOff=true;
//chan[i].sample=-1;
}
}

View file

@ -74,7 +74,7 @@ void DivPlatformRF5C68::acquire(short** buf, size_t len) {
rf5c68.sound_stream_update(bufPtrs,chBufPtrs,blockLen);
for (int i=0; i<8; i++) {
for (size_t j=0; j<blockLen; j++) {
oscBuf[i]->data[oscBuf[i]->needle++]=bufC[i*2][j]+bufC[i*2+1][j];
oscBuf[i]->data[oscBuf[i]->needle++]=(bufC[i*2][j]+bufC[i*2+1][j])>>1;
}
}
pos+=blockLen;

View file

@ -87,7 +87,7 @@ void DivPlatformSCC::acquire(short** buf, size_t len) {
buf[0][h]=out;
for (int i=0; i<5; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=scc->voice_out(i)<<7;
oscBuf[i]->data[oscBuf[i]->needle++]=scc->voice_out(i)<<6;
}
}
}

View file

@ -49,7 +49,7 @@ void DivPlatformSegaPCM::acquire(short** buf, size_t len) {
buf[1][h]=os[1];
for (int i=0; i<16; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=pcm.lastOut[i][0]+pcm.lastOut[i][1];
oscBuf[i]->data[oscBuf[i]->needle++]=(pcm.lastOut[i][0]+pcm.lastOut[i][1])>>1;
}
}
}
@ -121,7 +121,7 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
DivSample* s=parent->getSample(chan[i].pcm.sample);
off=(double)s->centerRate/8363.0;
}
chan[i].pcm.freq=MIN(255,(15625+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+512)/(128.0*12.0)))*255)/31250)+(oldSlides?chan[i].pitch2:0);
chan[i].pcm.freq=MIN(255,((rate*0.5)+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+512)/(128.0*12.0)))*255)/rate)+(oldSlides?chan[i].pitch2:0);
rWrite(7+(i<<3),chan[i].pcm.freq);
}
chan[i].freqChanged=false;
@ -138,7 +138,7 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
rWrite(0x84+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff);
rWrite(0x85+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff);
rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]);
if (loopStart<0 || loopStart>=actualLength) {
if (!s->isLoopable()) {
rWrite(0x86+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
} else {
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart;
@ -156,7 +156,7 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
rWrite(0x84+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff);
rWrite(0x85+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff);
rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]);
if (loopStart<0 || loopStart>=actualLength) {
if (!s->isLoopable()) {
rWrite(0x86+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
} else {
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart;
@ -220,7 +220,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
rWrite(0x86+(c.chan<<3),3);
break;
}
chan[c.chan].pcm.freq=MIN(255,(parent->getSample(chan[c.chan].pcm.sample)->rate*255)/31250);
chan[c.chan].pcm.freq=MIN(255,(parent->getSample(chan[c.chan].pcm.sample)->rate*255)/rate);
chan[c.chan].furnacePCM=false;
chan[c.chan].active=true;
chan[c.chan].keyOn=true;

View file

@ -58,9 +58,9 @@ void DivPlatformSM8521::acquire(short** buf, size_t len) {
sm8521_sound_tick(&sm8521,8);
buf[0][h]=sm8521.out<<6;
for (int i=0; i<2; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out<<6;
oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out<<5;
}
oscBuf[2]->data[oscBuf[2]->needle++]=sm8521.noise.base.out<<6;
oscBuf[2]->data[oscBuf[2]->needle++]=sm8521.noise.base.out<<5;
}
}

View file

@ -91,7 +91,7 @@ void DivPlatformSNES::acquire(short** buf, size_t len) {
next=(next*254)/MAX(1,globalVolL+globalVolR);
if (next<-32768) next=-32768;
if (next>32767) next=32767;
oscBuf[i]->data[oscBuf[i]->needle++]=next;
oscBuf[i]->data[oscBuf[i]->needle++]=next>>1;
}
}
}
@ -202,6 +202,7 @@ void DivPlatformSNES::tick(bool sysTick) {
}
}
for (int i=0; i<8; i++) {
// TODO: if wavetable length is higher than 32, we lose precision!
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
DivSample* s=parent->getSample(chan[i].sample);
double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
@ -221,7 +222,7 @@ void DivPlatformSNES::tick(bool sysTick) {
if (chan[i].audPos>0) {
start=start+MIN(chan[i].audPos,s->lengthBRR-1)/16*9;
}
if (s->loopStart>=0) {
if (s->isLoopable()) {
loop=((s->depth!=DIV_SAMPLE_DEPTH_BRR)?9:0)+start+((s->loopStart/16)*9);
}
} else {
@ -300,6 +301,11 @@ void DivPlatformSNES::tick(bool sysTick) {
rWrite(0x4d,echoBits);
writeEcho=false;
}
if (writeDryVol) {
rWrite(0x0c,dryVolL);
rWrite(0x1c,dryVolR);
writeDryVol=false;
}
for (int i=0; i<8; i++) {
if (chan[i].shallWriteEnv) {
writeEnv(i);
@ -563,6 +569,14 @@ int DivPlatformSNES::dispatch(DivCommand c) {
rWrite(0x3c,echoVolR);
}
break;
case DIV_CMD_SNES_GLOBAL_VOL_LEFT:
dryVolL=c.value;
writeDryVol=true;
break;
case DIV_CMD_SNES_GLOBAL_VOL_RIGHT:
dryVolR=c.value;
writeDryVol=true;
break;
case DIV_CMD_GET_VOLMAX:
return 127;
break;
@ -673,6 +687,7 @@ void DivPlatformSNES::forceIns() {
writeNoise=true;
writePitchMod=true;
writeEcho=true;
writeDryVol=true;
initEcho();
}
@ -761,6 +776,10 @@ void DivPlatformSNES::reset() {
writeNoise=false;
writePitchMod=false;
writeEcho=true;
writeDryVol=false;
dryVolL=127;
dryVolR=127;
echoDelay=initEchoDelay;
echoFeedback=initEchoFeedback;

View file

@ -59,6 +59,7 @@ class DivPlatformSNES: public DivDispatch {
unsigned char noiseFreq;
signed char delay;
signed char echoVolL, echoVolR, echoFeedback;
signed char dryVolL, dryVolR;
signed char echoFIR[8];
unsigned char echoDelay;
size_t sampleTableBase;
@ -66,6 +67,7 @@ class DivPlatformSNES: public DivDispatch {
bool writeNoise;
bool writePitchMod;
bool writeEcho;
bool writeDryVol;
bool echoOn;
bool initEchoOn;

View file

@ -509,7 +509,7 @@ public:
}
if (oscb!=NULL) {
oscb[i]->data[oscb[i]->needle++]=oscbWrite;
oscb[i]->data[oscb[i]->needle++]=oscbWrite>>1;
}
}

View file

@ -39,7 +39,7 @@ static constexpr int MuteInit = 2;
static constexpr int MuteSerialInput = 8;
//just some magick value to match the audio level of mzpokeysnd
static constexpr int16_t MAGICK_VOLUME_BOOSTER = 160;
static constexpr int16_t MAGICK_OSC_VOLUME_BOOSTER = 4;
static constexpr int16_t MAGICK_OSC_VOLUME_BOOSTER = 2;
struct PokeyBase
{

View file

@ -87,7 +87,7 @@ void DivPlatformSwan::acquire(short** buf, size_t len) {
buf[0][h]=samp[0];
buf[1][h]=samp[1];
for (int i=0; i<4; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(ws->sample_cache[i][0]+ws->sample_cache[i][1])<<6;
oscBuf[i]->data[oscBuf[i]->needle++]=(ws->sample_cache[i][0]+ws->sample_cache[i][1])<<5;
}
}
}

View file

@ -51,8 +51,8 @@ void DivPlatformTIA::acquire(short** buf, size_t len) {
}
if (++chanOscCounter>=114) {
chanOscCounter=0;
oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0];
oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1];
oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0]>>1;
oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1]>>1;
}
}
}

View file

@ -78,7 +78,7 @@ void DivPlatformTX81Z::acquire(short** buf, size_t len) {
fm_ymfm->generate(&out_ymfm);
for (int i=0; i<8; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1));
oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))>>1;
}
os[0]=out_ymfm.data[0];

View file

@ -107,7 +107,7 @@ void DivPlatformVB::acquire(short** buf, size_t len) {
tempL=0;
tempR=0;
for (int i=0; i<6; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(vb->last_output[i][0]+vb->last_output[i][1])*8;
oscBuf[i]->data[oscBuf[i]->needle++]=(vb->last_output[i][0]+vb->last_output[i][1])*4;
tempL+=vb->last_output[i][0];
tempR+=vb->last_output[i][1];
}

View file

@ -35,7 +35,7 @@ extern "C" {
#define rWritePCMCtrl(d) {regPool[64]=(d); pcm_write_ctrl(pcm,d);}
#define rWritePCMRate(d) {regPool[65]=(d); pcm_write_rate(pcm,d);}
#define rWritePCMData(d) {regPool[66]=(d); pcm_write_fifo(pcm,d);}
#define rWritePCMVol(d) rWritePCMCtrl((regPool[64]&(~0x3f))|((d)&0x3f))
#define rWritePCMVol(d) rWritePCMCtrl((regPool[64]&(~0x8f))|((d)&15))
const char* regCheatSheetVERA[]={
"CHxFreq", "00+x*4",
@ -107,9 +107,9 @@ void DivPlatformVERA::acquire(short** buf, size_t len) {
pos++;
for (int i=0; i<16; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=psg->channels[i].lastOut<<4;
oscBuf[i]->data[oscBuf[i]->needle++]=psg->channels[i].lastOut<<3;
}
int pcmOut=whyCallItBuf[2][i]+whyCallItBuf[3][i];
int pcmOut=(whyCallItBuf[2][i]+whyCallItBuf[3][i])>>1;
if (pcmOut<-32768) pcmOut=-32768;
if (pcmOut>32767) pcmOut=32767;
oscBuf[16]->data[oscBuf[16]->needle++]=pcmOut;

View file

@ -69,7 +69,7 @@ void DivPlatformVIC20::acquire(short** buf, size_t len) {
vic_sound_machine_calculate_samples(vic,&samp,1,1,0,SAMP_DIVIDER);
buf[0][h]=samp;
for (int i=0; i<4; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=vic->ch[i].out?(vic->volume<<11):0;
oscBuf[i]->data[oscBuf[i]->needle++]=vic->ch[i].out?(vic->volume<<10):0;
}
}
}

View file

@ -87,9 +87,9 @@ void DivPlatformVRC6::acquire(short** buf, size_t len) {
if (++writeOscBuf>=32) {
writeOscBuf=0;
for (int i=0; i<2; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=vrc6.pulse_out(i)<<10;
oscBuf[i]->data[oscBuf[i]->needle++]=vrc6.pulse_out(i)<<9;
}
oscBuf[2]->data[oscBuf[2]->needle++]=vrc6.sawtooth_out()<<10;
oscBuf[2]->data[oscBuf[2]->needle++]=vrc6.sawtooth_out()<<9;
}
// Command part

View file

@ -222,7 +222,7 @@ void DivPlatformX1_010::acquire(short** buf, size_t len) {
if (stereo) buf[1][h]=tempR;
for (int i=0; i<16; i++) {
int vo=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))<<3;
int vo=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))<<2;
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(vo,-32768,32767);
}
}

View file

@ -169,6 +169,15 @@ void DivPlatformYM2203::acquire_combo(short** buf, size_t len) {
static short ignored[2];
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os=0;
// Nuked part
for (unsigned int i=0; i<nukedMult; i++) {
@ -222,11 +231,11 @@ void DivPlatformYM2203::acquire_combo(short** buf, size_t len) {
buf[0][h]=os;
for (int i=0; i<3; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i];
oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i]>>1;
}
for (int i=3; i<6; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2];
oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]>>1;
}
}
}
@ -242,6 +251,15 @@ void DivPlatformYM2203::acquire_ymfm(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os=0;
if (!writes.empty()) {
if (--delay<1) {
@ -264,11 +282,11 @@ void DivPlatformYM2203::acquire_ymfm(short** buf, size_t len) {
for (int i=0; i<3; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1));
oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))>>1;
}
for (int i=3; i<6; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2];
oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]>>1;
}
}
}
@ -567,7 +585,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) {
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
chan[c.chan].macroInit(NULL);
if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].keyOff=true;

View file

@ -321,6 +321,15 @@ void DivPlatformYM2608::acquire_combo(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os[0]=0; os[1]=0;
// Nuked part
for (int i=0; i<nukedMult; i++) {
@ -393,19 +402,19 @@ void DivPlatformYM2608::acquire_combo(short** buf, size_t len) {
for (int i=0; i<psgChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i];
oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i]>>1;
}
ssge->get_last_out(ssgOut);
for (int i=psgChanOffs; i<adpcmAChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs];
oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1;
}
for (int i=adpcmAChanOffs; i<adpcmBChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1);
oscBuf[i]->data[oscBuf[i]->needle++]=(adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1))>>1;
}
oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1);
oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=(abe->get_last_out(0)+abe->get_last_out(1))>>1;
}
}
@ -427,6 +436,15 @@ void DivPlatformYM2608::acquire_ymfm(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os[0]=0; os[1]=0;
if (!writes.empty()) {
if (--delay<1) {
@ -453,19 +471,19 @@ void DivPlatformYM2608::acquire_ymfm(short** buf, size_t len) {
buf[1][h]=os[1];
for (int i=0; i<6; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1));
oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))>>1;
}
ssge->get_last_out(ssgOut);
for (int i=6; i<9; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6];
oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]>>1;
}
for (int i=9; i<15; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1);
oscBuf[i]->data[oscBuf[i]->needle++]=(adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1))>>1;
}
oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1);
oscBuf[15]->data[oscBuf[15]->needle++]=(abe->get_last_out(0)+abe->get_last_out(1))>>1;
}
}
@ -1007,7 +1025,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
chan[c.chan].macroInit(NULL);
if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].keyOff=true;
@ -1675,7 +1693,7 @@ int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, const DivCo
fm=new ymfm::ym2608(iface);
fm->set_fidelity(ymfm::OPN_FIDELITY_MIN);
// YM2149, 2MHz
ay=new DivPlatformAY8910(true,chipClock,ayDiv);
ay=new DivPlatformAY8910(true,chipClock,ayDiv,48);
ay->init(p,3,sugRate,ayFlags);
ay->toggleRegisterDump(true);
setFlags(flags);

View file

@ -256,6 +256,15 @@ void DivPlatformYM2610::acquire_combo(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os[0]=0; os[1]=0;
// Nuked part
for (int i=0; i<24; i++) {
@ -324,19 +333,19 @@ void DivPlatformYM2610::acquire_combo(short** buf, size_t len) {
for (int i=0; i<psgChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[bchOffs[i]];
oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[bchOffs[i]]>>1;
}
ssge->get_last_out(ssgOut);
for (int i=psgChanOffs; i<adpcmAChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs];
oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1;
}
for (int i=adpcmAChanOffs; i<adpcmBChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1);
oscBuf[i]->data[oscBuf[i]->needle++]=(adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1))>>1;
}
oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1);
oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=(abe->get_last_out(0)+abe->get_last_out(1))>>1;
}
}
@ -360,6 +369,15 @@ void DivPlatformYM2610::acquire_ymfm(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os[0]=0; os[1]=0;
if (!writes.empty()) {
if (--delay<1 && !(fm->read(0)&0x80)) {
@ -386,19 +404,19 @@ void DivPlatformYM2610::acquire_ymfm(short** buf, size_t len) {
buf[1][h]=os[1];
for (int i=0; i<psgChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1));
oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))>>1;
}
ssge->get_last_out(ssgOut);
for (int i=psgChanOffs; i<adpcmAChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs];
oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1;
}
for (int i=adpcmAChanOffs; i<adpcmBChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1);
oscBuf[i]->data[oscBuf[i]->needle++]=(adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1))>>1;
}
oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1);
oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=(abe->get_last_out(0)+abe->get_last_out(1))>>1;
}
}
@ -979,7 +997,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
chan[c.chan].macroInit(NULL);
if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].keyOff=true;

View file

@ -320,7 +320,16 @@ void DivPlatformYM2610B::acquire_combo(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os[0]=0; os[1]=0;
// Nuked part
for (int i=0; i<24; i++) {
if (!writes.empty()) {
@ -392,19 +401,19 @@ void DivPlatformYM2610B::acquire_combo(short** buf, size_t len) {
for (int i=0; i<psgChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i];
oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i]>>1;
}
ssge->get_last_out(ssgOut);
for (int i=psgChanOffs; i<adpcmAChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs];
oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1;
}
for (int i=adpcmAChanOffs; i<adpcmBChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1);
oscBuf[i]->data[oscBuf[i]->needle++]=(adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1))>>1;
}
oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1);
oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=(abe->get_last_out(0)+abe->get_last_out(1))>>1;
}
}
@ -426,6 +435,15 @@ void DivPlatformYM2610B::acquire_ymfm(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os[0]=0; os[1]=0;
if (!writes.empty()) {
if (--delay<1 && !(fm->read(0)&0x80)) {
@ -453,19 +471,19 @@ void DivPlatformYM2610B::acquire_ymfm(short** buf, size_t len) {
for (int i=0; i<psgChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1));
oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))>>1;
}
ssge->get_last_out(ssgOut);
for (int i=psgChanOffs; i<adpcmAChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs];
oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1;
}
for (int i=adpcmAChanOffs; i<adpcmBChanOffs; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1);
oscBuf[i]->data[oscBuf[i]->needle++]=(adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1))>>1;
}
oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1);
oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=(abe->get_last_out(0)+abe->get_last_out(1))>>1;
}
}
@ -1046,7 +1064,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
chan[c.chan].macroInit(NULL);
if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].keyOff=true;

View file

@ -250,7 +250,7 @@ class DivPlatformYM2610Base: public DivPlatformOPN {
fm->set_fidelity(ymfm::OPN_FIDELITY_MED);
setFlags(flags);
// YM2149, 2MHz
ay=new DivPlatformAY8910(true,chipClock,32);
ay=new DivPlatformAY8910(true,chipClock,32,144);
ay->init(p,3,sugRate,ayFlags);
ay->toggleRegisterDump(true);
return 0;

View file

@ -76,7 +76,7 @@ void DivPlatformYMZ280B::acquire(short** buf, size_t len) {
for (int j=0; j<8; j++) {
dataL+=why[j*2][i];
dataR+=why[j*2+1][i];
oscBuf[j]->data[oscBuf[j]->needle++]=(short)(((int)why[j*2][i]+why[j*2+1][i])/2);
oscBuf[j]->data[oscBuf[j]->needle++]=(short)(((int)why[j*2][i]+why[j*2+1][i])/4);
}
buf[0][pos]=(short)(dataL/8);
buf[1][pos]=(short)(dataR/8);

View file

@ -231,6 +231,11 @@ const char* cmdName[]={
"HINT_ARP_TIME",
"SNES_GLOBAL_VOL_LEFT",
"SNES_GLOBAL_VOL_RIGHT",
"NES_LINEAR_LENGTH",
"ALWAYS_SET_VOLUME"
};
@ -412,6 +417,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
short effectVal=pat->data[whatRow][5+(j<<1)];
if (effectVal==-1) effectVal=0;
effectVal&=255;
switch (effect) {
case 0x09: // select groove pattern/speed 1
@ -592,6 +598,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
short effectVal=pat->data[whatRow][5+(j<<1)];
if (effectVal==-1) effectVal=0;
effectVal&=255;
// per-system effect
if (!perSystemEffect(i,effect,effectVal)) switch (effect) {
@ -774,21 +781,17 @@ void DivEngine::processRow(int i, bool afterDelay) {
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
break;
case 0x07: // tremolo
// TODO
// this effect is really weird. i thought it would alter the tremolo depth but turns out it's completely different
// this is how it works:
// - 07xy enables tremolo
// - when enabled, a "low" boundary is calculated based on the current volume
// - then a volume slide down starts to the low boundary, and then when this is reached a volume slide up begins
// - this process repeats until 0700 or 0Axy are found
// - note that a volume value does not stop tremolo - instead it glitches this whole thing up
if (chan[i].tremoloDepth==0) {
chan[i].tremoloPos=0;
}
chan[i].tremoloDepth=effectVal&15;
chan[i].tremoloRate=effectVal>>4;
// tremolo and vol slides are incompatiblw
chan[i].volSpeed=0;
if (chan[i].tremoloDepth!=0) {
chan[i].volSpeed=0;
} else {
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
}
break;
case 0x0a: // volume ramp
// TODO: non-0x-or-x0 value should be treated as 00
@ -1059,6 +1062,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
short effectVal=pat->data[whatRow][5+(j<<1)];
if (effectVal==-1) effectVal=0;
effectVal&=255;
perSystemPostEffect(i,effect,effectVal);
}
}
@ -1198,7 +1202,7 @@ void DivEngine::nextRow() {
for (int j=0; j<curPat[i].effectCols; j++) {
if (pat->data[curRow][4+(j<<1)]==0xed) {
if (pat->data[curRow][5+(j<<1)]>0) {
addition=pat->data[curRow][5+(j<<1)];
addition=pat->data[curRow][5+(j<<1)]&255;
break;
}
}
@ -1228,7 +1232,7 @@ void DivEngine::nextRow() {
}
if (pat->data[curRow][4+(j<<1)]==0xed) {
if (pat->data[curRow][5+(j<<1)]>0) {
addition=pat->data[curRow][5+(j<<1)];
addition=pat->data[curRow][5+(j<<1)]&255;
break;
}
}
@ -1309,11 +1313,6 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
if (--subticks<=0) {
subticks=tickMult;
// MIDI clock
if (output) if (!skipping && output->midiOut!=NULL && midiOutClock) {
output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0));
}
if (stepPlay!=1) {
tempoAccum+=curSubSong->virtualTempoN;
while (tempoAccum>=curSubSong->virtualTempoD) {
@ -1383,10 +1382,10 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
}
if (chan[i].vibratoDepth>0) {
chan[i].vibratoPos+=chan[i].vibratoRate;
if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64;
while (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64;
chan[i].vibratoPosGiant+=chan[i].vibratoRate;
if (chan[i].vibratoPos>=512) chan[i].vibratoPos-=512;
while (chan[i].vibratoPos>=512) chan[i].vibratoPos-=512;
switch (chan[i].vibratoDir) {
case 1: // up
@ -1543,6 +1542,147 @@ int DivEngine::getBufferPos() {
return bufferPos>>MASTER_CLOCK_PREC;
}
void DivEngine::runMidiClock(int totalCycles) {
if (freelance) return;
midiClockCycles-=totalCycles;
while (midiClockCycles<=0) {
curMidiClock++;
if (output) if (!skipping && output->midiOut!=NULL && midiOutClock) {
output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0));
}
double hl=curSubSong->hilightA;
if (hl<=0.0) hl=4.0;
double timeBase=curSubSong->timeBase+1;
double speedSum=0;
double vD=curSubSong->virtualTempoD;
for (int i=0; i<MIN(16,speeds.len); i++) {
speedSum+=speeds.val[i];
}
speedSum/=MAX(1,speeds.len);
if (timeBase<1.0) timeBase=1.0;
if (speedSum<1.0) speedSum=1.0;
if (vD<1) vD=1;
double bpm=((24.0*divider)/(timeBase*hl*speedSum))*(double)curSubSong->virtualTempoN/vD;
midiClockCycles+=got.rate*pow(2,MASTER_CLOCK_PREC)/(bpm);
midiClockDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)(bpm));
if (midiClockDrift>=(bpm)) {
midiClockDrift-=(bpm);
midiClockCycles++;
}
}
}
void DivEngine::runMidiTime(int totalCycles) {
if (freelance) return;
midiTimeCycles-=totalCycles;
while (midiTimeCycles<=0) {
if (curMidiTimePiece==0) {
curMidiTimeCode=curMidiTime;
}
if (!(curMidiTimePiece&3)) curMidiTime++;
double frameRate=96.0;
int timeRate=midiOutTimeRate;
if (timeRate<1 || timeRate>4) {
if (curSubSong->hz>=47.98 && curSubSong->hz<=48.02) {
timeRate=1;
} else if (curSubSong->hz>=49.98 && curSubSong->hz<=50.02) {
timeRate=2;
} else if (curSubSong->hz>=59.9 && curSubSong->hz<=60.11) {
timeRate=4;
} else {
timeRate=4;
}
}
int hour=0;
int minute=0;
int second=0;
int frame=0;
int drop=0;
int actualTime=curMidiTimeCode;
switch (timeRate) {
case 1: // 24
frameRate=96.0;
hour=(actualTime/(60*60*24))%24;
minute=(actualTime/(60*24))%60;
second=(actualTime/24)%60;
frame=actualTime%24;
break;
case 2: // 25
frameRate=100.0;
hour=(actualTime/(60*60*25))%24;
minute=(actualTime/(60*25))%60;
second=(actualTime/25)%60;
frame=actualTime%25;
break;
case 3: // 29.97 (NTSC drop)
frameRate=120.0*(1000.0/1001.0);
// drop
drop=((actualTime/(30*60))-(actualTime/(30*600)))*2;
actualTime+=drop;
hour=(actualTime/(60*60*30))%24;
minute=(actualTime/(60*30))%60;
second=(actualTime/30)%60;
frame=actualTime%30;
break;
case 4: // 30 (NTSC non-drop)
default:
frameRate=120.0;
hour=(actualTime/(60*60*30))%24;
minute=(actualTime/(60*30))%60;
second=(actualTime/30)%60;
frame=actualTime%30;
break;
}
if (output) if (!skipping && output->midiOut!=NULL && midiOutTime) {
unsigned char val=0;
switch (curMidiTimePiece) {
case 0:
val=frame&15;
break;
case 1:
val=frame>>4;
break;
case 2:
val=second&15;
break;
case 3:
val=second>>4;
break;
case 4:
val=minute&15;
break;
case 5:
val=minute>>4;
break;
case 6:
val=hour&15;
break;
case 7:
val=(hour>>4)|((timeRate-1)<<1);
break;
}
val|=curMidiTimePiece<<4;
output->midiOut->send(TAMidiMessage(TA_MIDI_MTC_FRAME,val,0));
}
curMidiTimePiece=(curMidiTimePiece+1)&7;
midiTimeCycles+=got.rate*pow(2,MASTER_CLOCK_PREC)/(frameRate);
midiTimeDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)(frameRate));
if (midiTimeDrift>=(frameRate)) {
midiTimeDrift-=(frameRate);
midiTimeCycles++;
}
}
}
void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size) {
lastLoopPos=-1;
@ -1827,7 +1967,18 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
pendingMetroTick=0;
}
} else {
// 3. tick the clock and fill buffers as needed
// 3. run MIDI clock
int midiTotal=MIN(cycles,runLeftG);
for (int i=0; i<midiTotal; i++) {
runMidiClock();
}
// 4. run MIDI timecode
for (int i=0; i<midiTotal; i++) {
runMidiTime();
}
// 5. tick the clock and fill buffers as needed
if (cycles<runLeftG) {
for (int i=0; i<song.systemLen; i++) {
int total=(cycles*disCont[i].runtotal)/(size<<MASTER_CLOCK_PREC);
@ -1969,7 +2120,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
for (size_t i=0; i<size; i++) {
float chanSum=out[0][i];
for (int j=1; j<outChans; j++) {
chanSum=out[j][i];
chanSum+=out[j][i];
}
out[0][i]=chanSum/outChans;
for (int j=1; j<outChans; j++) {

View file

@ -19,6 +19,7 @@
#include "sample.h"
#include "../ta-log.h"
#include "../fileutils.h"
#include <math.h>
#include <string.h>
#ifdef HAVE_SNDFILE
@ -53,7 +54,7 @@ void DivSample::putSampleData(SafeWriter* w) {
w->writeC(depth);
w->writeC(loopMode);
w->writeC(brrEmphasis);
w->writeC(0); // reserved
w->writeC(dither);
w->writeI(loop?loopStart:-1);
w->writeI(loop?loopEnd:-1);
@ -130,8 +131,11 @@ DivDataErrors DivSample::readSampleData(SafeReader& reader, short version) {
} else {
reader.readC();
}
// reserved
reader.readC();
if (version>=159) {
dither=reader.readC()&1;
} else {
reader.readC();
}
loopStart=reader.readI();
loopEnd=reader.readI();
@ -444,6 +448,35 @@ bool DivSample::save(const char* path) {
#endif
}
bool DivSample::saveRaw(const char* path) {
if (samples<1) {
logE("sample is empty though!");
return false;
}
FILE* f=ps_fopen(path,"wb");
if (f==NULL) {
logE("could not save sample: %s!",strerror(errno));
return false;
}
if (depth==DIV_SAMPLE_DEPTH_BRR) {
if (isLoopable()) {
unsigned short loopPos=getLoopStartPosition(DIV_SAMPLE_DEPTH_BRR);
fputc(loopPos&0xff,f);
fputc(loopPos>>8,f);
} else {
fputc(0,f);
fputc(0,f);
}
}
if (fwrite(getCurBuf(),1,getCurBufLen(),f)!=getCurBufLen()) {
logW("did not write entire instrument!");
}
fclose(f);
return true;
}
// 16-bit memory is padded to 512, to make things easier for ADPCM-A/B.
bool DivSample::initInternal(DivSampleDepth d, int count) {
switch (d) {
@ -455,9 +488,9 @@ bool DivSample::initInternal(DivSampleDepth d, int count) {
break;
case DIV_SAMPLE_DEPTH_1BIT_DPCM: // DPCM
if (dataDPCM!=NULL) delete[] dataDPCM;
lengthDPCM=(count+7)/8;
lengthDPCM=1+((((count+7)/8)+15)&(~15));
dataDPCM=new unsigned char[lengthDPCM];
memset(dataDPCM,0,lengthDPCM);
memset(dataDPCM,0xaa,lengthDPCM);
break;
case DIV_SAMPLE_DEPTH_YMZ_ADPCM: // YMZ ADPCM
if (dataZ!=NULL) delete[] dataZ;
@ -554,7 +587,34 @@ bool DivSample::strip(unsigned int begin, unsigned int end) {
if (begin>samples) begin=samples;
if (end>samples) end=samples;
int count=samples-(end-begin);
if (count<=0) return resize(0);
if (count<=0) {
loopStart=-1;
loopEnd=-1;
loop=false;
return resize(0);
}
if (loopStart>(int)begin && loopEnd<(int)end) {
loopStart=-1;
loopEnd=-1;
loop=false;
} else {
if (loopStart<(int)end && loopStart>(int)begin) {
loopStart=end;
}
if (loopStart>(int)begin && loopEnd>(int)begin) {
loopStart-=end-begin;
loopEnd-=end-begin;
if (loopEnd<0) loopEnd=0;
if (loopStart<0) loopStart=0;
} else if (loopEnd>(int)begin) {
loopEnd=begin;
}
}
if (loopStart>loopEnd) {
loopStart=-1;
loopEnd=-1;
loop=false;
}
if (depth==DIV_SAMPLE_DEPTH_8BIT) {
if (data8!=NULL) {
signed char* oldData8=data8;
@ -599,6 +659,16 @@ bool DivSample::trim(unsigned int begin, unsigned int end) {
int count=end-begin;
if (count==0) return true;
if (begin==0 && end==samples) return true;
if (((int)begin<loopStart && (int)end<loopStart) || ((int)begin>loopEnd && (int)end>loopEnd)) {
loopStart=-1;
loopEnd=-1;
loop=false;
} else {
loopStart-=begin;
loopEnd-=begin;
if (loopStart<0) loopStart=0;
if (loopEnd>count) loopEnd=count;
}
if (depth==DIV_SAMPLE_DEPTH_8BIT) {
if (data8!=NULL) {
signed char* oldData8=data8;
@ -669,9 +739,43 @@ bool DivSample::insert(unsigned int pos, unsigned int length) {
return false;
}
void DivSample::convert(DivSampleDepth newDepth) {
render();
depth=newDepth;
switch (depth) {
case DIV_SAMPLE_DEPTH_1BIT:
setSampleCount((samples+7)&(~7));
break;
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
setSampleCount((1+((((samples+7)/8)+15)&(~15)))<<3);
break;
case DIV_SAMPLE_DEPTH_YMZ_ADPCM:
setSampleCount(((lengthZ+3)&(~0x03))*2);
break;
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: // QSound ADPCM
setSampleCount((samples+1)&(~1));
break;
case DIV_SAMPLE_DEPTH_ADPCM_A: // ADPCM-A
setSampleCount((samples+1)&(~1));
break;
case DIV_SAMPLE_DEPTH_ADPCM_B: // ADPCM-B
setSampleCount((samples+1)&(~1));
break;
case DIV_SAMPLE_DEPTH_BRR: // BRR
setSampleCount(16*(lengthBRR/9));
break;
case DIV_SAMPLE_DEPTH_VOX: // VOX
setSampleCount((samples+1)&(~1));
break;
default:
break;
}
render();
}
#define RESAMPLE_BEGIN \
if (samples<1) return true; \
int finalCount=(double)samples*(r/(double)rate); \
int finalCount=(double)samples*(tRate/sRate); \
signed char* oldData8=data8; \
short* oldData16=data16; \
if (depth==DIV_SAMPLE_DEPTH_16BIT) { \
@ -689,10 +793,10 @@ bool DivSample::insert(unsigned int pos, unsigned int length) {
}
#define RESAMPLE_END \
if (loopStart>=0) loopStart=(double)loopStart*(r/(double)rate); \
if (loopEnd>=0) loopEnd=(double)loopEnd*(r/(double)rate); \
centerRate=(int)((double)centerRate*(r/(double)rate)); \
rate=r; \
if (loopStart>=0) loopStart=(double)loopStart*(tRate/sRate); \
if (loopEnd>=0) loopEnd=(double)loopEnd*(tRate/sRate); \
centerRate=(int)((double)centerRate*(tRate/sRate)); \
rate=(int)((double)rate*(tRate/sRate)); \
samples=finalCount; \
if (depth==DIV_SAMPLE_DEPTH_16BIT) { \
delete[] oldData16; \
@ -700,12 +804,12 @@ bool DivSample::insert(unsigned int pos, unsigned int length) {
delete[] oldData8; \
}
bool DivSample::resampleNone(double r) {
bool DivSample::resampleNone(double sRate, double tRate) {
RESAMPLE_BEGIN;
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
for (int i=0; i<finalCount; i++) {
unsigned int pos=(unsigned int)((double)i*((double)rate/r));
unsigned int pos=(unsigned int)((double)i*(sRate/tRate));
if (pos>=samples) {
data16[i]=0;
} else {
@ -714,7 +818,7 @@ bool DivSample::resampleNone(double r) {
}
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
for (int i=0; i<finalCount; i++) {
unsigned int pos=(unsigned int)((double)i*((double)rate/r));
unsigned int pos=(unsigned int)((double)i*(sRate/tRate));
if (pos>=samples) {
data8[i]=0;
} else {
@ -727,12 +831,12 @@ bool DivSample::resampleNone(double r) {
return true;
}
bool DivSample::resampleLinear(double r) {
bool DivSample::resampleLinear(double sRate, double tRate) {
RESAMPLE_BEGIN;
double posFrac=0;
unsigned int posInt=0;
double factor=(double)rate/r;
double factor=sRate/tRate;
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
for (int i=0; i<finalCount; i++) {
@ -766,12 +870,12 @@ bool DivSample::resampleLinear(double r) {
return true;
}
bool DivSample::resampleCubic(double r) {
bool DivSample::resampleCubic(double sRate, double tRate) {
RESAMPLE_BEGIN;
double posFrac=0;
unsigned int posInt=0;
double factor=(double)rate/r;
double factor=sRate/tRate;
float* cubicTable=DivFilterTables::getCubicTable();
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
@ -820,12 +924,12 @@ bool DivSample::resampleCubic(double r) {
return true;
}
bool DivSample::resampleBlep(double r) {
bool DivSample::resampleBlep(double sRate, double tRate) {
RESAMPLE_BEGIN;
double posFrac=0;
unsigned int posInt=0;
double factor=r/(double)rate;
double factor=tRate/sRate;
float* sincITable=DivFilterTables::getSincIntegralTable();
float* floatData=new float[finalCount];
@ -904,12 +1008,12 @@ bool DivSample::resampleBlep(double r) {
return true;
}
bool DivSample::resampleSinc(double r) {
bool DivSample::resampleSinc(double sRate, double tRate) {
RESAMPLE_BEGIN;
double posFrac=0;
unsigned int posInt=0;
double factor=(double)rate/r;
double factor=sRate/tRate;
float* sincTable=DivFilterTables::getSincTable();
float s[16];
@ -971,29 +1075,29 @@ bool DivSample::resampleSinc(double r) {
return true;
}
bool DivSample::resample(double r, int filter) {
bool DivSample::resample(double sRate, double tRate, int filter) {
if (depth!=DIV_SAMPLE_DEPTH_8BIT && depth!=DIV_SAMPLE_DEPTH_16BIT) return false;
switch (filter) {
case DIV_RESAMPLE_NONE:
return resampleNone(r);
return resampleNone(sRate,tRate);
break;
case DIV_RESAMPLE_LINEAR:
return resampleLinear(r);
return resampleLinear(sRate,tRate);
break;
case DIV_RESAMPLE_CUBIC:
return resampleCubic(r);
return resampleCubic(sRate,tRate);
break;
case DIV_RESAMPLE_BLEP:
return resampleBlep(r);
return resampleBlep(sRate,tRate);
break;
case DIV_RESAMPLE_SINC:
return resampleSinc(r);
return resampleSinc(sRate,tRate);
break;
case DIV_RESAMPLE_BEST:
if (r>rate) {
return resampleSinc(r);
if (tRate>sRate) {
return resampleSinc(sRate,tRate);
} else {
return resampleBlep(r);
return resampleBlep(sRate,tRate);
}
break;
}
@ -1062,12 +1166,14 @@ void DivSample::render(unsigned int formatMask) {
if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_1BIT_DPCM)) { // DPCM
if (!initInternal(DIV_SAMPLE_DEPTH_1BIT_DPCM,samples)) return;
int accum=63;
int next=63;
for (unsigned int i=0; i<samples; i++) {
int next=((unsigned short)(data16[i]^0x8000))>>9;
next=((unsigned short)(data16[i]^0x8000))>>9;
if (next>accum) {
dataDPCM[i>>3]|=1<<(i&7);
accum++;
} else {
dataDPCM[i>>3]&=~(1<<(i&7));
accum--;
}
if (accum<0) accum=0;
@ -1093,13 +1199,29 @@ void DivSample::render(unsigned int formatMask) {
}
if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_8BIT)) { // 8-bit PCM
if (!initInternal(DIV_SAMPLE_DEPTH_8BIT,samples)) return;
for (unsigned int i=0; i<samples; i++) {
data8[i]=data16[i]>>8;
if (dither) {
unsigned short lfsr=0x6438;
unsigned short lfsr1=0x1283;
signed char errorLast=0;
signed char errorCur=0;
for (unsigned int i=0; i<samples; i++) {
signed char val=CLAMP(data16[i]+128,-32768,32767)>>8;
errorLast=errorCur;
errorCur=(val<<8)-data16[i];
data8[i]=CLAMP(val-((((errorLast+errorCur)>>1)+(lfsr&0xff))>>8),-128,127);
lfsr=(lfsr<<1)|(((lfsr>>1)^(lfsr>>2)^(lfsr>>4)^(lfsr>>15))&1);
lfsr1=(lfsr1<<1)|(((lfsr1>>1)^(lfsr1>>2)^(lfsr1>>4)^(lfsr1>>15))&1);
}
} else {
for (unsigned int i=0; i<samples; i++) {
data8[i]=data16[i]>>8;
}
}
}
if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_BRR)) { // BRR
if (!initInternal(DIV_SAMPLE_DEPTH_BRR,samples)) return;
brrEncode(data16,dataBRR,samples,loop?loopStart:-1,brrEmphasis);
int sampleCount=loop?loopEnd:samples;
if (!initInternal(DIV_SAMPLE_DEPTH_BRR,sampleCount)) return;
brrEncode(data16,dataBRR,sampleCount,loop?loopStart:-1,brrEmphasis);
}
if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_VOX)) { // VOX
if (!initInternal(DIV_SAMPLE_DEPTH_VOX,samples)) return;
@ -1173,9 +1295,9 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) {
duplicate=new unsigned char[getCurBufLen()];
memcpy(duplicate,getCurBuf(),getCurBufLen());
}
h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart,loopEnd,loop,brrEmphasis,loopMode);
h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart,loopEnd,loop,brrEmphasis,dither,loopMode);
} else {
h=new DivSampleHistory(depth,rate,centerRate,loopStart,loopEnd,loop,brrEmphasis,loopMode);
h=new DivSampleHistory(depth,rate,centerRate,loopStart,loopEnd,loop,brrEmphasis,dither,loopMode);
}
if (!doNotPush) {
while (!redoHist.empty()) {
@ -1208,6 +1330,8 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) {
loopStart=h->loopStart; \
loopEnd=h->loopEnd; \
loop=h->loop; \
brrEmphasis=h->brrEmphasis; \
dither=h->dither; \
loopMode=h->loopMode;

View file

@ -61,10 +61,10 @@ struct DivSampleHistory {
unsigned int length, samples;
DivSampleDepth depth;
int rate, centerRate, loopStart, loopEnd;
bool loop, brrEmphasis;
bool loop, brrEmphasis, dither;
DivSampleLoopMode loopMode;
bool hasSample;
DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, DivSampleLoopMode lm):
DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, bool di, DivSampleLoopMode lm):
data((unsigned char*)d),
length(l),
samples(s),
@ -75,9 +75,10 @@ struct DivSampleHistory {
loopEnd(le),
loop(lp),
brrEmphasis(be),
dither(di),
loopMode(lm),
hasSample(true) {}
DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, DivSampleLoopMode lm):
DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, bool di, DivSampleLoopMode lm):
data(NULL),
length(0),
samples(0),
@ -88,6 +89,7 @@ struct DivSampleHistory {
loopEnd(le),
loop(lp),
brrEmphasis(be),
dither(di),
loopMode(lm),
hasSample(false) {}
~DivSampleHistory();
@ -108,7 +110,7 @@ struct DivSample {
// - 10: VOX ADPCM
// - 16: 16-bit PCM
DivSampleDepth depth;
bool loop, brrEmphasis;
bool loop, brrEmphasis, dither;
// valid values are:
// - 0: Forward loop
// - 1: Backward loop
@ -188,11 +190,11 @@ struct DivSample {
/**
* @warning DO NOT USE - internal functions
*/
bool resampleNone(double rate);
bool resampleLinear(double rate);
bool resampleCubic(double rate);
bool resampleBlep(double rate);
bool resampleSinc(double rate);
bool resampleNone(double sRate, double tRate);
bool resampleLinear(double sRate, double tRate);
bool resampleCubic(double sRate, double tRate);
bool resampleBlep(double sRate, double tRate);
bool resampleSinc(double sRate, double tRate);
/**
* save this sample to a file.
@ -201,6 +203,13 @@ struct DivSample {
*/
bool save(const char* path);
/**
* save this sample to a file (raw).
* @param path a path.
* @return whether saving succeeded or not.
*/
bool saveRaw(const char* path);
/**
* @warning DO NOT USE - internal function
* initialize sample data.
@ -255,11 +264,19 @@ struct DivSample {
/**
* change the sample rate.
* @warning do not attempt to resample outside of a synchronized block!
* @param rate number of samples.
* @param sRate source rate.
* @param tRate target rate.
* @param filter the interpolation filter.
* @return whether it was successful.
*/
bool resample(double rate, int filter);
bool resample(double sRate, double tRate, int filter);
/**
* convert sample depth.
* @warning do not attempt to do this outside of a synchronized block!
* @param newDepth the new depth.
*/
void convert(DivSampleDepth newDepth);
/**
* initialize the rest of sample formats for this sample.
@ -308,6 +325,7 @@ struct DivSample {
depth(DIV_SAMPLE_DEPTH_16BIT),
loop(false),
brrEmphasis(true),
dither(false),
loopMode(DIV_SAMPLE_LOOP_FORWARD),
data8(NULL),
data16(NULL),

View file

@ -131,6 +131,14 @@ enum DivSystem {
DIV_SYSTEM_K053260
};
enum DivEffectType: unsigned short {
DIV_EFFECT_NULL=0,
DIV_EFFECT_DUMMY,
DIV_EFFECT_EXTERNAL,
DIV_EFFECT_VOLUME,
DIV_EFFECT_FILTER
};
struct DivGroovePattern {
unsigned char val[16];
unsigned char len;
@ -146,8 +154,6 @@ struct DivSubSong {
unsigned char timeBase, arpLen;
DivGroovePattern speeds;
short virtualTempoN, virtualTempoD;
bool pal;
bool customTempo;
float hz;
int patLen, ordersLen;
@ -170,8 +176,6 @@ struct DivSubSong {
arpLen(1),
virtualTempoN(150),
virtualTempoD(150),
pal(true),
customTempo(false),
hz(60.0),
patLen(64),
ordersLen(1) {
@ -192,6 +196,21 @@ struct DivAssetDir {
name(n) {}
};
struct DivEffectStorage {
DivEffectType id;
unsigned short slot, storageVer;
float dryWet;
unsigned char* storage;
size_t storageLen;
DivEffectStorage():
id(DIV_EFFECT_NULL),
slot(0),
storageVer(0),
dryWet(1.0f),
storage(NULL),
storageLen(0) {}
};
struct DivSong {
// version number used for saving the song.
// Furnace will save using the latest possible version,
@ -353,6 +372,7 @@ struct DivSong {
bool oldArpStrategy;
bool patchbayAuto;
bool brokenPortaLegato;
bool brokenFMOff;
std::vector<DivInstrument*> ins;
std::vector<DivWavetable*> wave;
@ -366,6 +386,8 @@ struct DivSong {
std::vector<DivAssetDir> waveDir;
std::vector<DivAssetDir> sampleDir;
std::vector<DivEffectStorage> effects;
DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsOPLDrums, nullInsQSound;
DivWavetable nullWave;
DivSample nullSample;
@ -468,7 +490,8 @@ struct DivSong {
autoSystem(true),
oldArpStrategy(false),
patchbayAuto(true),
brokenPortaLegato(false) {
brokenPortaLegato(false),
brokenFMOff(false) {
for (int i=0; i<DIV_MAX_CHIPS; i++) {
system[i]=DIV_SYSTEM_NULL;
systemVol[i]=1.0;

View file

@ -699,8 +699,8 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_NES]=new DivSysDef(
"NES (Ricoh 2A03)", NULL, 0x06, 0x06, 5, false, true, 0x161, false, (1U<<DIV_SAMPLE_DEPTH_1BIT_DPCM)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"also known as Famicom in Japan, it's the most well-known game console of the '80's.",
{"Pulse 1", "Pulse 2", "Triangle", "Noise", "PCM"},
{"S1", "S2", "TR", "NO", "PCM"},
{"Pulse 1", "Pulse 2", "Triangle", "Noise", "DPCM"},
{"S1", "S2", "TR", "NO", "DMC"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE, DIV_CH_NOISE, DIV_CH_PCM},
{DIV_INS_NES, DIV_INS_NES, DIV_INS_NES, DIV_INS_NES, DIV_INS_AMIGA},
{},
@ -712,7 +712,9 @@ void DivEngine::registerSystems() {
{0x15, {DIV_CMD_NES_ENV_MODE, "15xx: Set envelope mode (0: envelope, 1: length, 2: looping, 3: constant)"}},
{0x16, {DIV_CMD_NES_LENGTH, "16xx: Set length counter (refer to manual for a list of values)"}},
{0x17, {DIV_CMD_NES_COUNT_MODE, "17xx: Set frame counter mode (0: 4-step, 1: 5-step)"}},
{0x18, {DIV_CMD_SAMPLE_MODE, "18xx: Select PCM/DPCM mode (0: PCM; 1: DPCM)"}}
{0x18, {DIV_CMD_SAMPLE_MODE, "18xx: Select PCM/DPCM mode (0: PCM; 1: DPCM)"}},
{0x19, {DIV_CMD_NES_LINEAR_LENGTH, "19xx: Set triangle linear counter (0 to 7F; 80 and higher halt)"}},
{0x20, {DIV_CMD_SAMPLE_FREQ, "20xx: Set DPCM frequency (0 to F)"}}
}
);
@ -911,6 +913,8 @@ void DivEngine::registerSystems() {
{0x1a, {DIV_CMD_SNES_ECHO_VOL_LEFT, "1Axx: Set left echo volume"}},
{0x1b, {DIV_CMD_SNES_ECHO_VOL_RIGHT, "1Bxx: Set right echo volume"}},
{0x1c, {DIV_CMD_SNES_ECHO_FEEDBACK, "1Cxx: Set echo feedback"}},
{0x1e, {DIV_CMD_SNES_GLOBAL_VOL_LEFT, "1Exx: Set dry output volume (left)"}},
{0x1f, {DIV_CMD_SNES_GLOBAL_VOL_RIGHT, "1Fxx: Set dry output volume (right)"}},
{0x30, {DIV_CMD_SNES_ECHO_FIR, "30xx: Set echo filter coefficient 0",constVal<0>,effectVal}},
{0x31, {DIV_CMD_SNES_ECHO_FIR, "31xx: Set echo filter coefficient 1",constVal<1>,effectVal}},
{0x32, {DIV_CMD_SNES_ECHO_FIR, "32xx: Set echo filter coefficient 2",constVal<2>,effectVal}},
@ -1848,7 +1852,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SFX_BEEPER_QUADTONE]=new DivSysDef(
"ZX Spectrum Beeper (QuadTone Engine)", NULL, 0xca, 0, 5, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Another ZX Spectrum beeper system with full PWM pulses and 3-level volume per channel. It also has a pitchable overlay sample channel.",
"another ZX Spectrum beeper system with full PWM pulses and 3-level volume per channel. it also has a pitchable overlay sample channel.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "PCM"},
{"CH1", "CH2", "CH3", "CH4", "PCM"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM},

View file

@ -601,7 +601,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0x95);
w->writeC(streamID);
w->writeS(write.val); // sample number
w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0)|(sampleDir[streamID]?0x10:0)); // flags
w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags
if (sample->isLoopable() && !sampleDir[streamID]) {
loopTimer[streamID]=sample->length8;
loopSample[streamID]=write.val;
@ -620,7 +620,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0x95);
w->writeC(streamID);
w->writeS(pendingFreq[streamID]); // sample number
w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0)|(sampleDir[streamID]?0x10:0)); // flags
w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags
if (sample->isLoopable() && !sampleDir[streamID]) {
loopTimer[streamID]=sample->length8;
loopSample[streamID]=pendingFreq[streamID];
@ -1657,6 +1657,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
}
}
// variable set but not used?
logV("howManyChips: %d",howManyChips);
// write chips and stuff
w->writeI(hasSN);
w->writeI(hasOPLL);
@ -2135,6 +2138,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
if (nextTick(false,true)) {
if (trailing) beenOneLoopAlready=true;
trailing=true;
if (!loop) countDown=0;
for (int i=0; i<chans; i++) {
if (!willExport[dispatchOfChan[i]]) continue;
chan[i].wentThroughNote=false;
@ -2246,7 +2250,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
int delay=i.second.time-lastOne;
if (delay>16) {
w->writeC(0x61);
w->writeS(totalWait);
w->writeS(delay);
} else if (delay>0) {
w->writeC(0x70+delay-1);
}

View file

@ -23,7 +23,7 @@
#include "song.h"
DivZSM::DivZSM() {
w = NULL;
w=NULL;
init();
}
@ -31,8 +31,8 @@ DivZSM::~DivZSM() {
}
void DivZSM::init(unsigned int rate) {
if (w != NULL) delete w;
w = new SafeWriter;
if (w!=NULL) delete w;
w=new SafeWriter;
w->init();
// write default ZSM data header
w->write("zm",2); // magic header
@ -50,7 +50,7 @@ void DivZSM::init(unsigned int rate) {
w->writeS((unsigned short)rate);
// 2 reserved bytes (set to zero)
w->writeS(0x00);
tickRate = rate;
tickRate=rate;
loopOffset=-1;
numWrites=0;
memset(&ymState,-1,sizeof(ymState));
@ -63,44 +63,43 @@ int DivZSM::getoffset() {
}
void DivZSM::writeYM(unsigned char a, unsigned char v) {
int lastMask = ymMask;
int lastMask=ymMask;
if (a==0x19 && v>=0x80) a=0x1a; // AMD/PSD use same reg addr. store PMD as 0x1a
if (a==0x08 && (v&0xf8)) ymMask |= (1 << (v & 0x07)); // mark chan as in-use if keyDN
if (a!=0x08) ymState[ym_NEW][a] = v; // cache the newly-written value
if (a==0x08 && (v&0xf8)) ymMask|=(1<<(v&0x07)); // mark chan as in-use if keyDN
if (a!=0x08) ymState[ym_NEW][a]=v; // cache the newly-written value
bool writeit=false; // used to suppress spurious writes to unused channels
if (a < 0x20) {
if (a == 0x08) {
if (a<0x20) {
if (a==0x08) {
// write keyUPDN messages if channel is active.
writeit = (ymMask & (1 << (v & 0x07))) > 0;
}
else {
writeit=(ymMask&(1<<(v&0x07)))>0;
} else {
// do not suppress global registers
writeit = true;
writeit=true;
}
} else {
writeit = (ymMask & (1 << (a & 0x07))) > 0; // a&0x07 = chan ID for regs >=0x20
writeit=(ymMask&(1<<(a&0x07)))>0; // a&0x07 = chan ID for regs >=0x20
}
if (lastMask != ymMask) {
if (lastMask!=ymMask) {
// if the ymMask just changed, then the channel has become active.
// This can only happen on a KeyDN event, so voice = v & 0x07
// This can only happen on a KeyDN event, so voice=v&0x07
// insert a keyUP just to be safe.
ymwrites.push_back(DivRegWrite(0x08,v&0x07));
numWrites++;
// flush the ym_NEW cached states for this channel into the ZSM....
for ( int i=0x20 + (v&0x07); i <= 0xff ; i+=8) {
if (ymState[ym_NEW][i] != ymState[ym_PREV][i]) {
for (int i=0x20+(v&0x07); i<=0xff; i+=8) {
if (ymState[ym_NEW][i]!=ymState[ym_PREV][i]) {
ymwrites.push_back(DivRegWrite(i,ymState[ym_NEW][i]));
numWrites++;
// ...and update the shadow
ymState[ym_PREV][i] = ymState[ym_NEW][i];
ymState[ym_PREV][i]=ymState[ym_NEW][i];
}
}
}
// Handle the current write if channel is active
if (writeit && ((ymState[ym_NEW][a] != ymState[ym_PREV][a])||a==0x08) ) {
if (writeit && ((ymState[ym_NEW][a]!=ymState[ym_PREV][a]) || a==0x08)) {
// update YM shadow if not the KeyUPDN register.
if (a!=0x008) ymState[ym_PREV][a] = ymState[ym_NEW][a];
// if reg = PMD, then change back to real register 0x19
if (a!=8) ymState[ym_PREV][a]=ymState[ym_NEW][a];
// if reg=PMD, then change back to real register 0x19
if (a==0x1a) a=0x19;
ymwrites.push_back(DivRegWrite(a,v));
numWrites++;
@ -109,24 +108,26 @@ void DivZSM::writeYM(unsigned char a, unsigned char v) {
void DivZSM::writePSG(unsigned char a, unsigned char v) {
// TODO: suppress writes to PSG voice that is not audible (volume=0)
if (a >= 64) {
if (a>=64) {
logD ("ZSM: ignoring VERA PSG write a=%02x v=%02x",a,v);
return;
}
if(psgState[psg_PREV][a] == v) {
if (psgState[psg_NEW][a] != v)
if (psgState[psg_PREV][a]==v) {
if (psgState[psg_NEW][a]!=v) {
// NEW value is being reset to the same as PREV value
// so it is no longer a new write.
numWrites--;
}
} else {
if (psgState[psg_PREV][a] == psgState[psg_NEW][a])
if (psgState[psg_PREV][a]==psgState[psg_NEW][a]) {
// if this write changes the NEW cached value to something other
// than the PREV value, then this is a new write.
numWrites++;
}
}
psgState[psg_NEW][a] = v;
// mark channel as used in the psgMask if volume is set > 0.
if ((a % 4 == 2) && (v & 0x3f)) psgMask |= (1 << (a>>2));
psgState[psg_NEW][a]=v;
// mark channel as used in the psgMask if volume is set>0.
if ((a%4==2) && (v&0x3f)) psgMask|=(1<<(a>>2));
}
void DivZSM::writePCM(unsigned char a, unsigned char v) {
@ -135,7 +136,7 @@ void DivZSM::writePCM(unsigned char a, unsigned char v) {
void DivZSM::tick(int numticks) {
flushWrites();
ticks += numticks;
ticks+=numticks;
}
void DivZSM::setLoopPoint() {
@ -154,12 +155,14 @@ void DivZSM::setLoopPoint() {
memset(&ymState[ym_PREV],-1,sizeof(ymState[ym_PREV]));
// ... and cache (except for unused channels)
memset(&ymState[ym_NEW],-1,0x20);
for (int chan=0; chan<8 ; chan++) {
for (int chan=0; chan<8; chan++) {
// do not clear state for as-yet-unused channels
if (!(ymMask & (1<<chan))) continue;
if (!(ymMask&(1<<chan))) continue;
// clear the state for channels in use so they match the unknown state
// of the YM shadow.
for (int i=0x20+chan; i<=0xff; i+= 8) ymState[ym_NEW][i] = -1;
for (int i=0x20+chan; i<=0xff; i+=8) {
ymState[ym_NEW][i]=-1;
}
}
}
@ -169,8 +172,8 @@ SafeWriter* DivZSM::finish() {
w->writeC(ZSM_EOF);
// update channel use masks.
w->seek(0x09,SEEK_SET);
w->writeC((unsigned char)(ymMask & 0xff));
w->writeS((short)(psgMask & 0xffff));
w->writeC((unsigned char)(ymMask&0xff));
w->writeS((short)(psgMask&0xffff));
// todo: put PCM offset/data writes here once defined in ZSM standard.
return w;
}
@ -179,16 +182,16 @@ void DivZSM::flushWrites() {
logD("ZSM: flushWrites.... numwrites=%d ticks=%d ymwrites=%d",numWrites,ticks,ymwrites.size());
if (numWrites==0) return;
flushTicks(); // only flush ticks if there are writes pending.
for (unsigned char i=0;i<64;i++) {
if (psgState[psg_NEW][i] == psgState[psg_PREV][i]) continue;
for (unsigned char i=0; i<64; i++) {
if (psgState[psg_NEW][i]==psgState[psg_PREV][i]) continue;
psgState[psg_PREV][i]=psgState[psg_NEW][i];
w->writeC(i);
w->writeC(psgState[psg_NEW][i]);
}
int n=0; // n = completed YM writes. used to determine when to write the CMD byte...
int n=0; // n=completed YM writes. used to determine when to write the CMD byte...
for (DivRegWrite& write: ymwrites) {
if (n%ZSM_YM_MAX_WRITES == 0) {
if(ymwrites.size()-n > ZSM_YM_MAX_WRITES) {
if (n%ZSM_YM_MAX_WRITES==0) {
if (ymwrites.size()-n>ZSM_YM_MAX_WRITES) {
w->writeC((unsigned char)(ZSM_YM_CMD+ZSM_YM_MAX_WRITES));
logD("ZSM: YM-write: %d (%02x) [max]",ZSM_YM_MAX_WRITES,ZSM_YM_MAX_WRITES+ZSM_YM_CMD);
} else {
@ -205,10 +208,10 @@ void DivZSM::flushWrites() {
}
void DivZSM::flushTicks() {
while (ticks > ZSM_DELAY_MAX) {
while (ticks>ZSM_DELAY_MAX) {
logD("ZSM: write delay %d (max)",ZSM_DELAY_MAX);
w->writeC((unsigned char)(ZSM_DELAY_CMD+ZSM_DELAY_MAX));
ticks -= ZSM_DELAY_MAX;
ticks-=ZSM_DELAY_MAX;
}
if (ticks>0) {
logD("ZSM: write delay %d",ticks);

View file

@ -27,36 +27,42 @@ constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
constexpr int MASTER_CLOCK_MASK=(sizeof(void*)==8)?0xff:0;
SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) {
int VERA=-1;
int YM=-1;
int IGNORED=0;
int VERA = -1;
int YM = -1;
int IGNORED = 0;
//loop = false;
// find indexes for YM and VERA. Ignore other systems.
for (int i=0; i<song.systemLen; i++) {
switch (song.system[i]) {
case DIV_SYSTEM_VERA:
if (VERA >= 0) { IGNORED++;break; }
VERA = i;
if (VERA>=0) {
IGNORED++;
break;
}
VERA=i;
logD("VERA detected as chip id %d",i);
break;
case DIV_SYSTEM_YM2151:
if (YM >= 0) { IGNORED++;break; }
YM = i;
if (YM>=0) {
IGNORED++;
break;
}
YM=i;
logD("YM detected as chip id %d",i);
break;
default:
IGNORED++;
logD("Ignoring chip %d systemID %d",i,song.system[i]);
break;
}
}
if (VERA < 0 && YM < 0) {
logE("No supported systems for ZSM");
return NULL;
if (VERA<0 && YM<0) {
logE("No supported systems for ZSM");
return NULL;
}
if (IGNORED > 0)
if (IGNORED>0) {
logW("ZSM export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' ');
}
stop();
repeatPattern=false;
@ -64,7 +70,7 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) {
BUSY_BEGIN_SOFT;
double origRate=got.rate;
got.rate=zsmrate & 0xffff;
got.rate=zsmrate&0xffff;
// determine loop point
int loopOrder=0;
@ -89,15 +95,14 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) {
//size_t tickCount=0;
bool done=false;
int loopPos=-1;
int writeCount=0;
int fracWait=0; // accumulates fractional ticks
if (VERA >= 0) disCont[VERA].dispatch->toggleRegisterDump(true);
if (YM >= 0) {
if (VERA>=0) disCont[VERA].dispatch->toggleRegisterDump(true);
if (YM>=0) {
disCont[YM].dispatch->toggleRegisterDump(true);
// emit LFO initialization commands
zsm.writeYM(0x18,0); // freq = 0
zsm.writeYM(0x19,0x7F); // AMD = 7F
zsm.writeYM(0x19,0xFF); // PMD = 7F
zsm.writeYM(0x18,0); // freq=0
zsm.writeYM(0x19,0x7F); // AMD =7F
zsm.writeYM(0x19,0xFF); // PMD =7F
// TODO: incorporate the Furnace meta-command for init data and filter
// out writes to otherwise-unused channels.
}
@ -126,35 +131,35 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) {
int i=0;
// dump YM writes first
if (j==0) {
if (YM < 0)
if (YM<0) {
continue;
else
} else {
i=YM;
}
}
// dump VERA writes second
if (j==1) {
if (VERA < 0)
if (VERA<0) {
continue;
else {
} else {
i=VERA;
}
}
std::vector<DivRegWrite>& writes=disCont[i].dispatch->getRegisterWrites();
if (writes.size() > 0)
logD("zsmOps: Writing %d messages to chip %d",writes.size(), i);
if (writes.size()>0)
logD("zsmOps: Writing %d messages to chip %d",writes.size(),i);
for (DivRegWrite& write: writes) {
if (i==YM) zsm.writeYM(write.addr&0xff, write.val);
if (i==VERA) zsm.writePSG(write.addr&0xff, write.val);
writeCount++;
if (i==YM) zsm.writeYM(write.addr&0xff,write.val);
if (i==VERA) zsm.writePSG(write.addr&0xff,write.val);
}
writes.clear();
}
// write wait
int totalWait=cycles>>MASTER_CLOCK_PREC;
fracWait += cycles & MASTER_CLOCK_MASK;
totalWait += fracWait>>MASTER_CLOCK_PREC;
fracWait &= MASTER_CLOCK_MASK;
fracWait+=cycles&MASTER_CLOCK_MASK;
totalWait+=fracWait>>MASTER_CLOCK_PREC;
fracWait&=MASTER_CLOCK_MASK;
if (totalWait>0) {
zsm.tick(totalWait);
//tickCount+=totalWait;
@ -163,9 +168,9 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) {
// end of song
// done - close out.
got.rate = origRate;
if (VERA >= 0) disCont[VERA].dispatch->toggleRegisterDump(false);
if (YM >= 0) disCont[YM].dispatch->toggleRegisterDump(false);
got.rate=origRate;
if (VERA>=0) disCont[VERA].dispatch->toggleRegisterDump(false);
if (YM>=0) disCont[YM].dispatch->toggleRegisterDump(false);
remainingLoops=-1;
playing=false;

View file

@ -78,11 +78,14 @@ const char* aboutLine[]={
"Dippy",
"djtuBIG-MaliceX",
"dumbut",
"ElectricKeet",
"EpicTyphlosion",
"FΛDE",
"Forte",
"Fragmare",
"freq-mod",
"gtr3qq",
"Hortus",
"iyatemu",
"JayBOB18",
"Jimmy-DS",
@ -100,6 +103,7 @@ const char* aboutLine[]={
"MelonadeM",
"Miker",
"nicco1690",
"niffuM",
"<nk>",
"NyaongI",
"potatoTeto",
@ -120,6 +124,7 @@ const char* aboutLine[]={
"Ultraprogramer",
"UserSniper",
"Weeppiko",
"Xan",
"Yuzu4K",
"Zaxolotl",
"ZoomTen (Zumi)",

View file

@ -93,7 +93,7 @@ void FurnaceGUI::calcChanOsc() {
unsigned short needlePos=buf->needle;
needlePos-=displaySize;
for (unsigned short i=0; i<512; i++) {
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f;
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/32768.0f;
if (minLevel>y) minLevel=y;
if (maxLevel<y) maxLevel=y;
}
@ -116,7 +116,7 @@ void FurnaceGUI::drawChanOsc() {
}
if (!chanOscOpen) return;
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH));
if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen,globalWinFlags|((chanOscOptions)?0:ImGuiWindowFlags_NoTitleBar))) {
if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen,globalWinFlags)) {
bool centerSettingReset=false;
ImDrawList* dl=ImGui::GetWindowDrawList();
if (chanOscOptions) {
@ -152,10 +152,10 @@ void FurnaceGUI::drawChanOsc() {
if (chanOscUseGrad) {
if (chanOscGradTex==NULL) {
chanOscGradTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STREAMING,chanOscGrad.width,chanOscGrad.height);
chanOscGradTex=rend->createTexture(true,chanOscGrad.width,chanOscGrad.height);
if (chanOscGradTex==NULL) {
logE("error while creating gradient texture! %s",SDL_GetError());
logE("error while creating gradient texture!");
} else {
updateChanOscGradTex=true;
}
@ -170,16 +170,16 @@ void FurnaceGUI::drawChanOsc() {
if (chanOscGradTex!=NULL) {
if (updateChanOscGradTex) {
chanOscGrad.render();
if (SDL_UpdateTexture(chanOscGradTex,NULL,chanOscGrad.grad.get(),chanOscGrad.width*4)==0) {
if (rend->updateTexture(chanOscGradTex,chanOscGrad.grad.get(),chanOscGrad.width*4)) {
updateChanOscGradTex=false;
} else {
logE("error while updating gradient texture! %s",SDL_GetError());
logE("error while updating gradient texture!");
}
}
ImVec2 gradLeft=ImGui::GetCursorPos();
ImVec2 gradSize=ImVec2(400.0f*dpiScale,400.0f*dpiScale);
ImGui::Image(chanOscGradTex,gradSize);
ImGui::Image(rend->getTextureID(chanOscGradTex),gradSize);
ImVec2 gradLeftAbs=ImGui::GetItemRectMin();
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
if (chanOscGrad.points.size()<32) {
@ -346,14 +346,19 @@ void FurnaceGUI::drawChanOsc() {
ImRect rect=ImRect(minArea,maxArea);
ImRect inRect=rect;
inRect.Min.x+=dpiScale;
inRect.Min.y+=dpiScale;
inRect.Min.y+=2.0*dpiScale;
inRect.Max.x-=dpiScale;
inRect.Max.y-=dpiScale;
inRect.Max.y-=2.0*dpiScale;
int precision=inRect.Max.x-inRect.Min.x;
if (precision<1) precision=1;
if (precision>512) precision=512;
ImGui::ItemSize(size,style.FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) {
if (!e->isRunning()) {
for (unsigned short i=0; i<512; i++) {
float x=(float)i/512.0f;
for (unsigned short i=0; i<precision; i++) {
float x=(float)i/(float)precision;
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f));
}
} else {
@ -394,18 +399,19 @@ void FurnaceGUI::drawChanOsc() {
*/
needlePos-=displaySize;
for (unsigned short i=0; i<512; i++) {
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f;
for (unsigned short i=0; i<precision; i++) {
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f;
if (minLevel>y) minLevel=y;
if (maxLevel<y) maxLevel=y;
}
dcOff=(minLevel+maxLevel)*0.5f;
for (unsigned short i=0; i<512; i++) {
float x=(float)i/512.0f;
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f;
for (unsigned short i=0; i<precision; i++) {
float x=(float)i/(float)precision;
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f;
y-=dcOff;
if (y<-0.5f) y=-0.5f;
if (y>0.5f) y=0.5f;
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-(y-dcOff)));
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y));
}
}
ImU32 color=ImGui::GetColorU32(chanOscColor);
@ -418,7 +424,9 @@ void FurnaceGUI::drawChanOsc() {
color=chanOscGrad.get(xVal,1.0f-yVal);
}
dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale);
ImGui::PushClipRect(inRect.Min,inRect.Max,false);
dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale);
ImGui::PopClipRect();
}
}
}

View file

@ -180,6 +180,10 @@ void FurnaceGUI::drawCompatFlags() {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("behavior changed in 0.6pre4");
}
ImGui::Checkbox("Broken macros in some FM chips after note off",&e->song.brokenFMOff);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("behavior changed in 0.6pre5");
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(".mod import")) {

File diff suppressed because it is too large Load diff

View file

@ -380,7 +380,7 @@ void FurnaceGUI::drawDebug() {
}
ImGui::TreePop();
}
if (ImGui::TreeNode("Window Debug")) {
if (ImGui::TreeNodeEx("Window Debug",ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Text("Screen: %dx%d+%d+%d",scrW,scrH,scrX,scrY);
ImGui::Text("Screen (Conf): %dx%d+%d+%d",scrConfW,scrConfH,scrConfX,scrConfY);
ImGui::Text("Canvas: %dx%d",canvasW,canvasH);
@ -549,6 +549,7 @@ void FurnaceGUI::drawDebug() {
ImGui::Text("audio: %dµs",lastProcTime);
ImGui::Text("render: %.0fµs",(double)renderTimeDelta/perfFreq);
ImGui::Text("draw: %.0fµs",(double)drawTimeDelta/perfFreq);
ImGui::Text("layout: %.0fµs",(double)layoutTimeDelta/perfFreq);
ImGui::Text("event: %.0fµs",(double)eventTimeDelta/perfFreq);
ImGui::Separator();

View file

@ -561,17 +561,17 @@ void FurnaceGUI::doAction(int what) {
doFlip();
break;
case GUI_ACTION_PAT_COLLAPSE_ROWS:
doCollapse(2,selStart,selEnd);
doCollapse(collapseAmount,selStart,selEnd);
break;
case GUI_ACTION_PAT_EXPAND_ROWS:
doExpand(2,selStart,selEnd);
doExpand(collapseAmount,selStart,selEnd);
break;
case GUI_ACTION_PAT_COLLAPSE_PAT: {
SelectionPoint selEndPat;
selEndPat.xCoarse=e->getTotalChannelCount()-1;
selEndPat.xFine=2+e->curPat[selEndPat.xCoarse].effectCols*2;
selEndPat.y=e->curSubSong->patLen-1;
doCollapse(2,SelectionPoint(0,0,0),selEndPat);
doCollapse(collapseAmount,SelectionPoint(0,0,0),selEndPat);
break;
}
case GUI_ACTION_PAT_EXPAND_PAT: {
@ -579,14 +579,14 @@ void FurnaceGUI::doAction(int what) {
selEndPat.xCoarse=e->getTotalChannelCount()-1;
selEndPat.xFine=2+e->curPat[selEndPat.xCoarse].effectCols*2;
selEndPat.y=e->curSubSong->patLen-1;
doExpand(2,SelectionPoint(0,0,0),selEndPat);
doExpand(collapseAmount,SelectionPoint(0,0,0),selEndPat);
break;
}
case GUI_ACTION_PAT_COLLAPSE_SONG:
doCollapseSong(2);
doCollapseSong(collapseAmount);
break;
case GUI_ACTION_PAT_EXPAND_SONG:
doExpandSong(2);
doExpandSong(collapseAmount);
break;
case GUI_ACTION_PAT_LATCH: // TODO
break;
@ -684,6 +684,9 @@ void FurnaceGUI::doAction(int what) {
wavePreviewInit=true;
updateFMPreview=true;
break;
case GUI_ACTION_INS_LIST_DIR_VIEW:
insListDir=!insListDir;
break;
case GUI_ACTION_WAVE_LIST_ADD:
curWave=e->addWave();
@ -757,6 +760,9 @@ void FurnaceGUI::doAction(int what) {
if (++curWave>=(int)e->song.wave.size()) curWave=((int)e->song.wave.size())-1;
wantScrollList=true;
break;
case GUI_ACTION_WAVE_LIST_DIR_VIEW:
waveListDir=!waveListDir;
break;
case GUI_ACTION_SAMPLE_LIST_ADD:
curSample=e->addSample();
@ -785,6 +791,8 @@ void FurnaceGUI::doAction(int what) {
sample->loopEnd=prevSample->loopEnd;
sample->loop=prevSample->loop;
sample->loopMode=prevSample->loopMode;
sample->brrEmphasis=prevSample->brrEmphasis;
sample->dither=prevSample->dither;
sample->depth=prevSample->depth;
if (sample->init(prevSample->samples)) {
if (prevSample->getCurBuf()!=NULL) {
@ -815,6 +823,9 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_LIST_SAVE:
if (curSample>=0 && curSample<(int)e->song.sample.size()) openFileDialog(GUI_FILE_SAMPLE_SAVE);
break;
case GUI_ACTION_SAMPLE_LIST_SAVE_RAW:
if (curSample>=0 && curSample<(int)e->song.sample.size()) openFileDialog(GUI_FILE_SAMPLE_SAVE_RAW);
break;
case GUI_ACTION_SAMPLE_LIST_MOVE_UP:
if (e->moveSampleUp(curSample)) {
curSample--;
@ -835,8 +846,8 @@ void FurnaceGUI::doAction(int what) {
MARK_MODIFIED;
if (curSample>=(int)e->song.sample.size()) {
curSample--;
updateSampleTex=true;
}
updateSampleTex=true;
break;
case GUI_ACTION_SAMPLE_LIST_EDIT:
sampleEditOpen=true;
@ -857,6 +868,9 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW:
e->stopSamplePreview();
break;
case GUI_ACTION_SAMPLE_LIST_DIR_VIEW:
sampleListDir=!sampleListDir;
break;
case GUI_ACTION_SAMPLE_SELECT:
if (curSample<0 || curSample>=(int)e->song.sample.size()) break;

View file

@ -463,181 +463,185 @@ void FurnaceGUI::drawMobileControls() {
ImGui::Separator();
if (settings.unifiedDataView) {
drawInsList(true);
} else {
switch (mobScene) {
case GUI_SCENE_PATTERN:
case GUI_SCENE_ORDERS:
case GUI_SCENE_INSTRUMENT:
switch (mobScene) {
case GUI_SCENE_PATTERN:
case GUI_SCENE_ORDERS:
case GUI_SCENE_INSTRUMENT:
drawInsList(true);
break;
case GUI_SCENE_WAVETABLE:
if (settings.unifiedDataView) {
drawInsList(true);
break;
case GUI_SCENE_WAVETABLE:
} else {
drawWaveList(true);
break;
case GUI_SCENE_SAMPLE:
}
break;
case GUI_SCENE_SAMPLE:
if (settings.unifiedDataView) {
drawInsList(true);
} else {
drawSampleList(true);
break;
case GUI_SCENE_SONG: {
if (ImGui::Button("New")) {
mobileMenuOpen=false;
//doAction(GUI_ACTION_NEW);
if (modified) {
showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW);
} else {
displayNew=true;
}
}
ImGui::SameLine();
if (ImGui::Button("Open")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_OPEN);
}
ImGui::SameLine();
if (ImGui::Button("Save")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_SAVE);
}
ImGui::SameLine();
if (ImGui::Button("Save as...")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_SAVE_AS);
}
ImGui::Button("1.1+ .dmf");
ImGui::SameLine();
ImGui::Button("Legacy .dmf");
ImGui::SameLine();
if (ImGui::Button("Export Audio")) {
openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE);
}
ImGui::SameLine();
if (ImGui::Button("Export VGM")) {
openFileDialog(GUI_FILE_EXPORT_VGM);
}
if (ImGui::Button("CmdStream")) {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY);
}
ImGui::SameLine();
if (ImGui::Button("CmdStream Text")) {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM);
}
if (ImGui::Button("Restore Backup")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_OPEN_BACKUP);
}
ImGui::Separator();
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;
}
case GUI_SCENE_CHANNELS:
ImGui::Text("Channels here...");
break;
case GUI_SCENE_CHIPS:
ImGui::Text("Chips here...");
break;
case GUI_SCENE_MIXER:
ImGui::Text("What the hell...");
break;
case GUI_SCENE_OTHER: {
if (ImGui::Button("Osc")) {
oscOpen=!oscOpen;
break;
case GUI_SCENE_SONG: {
if (ImGui::Button("New")) {
mobileMenuOpen=false;
//doAction(GUI_ACTION_NEW);
if (modified) {
showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW);
} else {
displayNew=true;
}
ImGui::SameLine();
if (ImGui::Button("ChanOsc")) {
chanOscOpen=!chanOscOpen;
}
ImGui::SameLine();
if (ImGui::Button("RegView")) {
regViewOpen=!regViewOpen;
}
ImGui::SameLine();
if (ImGui::Button("Stats")) {
statsOpen=!statsOpen;
}
if (ImGui::Button("Compat Flags")) {
compatFlagsOpen=!compatFlagsOpen;
}
ImGui::Separator();
ImGui::Button("Panic");
ImGui::SameLine();
if (ImGui::Button("Settings")) {
mobileMenuOpen=false;
settingsOpen=true;
}
ImGui::SameLine();
if (ImGui::Button("Log")) {
logOpen=!logOpen;
}
ImGui::SameLine();
if (ImGui::Button("Debug")) {
debugOpen=!debugOpen;
}
ImGui::SameLine();
if (ImGui::Button("About")) {
mobileMenuOpen=false;
mobileMenuPos=0.0f;
aboutOpen=true;
}
if (ImGui::Button("Switch to Desktop Mode")) {
toggleMobileUI(!mobileUI);
}
int numAmiga=0;
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++;
}
if (numAmiga) {
ImGui::Text(
"this is NOT ROM export! only use for making sure the\n"
"Furnace Amiga emulator is working properly by\n"
"comparing it with real Amiga output."
);
ImGui::Text("Directory");
ImGui::SameLine();
ImGui::InputText("##AVDPath",&workingDirROMExport);
if (ImGui::Button("Bake Data")) {
std::vector<DivROMExportOutput> out=e->buildROM(DIV_ROM_AMIGA_VALIDATION);
if (workingDirROMExport.size()>0) {
if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR;
}
for (DivROMExportOutput& i: out) {
String path=workingDirROMExport+i.name;
FILE* outFile=ps_fopen(path.c_str(),"wb");
if (outFile!=NULL) {
fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile);
fclose(outFile);
}
i.data->finish();
delete i.data;
}
showError(fmt::sprintf("Done! Baked %d files.",(int)out.size()));
}
}
break;
}
ImGui::SameLine();
if (ImGui::Button("Open")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_OPEN);
}
ImGui::SameLine();
if (ImGui::Button("Save")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_SAVE);
}
ImGui::SameLine();
if (ImGui::Button("Save as...")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_SAVE_AS);
}
ImGui::Button("1.1+ .dmf");
ImGui::SameLine();
ImGui::Button("Legacy .dmf");
ImGui::SameLine();
if (ImGui::Button("Export Audio")) {
openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE);
}
ImGui::SameLine();
if (ImGui::Button("Export VGM")) {
openFileDialog(GUI_FILE_EXPORT_VGM);
}
if (ImGui::Button("CmdStream")) {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY);
}
ImGui::SameLine();
if (ImGui::Button("CmdStream Text")) {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM);
}
if (ImGui::Button("Restore Backup")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_OPEN_BACKUP);
}
ImGui::Separator();
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;
}
case GUI_SCENE_CHANNELS:
ImGui::Text("Channels here...");
break;
case GUI_SCENE_CHIPS:
ImGui::Text("Chips here...");
break;
case GUI_SCENE_MIXER:
ImGui::Text("What the hell...");
break;
case GUI_SCENE_OTHER: {
if (ImGui::Button("Osc")) {
oscOpen=!oscOpen;
}
ImGui::SameLine();
if (ImGui::Button("ChanOsc")) {
chanOscOpen=!chanOscOpen;
}
ImGui::SameLine();
if (ImGui::Button("RegView")) {
regViewOpen=!regViewOpen;
}
ImGui::SameLine();
if (ImGui::Button("Stats")) {
statsOpen=!statsOpen;
}
if (ImGui::Button("Compat Flags")) {
compatFlagsOpen=!compatFlagsOpen;
}
ImGui::Separator();
ImGui::Button("Panic");
ImGui::SameLine();
if (ImGui::Button("Settings")) {
mobileMenuOpen=false;
settingsOpen=true;
}
ImGui::SameLine();
if (ImGui::Button("Log")) {
logOpen=!logOpen;
}
ImGui::SameLine();
if (ImGui::Button("Debug")) {
debugOpen=!debugOpen;
}
ImGui::SameLine();
if (ImGui::Button("About")) {
mobileMenuOpen=false;
mobileMenuPos=0.0f;
aboutOpen=true;
}
if (ImGui::Button("Switch to Desktop Mode")) {
toggleMobileUI(!mobileUI);
}
int numAmiga=0;
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++;
}
if (numAmiga) {
ImGui::Text(
"this is NOT ROM export! only use for making sure the\n"
"Furnace Amiga emulator is working properly by\n"
"comparing it with real Amiga output."
);
ImGui::Text("Directory");
ImGui::SameLine();
ImGui::InputText("##AVDPath",&workingDirROMExport);
if (ImGui::Button("Bake Data")) {
std::vector<DivROMExportOutput> out=e->buildROM(DIV_ROM_AMIGA_VALIDATION);
if (workingDirROMExport.size()>0) {
if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR;
}
for (DivROMExportOutput& i: out) {
String path=workingDirROMExport+i.name;
FILE* outFile=ps_fopen(path.c_str(),"wb");
if (outFile!=NULL) {
fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile);
fclose(outFile);
}
i.data->finish();
delete i.data;
}
showError(fmt::sprintf("Done! Baked %d files.",(int)out.size()));
}
}
break;
}
}
}

View file

@ -245,14 +245,22 @@ void FurnaceGUI::doPullDelete() {
updateScroll(cursor.y);
}
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
SelectionPoint sStart=selStart;
SelectionPoint sEnd=selEnd;
if (selStart.xCoarse==selEnd.xCoarse && selStart.xFine==selEnd.xFine && selStart.y==selEnd.y && settings.pullDeleteRow) {
sStart.xFine=0;
sEnd.xFine=2+e->curPat[sEnd.xCoarse].effectCols*2;
}
int iCoarse=sStart.xCoarse;
int iFine=sStart.xFine;
for (; iCoarse<=sEnd.xCoarse; iCoarse++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); iFine++) {
maskOut(opMaskPullDelete,iFine);
for (int j=selStart.y; j<e->curSubSong->patLen; j++) {
for (int j=sStart.y; j<e->curSubSong->patLen; j++) {
if (j<e->curSubSong->patLen-1) {
if (iFine==0) {
pat->data[j][iFine]=pat->data[j+1][iFine];
@ -277,15 +285,23 @@ void FurnaceGUI::doInsert() {
prepareUndo(GUI_UNDO_PATTERN_PUSH);
curNibble=false;
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
SelectionPoint sStart=selStart;
SelectionPoint sEnd=selEnd;
if (selStart.xCoarse==selEnd.xCoarse && selStart.xFine==selEnd.xFine && selStart.y==selEnd.y && settings.insertBehavior) {
sStart.xFine=0;
sEnd.xFine=2+e->curPat[sEnd.xCoarse].effectCols*2;
}
int iCoarse=sStart.xCoarse;
int iFine=sStart.xFine;
for (; iCoarse<=sEnd.xCoarse; iCoarse++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); iFine++) {
maskOut(opMaskInsert,iFine);
for (int j=e->curSubSong->patLen-1; j>=selStart.y; j--) {
if (j==selStart.y) {
for (int j=e->curSubSong->patLen-1; j>=sStart.y; j--) {
if (j==sStart.y) {
if (iFine==0) {
pat->data[j][iFine]=0;
}

View file

@ -1,5 +1,6 @@
#include "fileDialog.h"
#include "ImGuiFileDialog.h"
#include "util.h"
#include "../ta-log.h"
#ifdef USE_NFD
@ -152,6 +153,7 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector<String> filter, c
ImGuiFileDialog::Instance()->singleClickSel=mobileUI;
ImGuiFileDialog::Instance()->DpiScale=dpiScale;
ImGuiFileDialog::Instance()->mobileMode=mobileUI;
ImGuiFileDialog::Instance()->homePath=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,allowMultiple?999:1,nullptr,0,clickCallback);
}
opened=true;
@ -235,6 +237,7 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector<String> filter, c
ImGuiFileDialog::Instance()->singleClickSel=false;
ImGuiFileDialog::Instance()->DpiScale=dpiScale;
ImGuiFileDialog::Instance()->mobileMode=mobileUI;
ImGuiFileDialog::Instance()->homePath=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite);
}
opened=true;

View file

@ -29,8 +29,6 @@
#include "../fileutils.h"
#include "imgui.h"
#include "imgui_internal.h"
#include "imgui_impl_sdl.h"
#include "imgui_impl_sdlrenderer.h"
#include "ImGuiFileDialog.h"
#include "IconsFontAwesome4.h"
#include "misc/cpp/imgui_stdlib.h"
@ -1726,6 +1724,16 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
dpiScale
);
break;
case GUI_FILE_SAMPLE_SAVE_RAW:
if (!dirExists(workingDirSample)) workingDirSample=getHomeDir();
hasOpened=fileDialog->openSave(
"Load Raw Sample",
{"all files", "*"},
".*",
workingDirSample,
dpiScale
);
break;
case GUI_FILE_EXPORT_AUDIO_ONE:
if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir();
hasOpened=fileDialog->openSave(
@ -1958,8 +1966,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
//ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard;
}
#define FURNACE_ZLIB_COMPRESS
int FurnaceGUI::save(String path, int dmfVersion) {
SafeWriter* w;
logD("saving file...");
@ -1967,7 +1973,7 @@ int FurnaceGUI::save(String path, int dmfVersion) {
if (dmfVersion<24) dmfVersion=24;
w=e->saveDMF(dmfVersion);
} else {
w=e->saveFur();
w=e->saveFur(false,settings.newPatternFormat);
}
if (w==NULL) {
lastError=e->getLastError();
@ -1982,35 +1988,56 @@ int FurnaceGUI::save(String path, int dmfVersion) {
w->finish();
return 1;
}
#ifdef FURNACE_ZLIB_COMPRESS
unsigned char zbuf[131072];
int ret;
z_stream zl;
memset(&zl,0,sizeof(z_stream));
ret=deflateInit(&zl,Z_DEFAULT_COMPRESSION);
if (ret!=Z_OK) {
logE("zlib error!");
lastError="compression error";
fclose(outFile);
w->finish();
return 2;
}
zl.avail_in=w->size();
zl.next_in=w->getFinalBuf();
while (zl.avail_in>0) {
if (settings.compress) {
unsigned char zbuf[131072];
int ret;
z_stream zl;
memset(&zl,0,sizeof(z_stream));
ret=deflateInit(&zl,Z_DEFAULT_COMPRESSION);
if (ret!=Z_OK) {
logE("zlib error!");
lastError="compression error";
fclose(outFile);
w->finish();
return 2;
}
zl.avail_in=w->size();
zl.next_in=w->getFinalBuf();
while (zl.avail_in>0) {
zl.avail_out=131072;
zl.next_out=zbuf;
if ((ret=deflate(&zl,Z_NO_FLUSH))==Z_STREAM_ERROR) {
logE("zlib stream error!");
lastError="zlib stream error";
deflateEnd(&zl);
fclose(outFile);
w->finish();
return 2;
}
size_t amount=131072-zl.avail_out;
if (amount>0) {
if (fwrite(zbuf,1,amount,outFile)!=amount) {
logE("did not write entirely: %s!",strerror(errno));
lastError=strerror(errno);
deflateEnd(&zl);
fclose(outFile);
w->finish();
return 1;
}
}
}
zl.avail_out=131072;
zl.next_out=zbuf;
if ((ret=deflate(&zl,Z_NO_FLUSH))==Z_STREAM_ERROR) {
logE("zlib stream error!");
lastError="zlib stream error";
if ((ret=deflate(&zl,Z_FINISH))==Z_STREAM_ERROR) {
logE("zlib finish stream error!");
lastError="zlib finish stream error";
deflateEnd(&zl);
fclose(outFile);
w->finish();
return 2;
}
size_t amount=131072-zl.avail_out;
if (amount>0) {
if (fwrite(zbuf,1,amount,outFile)!=amount) {
if (131072-zl.avail_out>0) {
if (fwrite(zbuf,1,131072-zl.avail_out,outFile)!=(131072-zl.avail_out)) {
logE("did not write entirely: %s!",strerror(errno));
lastError=strerror(errno);
deflateEnd(&zl);
@ -2019,37 +2046,16 @@ int FurnaceGUI::save(String path, int dmfVersion) {
return 1;
}
}
}
zl.avail_out=131072;
zl.next_out=zbuf;
if ((ret=deflate(&zl,Z_FINISH))==Z_STREAM_ERROR) {
logE("zlib finish stream error!");
lastError="zlib finish stream error";
deflateEnd(&zl);
fclose(outFile);
w->finish();
return 2;
}
if (131072-zl.avail_out>0) {
if (fwrite(zbuf,1,131072-zl.avail_out,outFile)!=(131072-zl.avail_out)) {
} else {
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
logE("did not write entirely: %s!",strerror(errno));
lastError=strerror(errno);
deflateEnd(&zl);
fclose(outFile);
w->finish();
return 1;
}
}
deflateEnd(&zl);
#else
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
logE("did not write entirely: %s!",strerror(errno));
lastError=strerror(errno);
fclose(outFile);
w->finish();
return 1;
}
#endif
fclose(outFile);
w->finish();
backupLock.lock();
@ -2262,6 +2268,11 @@ void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
displayExporting=true;
}
void FurnaceGUI::editStr(String* which) {
editString=which;
displayEditString=true;
}
void FurnaceGUI::showWarning(String what, FurnaceGUIWarnings type) {
warnString=what;
warnAction=type;
@ -2842,8 +2853,14 @@ void FurnaceGUI::editOptions(bool topMenu) {
ImGui::Separator();
if (ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip();
if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2,selStart,selEnd);
if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2,selStart,selEnd);
ImGui::SetNextItemWidth(120.0f*dpiScale);
if (ImGui::InputInt("collapse/expand amount##CollapseAmount",&collapseAmount,1,1)) {
if (collapseAmount<2) collapseAmount=2;
if (collapseAmount>256) collapseAmount=256;
}
if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(collapseAmount,selStart,selEnd);
if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(collapseAmount,selStart,selEnd);
if (topMenu) {
ImGui::Separator();
@ -2933,6 +2950,12 @@ int _processEvent(void* instance, SDL_Event* event) {
return ((FurnaceGUI*)instance)->processEvent(event);
}
#if SDL_VERSION_ATLEAST(2,0,17)
#define VALID_MODS KMOD_NUM|KMOD_CAPS|KMOD_SCROLL
#else
#define VALID_MODS KMOD_NUM|KMOD_CAPS
#endif
int FurnaceGUI::processEvent(SDL_Event* ev) {
if (introPos<11.0 && !shortIntro) return 1;
#ifdef IS_MOBILE
@ -2944,7 +2967,7 @@ int FurnaceGUI::processEvent(SDL_Event* ev) {
}
#endif
if (ev->type==SDL_KEYDOWN) {
if (!ev->key.repeat && latchTarget==0 && !wantCaptureKeyboard && !sampleMapWaitingInput && (ev->key.keysym.mod&(~(KMOD_NUM|KMOD_CAPS|KMOD_SCROLL)))==0) {
if (!ev->key.repeat && latchTarget==0 && !wantCaptureKeyboard && !sampleMapWaitingInput && (ev->key.keysym.mod&(~(VALID_MODS)))==0) {
if (settings.notePreviewBehavior==0) return 1;
switch (curWindow) {
case GUI_WINDOW_SAMPLE_EDIT:
@ -3397,6 +3420,7 @@ bool FurnaceGUI::loop() {
logV("portrait: %d (%dx%d)",portrait,scrW,scrH);
logD("window resized to %dx%d",scrW,scrH);
updateWindow=true;
rend->resized(ev);
break;
case SDL_WINDOWEVENT_MOVED:
scrX=ev.window.data1;
@ -3431,6 +3455,7 @@ bool FurnaceGUI::loop() {
break;
}
break;
#if SDL_VERSION_ATLEAST(2,0,17)
case SDL_DISPLAYEVENT: {
switch (ev.display.event) {
case SDL_DISPLAYEVENT_CONNECTED:
@ -3448,6 +3473,7 @@ bool FurnaceGUI::loop() {
}
break;
}
#endif
case SDL_KEYDOWN:
if (!ImGui::GetIO().WantCaptureKeyboard) {
keyDown(ev);
@ -3464,7 +3490,7 @@ bool FurnaceGUI::loop() {
int sampleCountBefore=e->song.sampleLen;
std::vector<DivInstrument*> instruments=e->instrumentFromFile(ev.drop.file);
DivWavetable* droppedWave=NULL;
DivSample* droppedSample=NULL;;
DivSample* droppedSample=NULL;
if (!instruments.empty()) {
if (e->song.sampleLen!=sampleCountBefore) {
e->renderSamplesP();
@ -3510,7 +3536,8 @@ bool FurnaceGUI::loop() {
// update config x/y/w/h values based on scrMax state
if (updateWindow) {
logV("updateWindow is true");
if (!scrMax) {
if (!scrMax && !fullScreen) {
logV("updating scrConf");
scrConfX=scrX;
scrConfY=scrY;
scrConfW=scrW;
@ -3518,8 +3545,8 @@ bool FurnaceGUI::loop() {
}
}
// update canvas size as well
if (SDL_GetRendererOutputSize(sdlRend,&canvasW,&canvasH)!=0) {
logW("loop: error while getting output size! %s",SDL_GetError());
if (!rend->getOutputSize(canvasW,canvasH)) {
logW("loop: error while getting output size!");
} else {
//logV("updateWindow: canvas size %dx%d",canvasW,canvasH);
// and therefore window size
@ -3740,9 +3767,13 @@ bool FurnaceGUI::loop() {
});
}
bool fontsFailed=false;
layoutTimeBegin=SDL_GetPerformanceCounter();
ImGui_ImplSDLRenderer_NewFrame();
if (!rend->newFrame()) {
fontsFailed=true;
}
ImGui_ImplSDL2_NewFrame(sdlWin);
ImGui::NewFrame();
@ -3768,6 +3799,7 @@ bool FurnaceGUI::loop() {
}
}
if (ImGui::BeginMenu("open recent")) {
exitDisabledTimer=1;
for (int i=0; i<(int)recentFile.size(); i++) {
String item=recentFile[i];
if (ImGui::MenuItem(item.c_str())) {
@ -3814,6 +3846,7 @@ bool FurnaceGUI::loop() {
}
ImGui::Separator();
if (ImGui::BeginMenu("export audio...")) {
exitDisabledTimer=1;
if (ImGui::MenuItem("one file")) {
openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE);
}
@ -3832,6 +3865,7 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("export VGM...")) {
exitDisabledTimer=1;
ImGui::Text("settings:");
if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) {
for (int i=0; i<7; i++) {
@ -3920,6 +3954,7 @@ bool FurnaceGUI::loop() {
}
if (numZSMCompat > 0) {
if (ImGui::BeginMenu("export ZSM...")) {
exitDisabledTimer=1;
ImGui::Text("Commander X16 Zsound Music File");
if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) {
if (zsmExportTickRate<1) zsmExportTickRate=1;
@ -3940,6 +3975,7 @@ bool FurnaceGUI::loop() {
}
if (numAmiga && settings.iCannotWait) {
if (ImGui::BeginMenu("export Amiga validation data...")) {
exitDisabledTimer=1;
ImGui::Text(
"this is NOT ROM export! only use for making sure the\n"
"Furnace Amiga emulator is working properly by\n"
@ -3970,6 +4006,7 @@ bool FurnaceGUI::loop() {
}
}
if (ImGui::BeginMenu("export command stream...")) {
exitDisabledTimer=1;
ImGui::Text(
"this option exports a text or binary file which\n"
"contains a dump of the internal command stream\n"
@ -3987,6 +4024,7 @@ bool FurnaceGUI::loop() {
}
ImGui::Separator();
if (ImGui::BeginMenu("add chip...")) {
exitDisabledTimer=1;
DivSystem picked=systemPicker();
if (picked!=DIV_SYSTEM_NULL) {
if (!e->addSystem(picked)) {
@ -4003,6 +4041,7 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("configure chip...")) {
exitDisabledTimer=1;
for (int i=0; i<e->song.systemLen; i++) {
if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) {
drawSysConf(i,e->song.system[i],e->song.systemFlags[i],true);
@ -4012,6 +4051,7 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("change chip...")) {
exitDisabledTimer=1;
ImGui::Checkbox("Preserve channel positions",&preserveChanPos);
for (int i=0; i<e->song.systemLen; i++) {
if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) {
@ -4031,6 +4071,7 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("remove chip...")) {
exitDisabledTimer=1;
ImGui::Checkbox("Preserve channel positions",&preserveChanPos);
for (int i=0; i<e->song.systemLen; i++) {
if (ImGui::MenuItem(fmt::sprintf("%d. %s##_SYSR%d",i+1,getSystemName(e->song.system[i]),i).c_str())) {
@ -4047,6 +4088,7 @@ bool FurnaceGUI::loop() {
}
ImGui::EndMenu();
}
ImGui::BeginDisabled(exitDisabledTimer);
ImGui::Separator();
if (ImGui::MenuItem("restore backup",BIND_FOR(GUI_ACTION_OPEN_BACKUP))) {
doAction(GUI_ACTION_OPEN_BACKUP);
@ -4059,7 +4101,10 @@ bool FurnaceGUI::loop() {
quit=true;
}
}
ImGui::EndDisabled();
ImGui::EndMenu();
} else {
exitDisabledTimer=0;
}
if (ImGui::BeginMenu("edit")) {
ImGui::Text("...");
@ -4310,6 +4355,7 @@ bool FurnaceGUI::loop() {
MEASURE(log,drawLog());
MEASURE(compatFlags,drawCompatFlags());
MEASURE(stats,drawStats());
MEASURE(chanOsc,drawChanOsc());
} else {
globalWinFlags=0;
ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0);
@ -4381,7 +4427,7 @@ bool FurnaceGUI::loop() {
portrait=(scrW<scrH);
logV("portrait: %d (%dx%d)",portrait,scrW,scrH);
SDL_GetRendererOutputSize(sdlRend,&canvasW,&canvasH);
rend->getOutputSize(canvasW,canvasH);
#endif
if (patternOpen) nextWindow=GUI_WINDOW_PATTERN;
#ifdef __APPLE__
@ -4447,6 +4493,7 @@ bool FurnaceGUI::loop() {
case GUI_FILE_SAMPLE_OPEN_REPLACE:
case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW:
case GUI_FILE_SAMPLE_SAVE:
case GUI_FILE_SAMPLE_SAVE_RAW:
workingDirSample=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_EXPORT_AUDIO_ONE:
@ -4714,7 +4761,16 @@ bool FurnaceGUI::loop() {
break;
case GUI_FILE_SAMPLE_SAVE:
if (curSample>=0 && curSample<(int)e->song.sample.size()) {
e->song.sample[curSample]->save(copyOfName.c_str());
if (!e->song.sample[curSample]->save(copyOfName.c_str())) {
showError("could not save sample! open Log Viewer for more information.");
}
}
break;
case GUI_FILE_SAMPLE_SAVE_RAW:
if (curSample>=0 && curSample<(int)e->song.sample.size()) {
if (!e->song.sample[curSample]->saveRaw(copyOfName.c_str())) {
showError("could not save sample! open Log Viewer for more information.");
}
}
break;
case GUI_FILE_EXPORT_AUDIO_ONE:
@ -5023,6 +5079,10 @@ bool FurnaceGUI::loop() {
ImGui::OpenPopup("New Song");
}
if (displayEditString) {
ImGui::OpenPopup("EditString");
}
if (nextWindow==GUI_WINDOW_ABOUT) {
aboutOpen=true;
nextWindow=GUI_WINDOW_NOTHING;
@ -5044,7 +5104,7 @@ bool FurnaceGUI::loop() {
ImGui::EndPopup();
}
//drawTutorial();
drawTutorial();
ImVec2 newSongMinSize=mobileUI?ImVec2(canvasW-(portrait?0:(60.0*dpiScale)),canvasH-60.0*dpiScale):ImVec2(400.0f*dpiScale,200.0f*dpiScale);
ImVec2 newSongMaxSize=ImVec2(canvasW-((mobileUI && !portrait)?(60.0*dpiScale):0),canvasH-(mobileUI?(60.0*dpiScale):0));
@ -5562,6 +5622,27 @@ bool FurnaceGUI::loop() {
ImGui::EndPopup();
}
if (ImGui::BeginPopup("EditString",ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) {
if (editString==NULL) {
ImGui::Text("Error! No string provided!");
} else {
if (displayEditString) {
ImGui::SetItemDefaultFocus();
ImGui::SetKeyboardFocusHere();
}
ImGui::InputText("##StringVal",editString);
}
displayEditString=false;
ImGui::SameLine();
if (ImGui::Button("OK") || ImGui::IsKeyPressed(ImGuiKey_Enter,false)) {
editString=NULL;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
} else {
editString=NULL;
}
MEASURE_END(popup);
if (!tutorial.introPlayed || settings.alwaysPlayIntro!=0) {
@ -5602,7 +5683,7 @@ bool FurnaceGUI::loop() {
}
}
logD("saving backup...");
SafeWriter* w=e->saveFur(true);
SafeWriter* w=e->saveFur(true,true);
logV("writing file...");
if (w!=NULL) {
@ -5724,33 +5805,35 @@ bool FurnaceGUI::loop() {
}
}
SDL_SetRenderDrawColor(sdlRend,uiColors[GUI_COLOR_BACKGROUND].x*255,
uiColors[GUI_COLOR_BACKGROUND].y*255,
uiColors[GUI_COLOR_BACKGROUND].z*255,
uiColors[GUI_COLOR_BACKGROUND].w*255);
SDL_RenderClear(sdlRend);
if (!settings.renderClearPos) {
rend->clear(uiColors[GUI_COLOR_BACKGROUND]);
}
renderTimeBegin=SDL_GetPerformanceCounter();
ImGui::Render();
renderTimeEnd=SDL_GetPerformanceCounter();
ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData());
drawTimeBegin=SDL_GetPerformanceCounter();
rend->renderGUI();
if (mustClear) {
SDL_RenderClear(sdlRend);
rend->clear(ImVec4(0,0,0,0));
mustClear--;
} else {
if (initialScreenWipe>0.0f && !settings.disableFadeIn) {
WAKE_UP;
initialScreenWipe-=ImGui::GetIO().DeltaTime*5.0f;
if (initialScreenWipe>0.0f) {
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(sdlRend,0,0,0,255*pow(initialScreenWipe,2.0f));
SDL_RenderFillRect(sdlRend,NULL);
rend->wipe(pow(initialScreenWipe,2.0f));
}
}
}
SDL_RenderPresent(sdlRend);
drawTimeEnd=SDL_GetPerformanceCounter();
rend->present();
if (settings.renderClearPos) {
rend->clear(uiColors[GUI_COLOR_BACKGROUND]);
}
layoutTimeDelta=layoutTimeEnd-layoutTimeBegin;
renderTimeDelta=renderTimeEnd-renderTimeBegin;
drawTimeDelta=drawTimeEnd-drawTimeBegin;
eventTimeDelta=eventTimeEnd-eventTimeBegin;
soloTimeout-=ImGui::GetIO().DeltaTime;
@ -5760,6 +5843,10 @@ bool FurnaceGUI::loop() {
WAKE_UP;
}
if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
exitDisabledTimer=0;
}
wheelX=0;
wheelY=0;
wantScrollList=false;
@ -5772,6 +5859,20 @@ bool FurnaceGUI::loop() {
willCommit=false;
}
if (fontsFailed) {
showError("it appears I couldn't load these fonts. any setting you can check?");
logE("couldn't load fonts");
ImGui::GetIO().Fonts->Clear();
mainFont=ImGui::GetIO().Fonts->AddFontDefault();
patFont=mainFont;
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error again while building font atlas!");
} else {
rend->createFontsTexture();
}
}
if (!editOptsVisible) {
latchTarget=0;
latchNibble=false;
@ -5846,6 +5947,10 @@ bool FurnaceGUI::init() {
basicMode=true;
}
insListDir=e->getConfBool("insListDir",false);
waveListDir=e->getConfBool("waveListDir",false);
sampleListDir=e->getConfBool("sampleListDir",false);
tempoView=e->getConfBool("tempoView",true);
waveHex=e->getConfBool("waveHex",false);
waveSigned=e->getConfBool("waveSigned",false);
@ -5880,6 +5985,7 @@ bool FurnaceGUI::init() {
pianoOptions=e->getConfBool("pianoOptions",pianoOptions);
pianoSharePosition=e->getConfBool("pianoSharePosition",pianoSharePosition);
pianoOptionsSet=e->getConfBool("pianoOptionsSet",pianoOptionsSet);
pianoReadonly=e->getConfBool("pianoReadonly",false);
pianoOffset=e->getConfInt("pianoOffset",pianoOffset);
pianoOffsetEdit=e->getConfInt("pianoOffsetEdit",pianoOffsetEdit);
pianoView=e->getConfInt("pianoView",pianoView);
@ -5920,7 +6026,9 @@ bool FurnaceGUI::init() {
e->setAutoNotePoly(noteInputPoly);
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER,"1");
#if SDL_VERSION_ATLEAST(2,0,17)
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS,"0");
#endif
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS,"0");
// don't disable compositing on KWin
#if SDL_VERSION_ATLEAST(2,0,22)
@ -5929,7 +6037,18 @@ bool FurnaceGUI::init() {
#endif
// initialize SDL
SDL_Init(SDL_INIT_VIDEO|SDL_INIT_HAPTIC);
logD("initializing video...");
if (SDL_Init(SDL_INIT_VIDEO)!=0) {
logE("could not initialize video! %s",SDL_GetError());
return false;
}
#ifdef IS_MOBILE
logD("initializing haptic...");
if (SDL_Init(SDL_INIT_HAPTIC)!=0) {
logW("could not initialize haptic! %s",SDL_GetError());
}
#endif
const char* videoBackend=SDL_GetCurrentVideoDriver();
if (videoBackend!=NULL) {
@ -6030,7 +6149,28 @@ bool FurnaceGUI::init() {
logV("window size: %dx%d",scrW,scrH);
sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW,scrH,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0));
if (!initRender()) {
if (settings.renderBackend!="SDL" && !settings.renderBackend.empty()) {
settings.renderBackend="";
e->setConf("renderBackend","");
e->saveConf();
lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace.");
} else {
lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError());
if (!settings.renderDriver.empty()) {
settings.renderDriver="";
e->setConf("renderDriver","");
e->saveConf();
lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace.");
}
}
return false;
}
rend->preInit();
logD("creating window...");
sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW,scrH,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0)|rend->getWindowFlags());
if (sdlWin==NULL) {
lastError=fmt::sprintf("could not open window! %s",SDL_GetError());
return false;
@ -6094,22 +6234,28 @@ bool FurnaceGUI::init() {
SDL_SetHint(SDL_HINT_RENDER_DRIVER,settings.renderDriver.c_str());
}
sdlRend=SDL_CreateRenderer(sdlWin,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE);
if (sdlRend==NULL) {
lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError());
if (!settings.renderDriver.empty()) {
settings.renderDriver="";
e->setConf("renderDriver","");
e->saveConf();
lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace.");
logD("starting render backend...");
if (!rend->init(sdlWin)) {
if (settings.renderBackend!="SDL" && !settings.renderBackend.empty()) {
settings.renderBackend="";
//e->setConf("renderBackend","");
//e->saveConf();
//lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace.");
} else {
lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError());
if (!settings.renderDriver.empty()) {
settings.renderDriver="";
e->setConf("renderDriver","");
e->saveConf();
lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace.");
}
}
return false;
}
// try acquiring the canvas size
if (SDL_GetRendererOutputSize(sdlRend,&canvasW,&canvasH)!=0) {
logW("could not get renderer output size! %s",SDL_GetError());
if (!rend->getOutputSize(canvasW,canvasH)) {
logW("could not get renderer output size!");
} else {
logV("canvas size: %dx%d",canvasW,canvasH);
}
@ -6117,30 +6263,45 @@ bool FurnaceGUI::init() {
// special consideration for Wayland
if (settings.dpiScale<0.5f) {
if (strcmp(videoBackend,"wayland")==0) {
dpiScale=(double)canvasW/(double)scrW;
int realW=scrW;
int realH=scrH;
SDL_GetWindowSize(sdlWin,&realW,&realH);
if (realW<1) {
logW("screen width is zero!\n");
dpiScale=1.0;
} else {
dpiScale=(double)canvasW/(double)realW;
logV("we're on Wayland... scaling factor: %f",dpiScale);
}
}
}
IMGUI_CHECKVERSION();
ImGui::CreateContext();
updateWindowTitle();
ImGui_ImplSDL2_InitForSDLRenderer(sdlWin,sdlRend);
ImGui_ImplSDLRenderer_Init(sdlRend);
rend->clear(ImVec4(0.0,0.0,0.0,1.0));
rend->present();
logD("preparing user interface...");
rend->initGUI(sdlWin);
applyUISettings();
logD("building font...");
if (!ImGui::GetIO().Fonts->Build()) {
logE("error while building font atlas!");
showError("error while loading fonts! please check your settings.");
ImGui::GetIO().Fonts->Clear();
mainFont=ImGui::GetIO().Fonts->AddFontDefault();
patFont=mainFont;
ImGui_ImplSDLRenderer_DestroyFontsTexture();
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error again while building font atlas!");
}
}
logD("preparing layout...");
strncpy(finalLayoutPath,(e->getConfigPath()+String(LAYOUT_INI)).c_str(),4095);
backupPath=e->getConfigPath();
if (backupPath.size()>0) {
@ -6152,8 +6313,6 @@ bool FurnaceGUI::init() {
ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_DockingEnable;
toggleMobileUI(mobileUI,true);
updateWindowTitle();
for (int i=0; i<DIV_MAX_CHANS; i++) {
oldPat[i]=new DivPattern;
}
@ -6200,6 +6359,7 @@ bool FurnaceGUI::init() {
return curIns;
});
#ifdef IS_MOBILE
vibrator=SDL_HapticOpen(0);
if (vibrator==NULL) {
logD("could not open vibration device: %s",SDL_GetError());
@ -6210,7 +6370,9 @@ bool FurnaceGUI::init() {
logD("vibration not available: %s",SDL_GetError());
}
}
#endif
logI("done!");
return true;
}
@ -6273,6 +6435,11 @@ void FurnaceGUI::commitState() {
e->setConf("spoilerOpen",spoilerOpen);
e->setConf("basicMode",basicMode);
// commit dir state
e->setConf("insListDir",insListDir);
e->setConf("waveListDir",waveListDir);
e->setConf("sampleListDir",sampleListDir);
// commit last window size
e->setConf("lastWindowWidth",scrConfW);
e->setConf("lastWindowHeight",scrConfH);
@ -6310,6 +6477,7 @@ void FurnaceGUI::commitState() {
e->setConf("pianoOptions",pianoOptions);
e->setConf("pianoSharePosition",pianoSharePosition);
e->setConf("pianoOptionsSet",pianoOptionsSet);
e->setConf("pianoReadonly",pianoReadonly);
e->setConf("pianoOffset",pianoOffset);
e->setConf("pianoOffsetEdit",pianoOffsetEdit);
e->setConf("pianoView",pianoView);
@ -6342,10 +6510,10 @@ void FurnaceGUI::commitState() {
bool FurnaceGUI::finish() {
commitState();
ImGui_ImplSDLRenderer_Shutdown();
rend->quitGUI();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_DestroyRenderer(sdlRend);
quitRender();
SDL_DestroyWindow(sdlWin);
if (vibrator) {
@ -6365,8 +6533,9 @@ bool FurnaceGUI::finish() {
FurnaceGUI::FurnaceGUI():
e(NULL),
renderBackend(GUI_BACKEND_SDL),
rend(NULL),
sdlWin(NULL),
sdlRend(NULL),
vibrator(NULL),
vibratorAvailable(false),
sampleTex(NULL),
@ -6401,6 +6570,7 @@ FurnaceGUI::FurnaceGUI():
displayPendingRawSample(false),
snesFilterHex(false),
modTableHex(false),
displayEditString(false),
mobileEdit(false),
vgmExportVersion(0x171),
vgmExportTrailingTicks(-1),
@ -6420,6 +6590,7 @@ FurnaceGUI::FurnaceGUI():
fmPreviewOn(false),
fmPreviewPaused(false),
fmPreviewOPN(NULL),
editString(NULL),
pendingRawSampleDepth(8),
pendingRawSampleChannels(1),
pendingRawSampleUnsigned(false),
@ -6474,7 +6645,6 @@ FurnaceGUI::FurnaceGUI():
loopEnd(-1),
isClipping(0),
extraChannelButtons(0),
patNameTarget(-1),
newSongCategory(0),
latchTarget(0),
wheelX(0),
@ -6486,6 +6656,7 @@ FurnaceGUI::FurnaceGUI():
oldBeat(-1),
oldBar(-1),
curGroove(-1),
exitDisabledTimer(0),
soloTimeout(0.0f),
exportFadeOut(5.0),
editControlsOpen(true),
@ -6524,6 +6695,10 @@ FurnaceGUI::FurnaceGUI():
groovesOpen(false),
introMonOpen(false),
basicMode(true),
shortIntro(false),
insListDir(false),
waveListDir(false),
sampleListDir(false),
clockShowReal(true),
clockShowRow(true),
clockShowBeat(true),
@ -6541,7 +6716,6 @@ FurnaceGUI::FurnaceGUI():
collapseWindow(false),
demandScrollX(false),
fancyPattern(false),
wantPatName(false),
firstFrame(true),
tempoView(true),
waveHex(false),
@ -6558,6 +6732,7 @@ FurnaceGUI::FurnaceGUI():
dragMobileMenu(false),
dragMobileEditButton(false),
wantGrooveListFocus(false),
lastAssetType(0),
curWindow(GUI_WINDOW_NOTHING),
nextWindow(GUI_WINDOW_NOTHING),
curWindowLast(GUI_WINDOW_NOTHING),
@ -6573,6 +6748,7 @@ FurnaceGUI::FurnaceGUI():
wavePreviewLen(32),
wavePreviewHeight(255),
wavePreviewInit(true),
wavePreviewPaused(false),
pgSys(0),
pgAddr(0),
pgVal(0),
@ -6662,6 +6838,9 @@ FurnaceGUI::FurnaceGUI():
renderTimeBegin(0),
renderTimeEnd(0),
renderTimeDelta(0),
drawTimeBegin(0),
drawTimeEnd(0),
drawTimeDelta(0),
eventTimeBegin(0),
eventTimeEnd(0),
eventTimeDelta(0),
@ -6670,11 +6849,14 @@ FurnaceGUI::FurnaceGUI():
sysToMove(-1),
sysToDelete(-1),
opToMove(-1),
assetToMove(-1),
dirToMove(-1),
transposeAmount(0),
randomizeMin(0),
randomizeMax(255),
fadeMin(0),
fadeMax(255),
collapseAmount(2),
scaleMax(100.0f),
fadeMode(false),
randomMode(false),
@ -6698,6 +6880,7 @@ FurnaceGUI::FurnaceGUI():
sampleDragMode(false),
sampleDrag16(false),
sampleZoomAuto(true),
sampleSelTarget(0),
sampleDragTarget(NULL),
sampleDragStart(0,0),
sampleDragAreaSize(0,0),
@ -6746,6 +6929,7 @@ FurnaceGUI::FurnaceGUI():
pianoOptions(true),
pianoSharePosition(false),
pianoOptionsSet(false),
pianoReadonly(false),
pianoOffset(6),
pianoOffsetEdit(9),
pianoView(PIANO_LAYOUT_AUTOMATIC),
@ -6755,6 +6939,7 @@ FurnaceGUI::FurnaceGUI():
pianoOctavesEdit(4),
pianoOptions(false),
pianoSharePosition(true),
pianoReadonly(false),
pianoOffset(6),
pianoOffsetEdit(6),
pianoView(PIANO_LAYOUT_STANDARD),
@ -6779,6 +6964,7 @@ FurnaceGUI::FurnaceGUI():
mustClear(2),
initialScreenWipe(1.0f),
introSkipDo(false),
introStopped(false),
curTutorial(-1),
curTutorialStep(0) {
// value keys

View file

@ -23,8 +23,7 @@
#include "../engine/engine.h"
#include "../engine/waveSynth.h"
#include "imgui.h"
#include "imgui_impl_sdl.h"
#include "imgui_impl_sdlrenderer.h"
#include "imgui_impl_sdl2.h"
#include <SDL.h>
#include <fftw3.h>
#include <deque>
@ -70,19 +69,59 @@
#define FM_PREVIEW_SIZE 512
enum FurnaceGUIRenderBackend {
GUI_BACKEND_SDL=0,
GUI_BACKEND_GL,
GUI_BACKEND_DX11
};
#ifdef HAVE_RENDER_SDL
#define GUI_BACKEND_DEFAULT GUI_BACKEND_SDL
#define GUI_BACKEND_DEFAULT_NAME "SDL"
#else
#ifdef HAVE_RENDER_DX11
#define GUI_BACKEND_DEFAULT GUI_BACKEND_DX11
#define GUI_BACKEND_DEFAULT_NAME "DirectX 11"
#else
#define GUI_BACKEND_DEFAULT GUI_BACKEND_GL
#define GUI_BACKEND_DEFAULT_NAME "OpenGL"
#endif
#endif
// TODO:
// - add colors for FM envelope and waveform
// - maybe add "alternate" color for FM modulators/carriers (a bit difficult)
enum FurnaceGUIColors {
GUI_COLOR_BACKGROUND=0,
GUI_COLOR_FRAME_BACKGROUND,
GUI_COLOR_FRAME_BACKGROUND_CHILD,
GUI_COLOR_FRAME_BACKGROUND_POPUP,
GUI_COLOR_MODAL_BACKDROP,
GUI_COLOR_HEADER,
GUI_COLOR_TEXT,
GUI_COLOR_ACCENT_PRIMARY,
GUI_COLOR_ACCENT_SECONDARY,
GUI_COLOR_TITLE_INACTIVE,
GUI_COLOR_TITLE_COLLAPSED,
GUI_COLOR_MENU_BAR,
GUI_COLOR_BORDER,
GUI_COLOR_BORDER_SHADOW,
GUI_COLOR_SCROLL_BACKGROUND,
GUI_COLOR_SCROLL,
GUI_COLOR_SCROLL_HOVER,
GUI_COLOR_SCROLL_ACTIVE,
GUI_COLOR_SEPARATOR,
GUI_COLOR_SEPARATOR_HOVER,
GUI_COLOR_SEPARATOR_ACTIVE,
GUI_COLOR_DOCKING_PREVIEW,
GUI_COLOR_DOCKING_EMPTY,
GUI_COLOR_TABLE_HEADER,
GUI_COLOR_TABLE_BORDER_HARD,
GUI_COLOR_TABLE_BORDER_SOFT,
GUI_COLOR_DRAG_DROP_TARGET,
GUI_COLOR_NAV_HIGHLIGHT,
GUI_COLOR_NAV_WIN_HIGHLIGHT,
GUI_COLOR_NAV_WIN_BACKDROP,
GUI_COLOR_TOGGLE_OFF,
GUI_COLOR_TOGGLE_ON,
GUI_COLOR_EDITING,
@ -366,6 +405,7 @@ enum FurnaceGUIFileDialogs {
GUI_FILE_SAMPLE_OPEN_REPLACE,
GUI_FILE_SAMPLE_OPEN_REPLACE_RAW,
GUI_FILE_SAMPLE_SAVE,
GUI_FILE_SAMPLE_SAVE_RAW,
GUI_FILE_EXPORT_AUDIO_ONE,
GUI_FILE_EXPORT_AUDIO_PER_SYS,
GUI_FILE_EXPORT_AUDIO_PER_CHANNEL,
@ -566,6 +606,7 @@ enum FurnaceGUIActions {
GUI_ACTION_INS_LIST_EDIT,
GUI_ACTION_INS_LIST_UP,
GUI_ACTION_INS_LIST_DOWN,
GUI_ACTION_INS_LIST_DIR_VIEW,
GUI_ACTION_INS_LIST_MAX,
GUI_ACTION_WAVE_LIST_MIN,
@ -582,6 +623,7 @@ enum FurnaceGUIActions {
GUI_ACTION_WAVE_LIST_EDIT,
GUI_ACTION_WAVE_LIST_UP,
GUI_ACTION_WAVE_LIST_DOWN,
GUI_ACTION_WAVE_LIST_DIR_VIEW,
GUI_ACTION_WAVE_LIST_MAX,
GUI_ACTION_SAMPLE_LIST_MIN,
@ -592,6 +634,7 @@ enum FurnaceGUIActions {
GUI_ACTION_SAMPLE_LIST_OPEN_RAW,
GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE_RAW,
GUI_ACTION_SAMPLE_LIST_SAVE,
GUI_ACTION_SAMPLE_LIST_SAVE_RAW,
GUI_ACTION_SAMPLE_LIST_MOVE_UP,
GUI_ACTION_SAMPLE_LIST_MOVE_DOWN,
GUI_ACTION_SAMPLE_LIST_DELETE,
@ -600,6 +643,7 @@ enum FurnaceGUIActions {
GUI_ACTION_SAMPLE_LIST_DOWN,
GUI_ACTION_SAMPLE_LIST_PREVIEW,
GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW,
GUI_ACTION_SAMPLE_LIST_DIR_VIEW,
GUI_ACTION_SAMPLE_LIST_MAX,
GUI_ACTION_SAMPLE_MIN,
@ -1166,7 +1210,7 @@ struct FurnaceGUIQueryResult {
struct FurnaceGUIImage {
unsigned char* data;
SDL_Texture* tex;
void* tex;
int width, height, ch;
FurnaceGUIImage():
@ -1188,15 +1232,52 @@ struct FurnaceGUIPerfMetric {
elapsed(0) {}
};
enum FurnaceGUIBlendMode {
GUI_BLEND_MODE_NONE=0,
GUI_BLEND_MODE_BLEND,
GUI_BLEND_MODE_ADD,
GUI_BLEND_MODE_MULTIPLY
};
class FurnaceGUIRender {
public:
virtual ImTextureID getTextureID(void* which);
virtual bool lockTexture(void* which, void** data, int* pitch);
virtual bool unlockTexture(void* which);
virtual bool updateTexture(void* which, void* data, int pitch);
virtual void* createTexture(bool dynamic, int width, int height);
virtual bool destroyTexture(void* which);
virtual void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode);
virtual void setBlendMode(FurnaceGUIBlendMode mode);
virtual void resized(const SDL_Event& ev);
virtual void clear(ImVec4 color);
virtual bool newFrame();
virtual void createFontsTexture();
virtual void destroyFontsTexture();
virtual void renderGUI();
virtual void wipe(float alpha);
virtual void present();
virtual bool getOutputSize(int& w, int& h);
virtual int getWindowFlags();
virtual void preInit();
virtual bool init(SDL_Window* win);
virtual void initGUI(SDL_Window* win);
virtual void quitGUI();
virtual bool quit();
virtual ~FurnaceGUIRender();
};
class FurnaceGUI {
DivEngine* e;
FurnaceGUIRenderBackend renderBackend;
FurnaceGUIRender* rend;
SDL_Window* sdlWin;
SDL_Renderer* sdlRend;
SDL_Haptic* vibrator;
bool vibratorAvailable;
SDL_Texture* sampleTex;
void* sampleTex;
int sampleTexW, sampleTexH;
bool updateSampleTex;
@ -1206,6 +1287,7 @@ class FurnaceGUI {
String workingDirLayout, workingDirROM, workingDirTest;
String mmlString[32];
String mmlStringW, mmlStringSNES, grooveString, grooveListString, mmlStringModTable;
String folderString;
std::vector<DivSystem> sysSearchResults;
std::vector<FurnaceGUISysDef> newSongSearchResults;
@ -1218,7 +1300,7 @@ class FurnaceGUI {
bool portrait, injectBackUp, mobileMenuOpen;
bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly;
bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex;
bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString;
bool mobileEdit;
bool willExport[DIV_MAX_CHIPS];
int vgmExportVersion;
@ -1236,6 +1318,7 @@ class FurnaceGUI {
short fmPreview[FM_PREVIEW_SIZE];
bool updateFMPreview, fmPreviewOn, fmPreviewPaused;
void* fmPreviewOPN;
String* editString;
String pendingRawSample;
int pendingRawSampleDepth, pendingRawSampleChannels;
@ -1401,8 +1484,10 @@ class FurnaceGUI {
int channelFont;
int channelTextCenter;
int midiOutClock;
int midiOutTime;
int midiOutProgramChange;
int midiOutMode;
int midiOutTimeRate;
int maxRecentFile;
int centerPattern;
int ordersCursor;
@ -1416,6 +1501,11 @@ class FurnaceGUI {
int alwaysPlayIntro;
int iCannotWait;
int orderButtonPos;
int compress;
int newPatternFormat;
int renderClearPos;
int insertBehavior;
int pullDeleteRow;
unsigned int maxUndoSteps;
String mainFontPath;
String patFontPath;
@ -1423,6 +1513,7 @@ class FurnaceGUI {
String midiInDevice;
String midiOutDevice;
String c163Name;
String renderBackend;
String renderDriver;
String initialSysName;
String noteOffLabel;
@ -1544,8 +1635,10 @@ class FurnaceGUI {
channelFont(1),
channelTextCenter(1),
midiOutClock(0),
midiOutTime(0),
midiOutProgramChange(0),
midiOutMode(1),
midiOutTimeRate(0),
maxRecentFile(10),
centerPattern(0),
ordersCursor(1),
@ -1559,6 +1652,11 @@ class FurnaceGUI {
alwaysPlayIntro(0),
iCannotWait(0),
orderButtonPos(2),
compress(1),
newPatternFormat(1),
renderClearPos(0),
insertBehavior(1),
pullDeleteRow(1),
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),
@ -1566,6 +1664,7 @@ class FurnaceGUI {
midiInDevice(""),
midiOutDevice(""),
c163Name(""),
renderBackend(""),
renderDriver(""),
initialSysName("Sega Genesis/Mega Drive"),
noteOffLabel("OFF"),
@ -1578,12 +1677,12 @@ class FurnaceGUI {
struct Tutorial {
int userComesFrom;
bool introPlayed;
bool welcome;
bool protoWelcome;
bool taken[GUI_TUTORIAL_MAX];
Tutorial():
userComesFrom(0),
introPlayed(false),
welcome(false) {
protoWelcome(false) {
memset(taken,0,GUI_TUTORIAL_MAX*sizeof(bool));
}
} tutorial;
@ -1593,9 +1692,9 @@ class FurnaceGUI {
DivInstrument* prevInsData;
int curIns, curWave, curSample, curOctave, curOrder, prevIns, oldRow, oldOrder, oldOrder1, editStep, exportLoops, soloChan,orderEditMode, orderCursor;
int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory, latchTarget;
int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, newSongCategory, latchTarget;
int wheelX, wheelY, dragSourceX, dragSourceXFine, dragSourceY, dragDestinationX, dragDestinationXFine, dragDestinationY, oldBeat, oldBar;
int curGroove;
int curGroove, exitDisabledTimer;
float soloTimeout;
double exportFadeOut;
@ -1609,14 +1708,16 @@ class FurnaceGUI {
bool groovesOpen, introMonOpen;
bool basicMode, shortIntro;
bool insListDir, waveListDir, sampleListDir;
bool clockShowReal, clockShowRow, clockShowBeat, clockShowMetro, clockShowTime;
float clockMetroTick[16];
SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd;
bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI;
bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble;
bool collapseWindow, demandScrollX, fancyPattern, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble;
bool keepLoopAlive, keepGrooveAlive, orderScrollLocked, orderScrollTolerance, dragMobileMenu, dragMobileEditButton, wantGrooveListFocus;
unsigned char lastAssetType;
FurnaceGUIWindows curWindow, nextWindow, curWindowLast;
std::atomic<FurnaceGUIWindows> curWindowThreadSafe;
float peak[DIV_MAX_OUTPUTS];
@ -1634,7 +1735,7 @@ class FurnaceGUI {
DivWaveSynth wavePreview;
int wavePreviewLen, wavePreviewHeight;
bool wavePreviewInit;
bool wavePreviewInit, wavePreviewPaused;
// bit 31: ctrl
// bit 30: reserved for SDL scancode mask
@ -1771,6 +1872,7 @@ class FurnaceGUI {
int layoutTimeBegin, layoutTimeEnd, layoutTimeDelta;
int renderTimeBegin, renderTimeEnd, renderTimeDelta;
int drawTimeBegin, drawTimeEnd, drawTimeDelta;
int eventTimeBegin, eventTimeEnd, eventTimeDelta;
FurnaceGUIPerfMetric perfMetrics[64];
@ -1782,6 +1884,7 @@ class FurnaceGUI {
std::map<FurnaceGUIImages,FurnaceGUIImage*> images;
int chanToMove, sysToMove, sysToDelete, opToMove;
int assetToMove, dirToMove;
ImVec2 patWindowPos, patWindowSize;
@ -1790,7 +1893,7 @@ class FurnaceGUI {
ImVec2 noteCellSize, insCellSize, volCellSize, effectCellSize, effectValCellSize;
SelectionPoint sel1, sel2;
int dummyRows, demandX;
int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax;
int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax, collapseAmount;
float scaleMax;
bool fadeMode, randomMode, haveHitBounds, pendingStepUpdate;
@ -1812,6 +1915,9 @@ class FurnaceGUI {
int sampleSelStart, sampleSelEnd;
bool sampleInfo, sampleCompatRate;
bool sampleDragActive, sampleDragMode, sampleDrag16, sampleZoomAuto;
// 0: start
// 1: end
unsigned char sampleSelTarget;
void* sampleDragTarget;
ImVec2 sampleDragStart;
ImVec2 sampleDragAreaSize;
@ -1846,7 +1952,7 @@ class FurnaceGUI {
bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad;
ImVec4 chanOscColor;
Gradient2D chanOscGrad;
SDL_Texture* chanOscGradTex;
void* chanOscGradTex;
float chanOscLP0[DIV_MAX_CHANS];
float chanOscLP1[DIV_MAX_CHANS];
float chanOscVol[DIV_MAX_CHANS];
@ -1898,6 +2004,7 @@ class FurnaceGUI {
bool pianoOptions, pianoSharePosition, pianoOptionsSet;
float pianoKeyHit[180];
bool pianoKeyPressed[180];
bool pianoReadonly;
int pianoOffset, pianoOffsetEdit;
int pianoView, pianoInputPadMode;
@ -1932,7 +2039,7 @@ class FurnaceGUI {
double monitorPos;
int mustClear;
float initialScreenWipe;
bool introSkipDo;
bool introSkipDo, introStopped;
ImVec2 introMin, introMax;
// tutorial
@ -1987,6 +2094,10 @@ class FurnaceGUI {
void actualWaveList();
void actualSampleList();
void insListItem(int index, int dir, int asset);
void waveListItem(int index, float* wavePreview, int dir, int asset);
void sampleListItem(int index, int dir, int asset);
void toggleMobileUI(bool enable, bool force=false);
void pushToggleColors(bool status);
@ -1995,7 +2106,7 @@ class FurnaceGUI {
void highlightWindow(const char* winName);
FurnaceGUIImage* getImage(FurnaceGUIImages image);
SDL_Texture* getTexture(FurnaceGUIImages image, SDL_BlendMode blendMode=SDL_BLENDMODE_BLEND);
void* getTexture(FurnaceGUIImages image, FurnaceGUIBlendMode blendMode=GUI_BLEND_MODE_BLEND);
void drawImage(ImDrawList* dl, FurnaceGUIImages image, const ImVec2& pos, const ImVec2& scale, double rotate, const ImVec2& uvMin, const ImVec2& uvMax, const ImVec4& imgColor);
void drawMobileControls();
@ -2144,9 +2255,13 @@ class FurnaceGUI {
String encodeKeyMap(std::map<int,int>& map);
void decodeKeyMap(std::map<int,int>& map, String source);
bool initRender();
bool quitRender();
const char* getSystemName(DivSystem which);
public:
void editStr(String* which);
void showWarning(String what, FurnaceGUIWarnings type);
void showError(String what);
String getLastError();

View file

@ -507,7 +507,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("WINDOW_ABOUT", "About", 0),
D("WINDOW_SETTINGS", "Settings", 0),
D("WINDOW_MIXER", "Mixer", 0),
D("WINDOW_DEBUG", "Debug Menu", 0),
D("WINDOW_DEBUG", "Debug Menu", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_d),
D("WINDOW_OSCILLOSCOPE", "Oscilloscope (master)", 0),
D("WINDOW_VOL_METER", "Volume Meter", 0),
D("WINDOW_STATS", "Statistics", 0),
@ -611,6 +611,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("INS_LIST_EDIT", "Edit", FURKMOD_SHIFT|SDLK_RETURN),
D("INS_LIST_UP", "Cursor up", SDLK_UP),
D("INS_LIST_DOWN", "Cursor down", SDLK_DOWN),
D("INS_LIST_DIR_VIEW", "Toggle folders/standard view", FURKMOD_CMD|SDLK_v),
D("INS_LIST_MAX", "", NOT_AN_ACTION),
D("WAVE_LIST_MIN", "---Wavetable list", NOT_AN_ACTION),
@ -627,6 +628,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("WAVE_LIST_EDIT", "Edit", FURKMOD_SHIFT|SDLK_RETURN),
D("WAVE_LIST_UP", "Cursor up", SDLK_UP),
D("WAVE_LIST_DOWN", "Cursor down", SDLK_DOWN),
D("WAVE_LIST_DIR_VIEW", "Toggle folders/standard view", FURKMOD_CMD|SDLK_v),
D("WAVE_LIST_MAX", "", NOT_AN_ACTION),
D("SAMPLE_LIST_MIN", "---Sample list", NOT_AN_ACTION),
@ -637,6 +639,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("SAMPLE_LIST_OPEN_RAW", "Import raw data", 0),
D("SAMPLE_LIST_OPEN_REPLACE_RAW", "Import raw data (replace current)", 0),
D("SAMPLE_LIST_SAVE", "Save", 0),
D("SAMPLE_LIST_SAVE_RAW", "Save (raw)", 0),
D("SAMPLE_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP),
D("SAMPLE_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN),
D("SAMPLE_LIST_DELETE", "Delete", 0),
@ -645,6 +648,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("SAMPLE_LIST_DOWN", "Cursor down", SDLK_DOWN),
D("SAMPLE_LIST_PREVIEW", "Preview", 0),
D("SAMPLE_LIST_STOP_PREVIEW", "Stop preview", 0),
D("SAMPLE_LIST_DIR_VIEW", "Toggle folders/standard view", FURKMOD_CMD|SDLK_v),
D("SAMPLE_LIST_MAX", "", NOT_AN_ACTION),
D("SAMPLE_MIN", "---Sample editor", NOT_AN_ACTION),
@ -707,13 +711,34 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={
D(GUI_COLOR_BACKGROUND,"Background",ImVec4(0.1f,0.1f,0.1f,1.0f)),
D(GUI_COLOR_FRAME_BACKGROUND,"",ImVec4(0.0f,0.0f,0.0f,0.85f)),
D(GUI_COLOR_FRAME_BACKGROUND_CHILD,"",ImVec4(0.0f,0.0f,0.0f,0.0f)),
D(GUI_COLOR_FRAME_BACKGROUND_POPUP,"",ImVec4(0.08f,0.08f,0.08f,0.94f)),
D(GUI_COLOR_MODAL_BACKDROP,"",ImVec4(0.0f,0.0f,0.0f,0.55f)),
D(GUI_COLOR_HEADER,"",ImVec4(0.2f,0.2f,0.2f,1.0f)),
D(GUI_COLOR_TEXT,"",ImVec4(1.0f,1.0f,1.0f,1.0f)),
D(GUI_COLOR_ACCENT_PRIMARY,"",ImVec4(0.06f,0.53f,0.98f,1.0f)),
D(GUI_COLOR_ACCENT_SECONDARY,"",ImVec4(0.26f,0.59f,0.98f,1.0f)),
D(GUI_COLOR_TITLE_INACTIVE,"",ImVec4(0.04f,0.04f,0.04f,1.0f)),
D(GUI_COLOR_TITLE_COLLAPSED,"",ImVec4(0.0f,0.0f,0.0f,0.51f)),
D(GUI_COLOR_MENU_BAR,"",ImVec4(0.14f,0.14f,0.14f,1.0f)),
D(GUI_COLOR_BORDER,"",ImVec4(0.43f,0.43f,0.5f,0.5f)),
D(GUI_COLOR_BORDER_SHADOW,"",ImVec4(0.0f,0.0f,0.0f,0.0f)),
D(GUI_COLOR_SCROLL_BACKGROUND,"",ImVec4(0.02f,0.02f,0.02f,0.33f)),
D(GUI_COLOR_SCROLL,"",ImVec4(0.31f,0.31f,0.31f,1.0f)),
D(GUI_COLOR_SCROLL_HOVER,"",ImVec4(0.41f,0.41f,0.41f,1.0f)),
D(GUI_COLOR_SCROLL_ACTIVE,"",ImVec4(0.51f,0.51f,0.51f,1.0f)),
D(GUI_COLOR_SEPARATOR,"",ImVec4(0.43f,0.43f,0.5f,0.5f)),
D(GUI_COLOR_SEPARATOR_HOVER,"",ImVec4(0.1f,0.4f,0.75f,0.78f)),
D(GUI_COLOR_SEPARATOR_ACTIVE,"",ImVec4(0.1f,0.4f,0.75f,1.0f)),
D(GUI_COLOR_DOCKING_PREVIEW,"",ImVec4(0.26f,0.59f,0.98f,0.7f)),
D(GUI_COLOR_DOCKING_EMPTY,"",ImVec4(0.2f,0.2f,0.2f,1.0f)),
D(GUI_COLOR_TABLE_HEADER,"",ImVec4(0.19f,0.19f,0.2f,1.0f)),
D(GUI_COLOR_TABLE_BORDER_HARD,"",ImVec4(0.31f,0.31f,0.35f,1.0f)),
D(GUI_COLOR_TABLE_BORDER_SOFT,"",ImVec4(0.23f,0.23f,0.25f,1.0f)),
D(GUI_COLOR_DRAG_DROP_TARGET,"",ImVec4(1.0f,1.0f,0.0f,0.9f)),
D(GUI_COLOR_NAV_HIGHLIGHT,"",ImVec4(0.26f,0.59f,0.98f,1.0f)),
D(GUI_COLOR_NAV_WIN_HIGHLIGHT,"",ImVec4(1.0f,1.0f,1.0f,0.7f)),
D(GUI_COLOR_NAV_WIN_BACKDROP,"",ImVec4(0.8f,0.8f,0.8f,0.2f)),
D(GUI_COLOR_TOGGLE_OFF,"",ImVec4(0.2f,0.2f,0.2f,1.0f)),
D(GUI_COLOR_TOGGLE_ON,"",ImVec4(0.2f,0.6f,0.2f,1.0f)),
D(GUI_COLOR_EDITING,"",ImVec4(0.2f,0.1f,0.1f,1.0f)),

View file

@ -15,9 +15,6 @@
* 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.
*
* this license only applies to the code. for the license of each font used,
* see `papers/`.
*/
#include "gui.h"
@ -49,7 +46,7 @@ const unsigned int imageLen[GUI_IMAGE_MAX]={
image_pat_size
};
SDL_Texture* FurnaceGUI::getTexture(FurnaceGUIImages image, SDL_BlendMode blendMode) {
void* FurnaceGUI::getTexture(FurnaceGUIImages image, FurnaceGUIBlendMode blendMode) {
FurnaceGUIImage* img=getImage(image);
if (img==NULL) return NULL;
@ -57,14 +54,14 @@ SDL_Texture* FurnaceGUI::getTexture(FurnaceGUIImages image, SDL_BlendMode blendM
if (img->width<=0 || img->height<=0) return NULL;
if (img->tex==NULL) {
img->tex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STATIC,img->width,img->height);
img->tex=rend->createTexture(false,img->width,img->height);
if (img->tex==NULL) {
logE("error while creating image %d texture! %s",(int)image,SDL_GetError());
return NULL;
}
SDL_SetTextureBlendMode(img->tex,blendMode);
rend->setTextureBlendMode(img->tex,blendMode);
if (SDL_UpdateTexture(img->tex,NULL,img->data,img->width*4)!=0) {
if (!rend->updateTexture(img->tex,img->data,img->width*4)) {
logE("error while updating texture of image %d! %s",(int)image,SDL_GetError());
}
}

View file

@ -2262,9 +2262,11 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::PushID(2+curIns);
if (ImGui::InputText("##Name",&ins->name)) {
MARK_MODIFIED;
}
ImGui::PopID();
ImGui::TableNextRow();
ImGui::TableNextColumn();
@ -5154,7 +5156,9 @@ void FurnaceGUI::drawInsEdit() {
wavePreview2[i]=wave2->data[i];
}
}
if (ins->ws.enabled) wavePreview.tick(true);
if (ins->ws.enabled && (!wavePreviewPaused || wavePreviewInit)) {
wavePreview.tick(true);
}
for (int i=0; i<wavePreviewLen; i++) {
if (wave2->data[i]>wavePreviewHeight) {
wavePreview3[i]=wavePreviewHeight;
@ -5200,9 +5204,43 @@ void FurnaceGUI::drawInsEdit() {
}
}
ImGui::TableNextColumn();
if (ImGui::Button(wavePreviewPaused?(ICON_FA_PLAY "##WSPause"):(ICON_FA_PAUSE "##WSPause"))) {
wavePreviewPaused=!wavePreviewPaused;
}
if (ImGui::IsItemHovered()) {
if (wavePreviewPaused) {
ImGui::SetTooltip("Resume preview");
} else {
ImGui::SetTooltip("Pause preview");
}
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_REPEAT "##WSRestart")) {
wavePreviewInit=true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Restart preview");
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_UPLOAD "##WSCopy")) {
curWave=e->addWave();
if (curWave==-1) {
showError("too many wavetables!");
} else {
wantScrollList=true;
MARK_MODIFIED;
RESET_WAVE_MACRO_ZOOM;
nextWindow=GUI_WINDOW_WAVE_EDIT;
DivWavetable* copyWave=e->song.wave[curWave];
copyWave->len=wavePreviewLen;
copyWave->max=wavePreviewHeight;
memcpy(copyWave->data,wavePreview.output,256*sizeof(int));
}
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Copy to new wavetable");
}
ImGui::SameLine();
ImGui::Text("(%d×%d)",wavePreviewLen,wavePreviewHeight+1);
ImGui::EndTable();

View file

@ -19,12 +19,13 @@
#define _USE_MATH_DEFINES
#include "gui.h"
#include "../ta-log.h"
#include "imgui_internal.h"
#include <fmt/printf.h>
void FurnaceGUI::drawImage(ImDrawList* dl, FurnaceGUIImages image, const ImVec2& pos, const ImVec2& scale, double rotate, const ImVec2& uvMin, const ImVec2& uvMax, const ImVec4& imgColor) {
FurnaceGUIImage* imgI=getImage(image);
SDL_Texture* img=getTexture(image);
void* img=getTexture(image);
float squareSize=MAX(introMax.x-introMin.x,introMax.y-introMin.y);
float uDiff=uvMax.x-uvMin.x;
@ -69,10 +70,12 @@ void FurnaceGUI::drawImage(ImDrawList* dl, FurnaceGUIImages image, const ImVec2&
ImU32 colorConverted=ImGui::GetColorU32(imgColor);
dl->AddImageQuad(img,quad0,quad1,quad2,quad3,uv0,uv1,uv2,uv3,colorConverted);
dl->AddImageQuad(rend->getTextureID(img),quad0,quad1,quad2,quad3,uv0,uv1,uv2,uv3,colorConverted);
}
void FurnaceGUI::endIntroTune() {
if (introStopped) return;
logV("ending intro");
stop();
if (curFileName.empty()) {
e->createNewFromDefaults();
@ -95,6 +98,8 @@ void FurnaceGUI::endIntroTune() {
selEnd=SelectionPoint();
cursor=SelectionPoint();
updateWindowTitle();
updateScroll(0);
introStopped=true;
}
void FurnaceGUI::drawIntro(double introTime, bool monitor) {
@ -162,7 +167,7 @@ void FurnaceGUI::drawIntro(double introTime, bool monitor) {
getTexture(GUI_IMAGE_TALOGO);
getTexture(GUI_IMAGE_TACHIP);
getTexture(GUI_IMAGE_LOGO);
getTexture(GUI_IMAGE_INTROBG,SDL_BLENDMODE_ADD);
getTexture(GUI_IMAGE_INTROBG,GUI_BLEND_MODE_ADD);
if (monitor) {
ImVec2 textPos=ImLerp(top,bottom,ImVec2(0.5,0.5));
@ -290,7 +295,7 @@ void FurnaceGUI::drawIntro(double introTime, bool monitor) {
if (introSkipDo) {
introSkip+=ImGui::GetIO().DeltaTime;
if (introSkip>=0.5) {
if (e->isPlaying()) endIntroTune();
if (!shortIntro) endIntroTune();
introPos=0.1;
if (introSkip>=0.75) introPos=12.0;
}
@ -317,7 +322,7 @@ void FurnaceGUI::drawIntro(double introTime, bool monitor) {
e->setRepeatPattern(false);
play();
}
if (e->isPlaying() && introPos>=10.0 && !shortIntro) endIntroTune();
if (introPos>=10.0 && !shortIntro) endIntroTune();
introPos+=ImGui::GetIO().DeltaTime;
if (introPos>=(shortIntro?1.0:11.0)) {
introPos=12.0;
@ -325,5 +330,7 @@ void FurnaceGUI::drawIntro(double introTime, bool monitor) {
commitTutorial();
}
}
} else if (!shortIntro) {
endIntroTune();
}
}

View file

@ -49,11 +49,11 @@ void _popPartBlend(const ImDrawList* drawList, const ImDrawCmd* cmd) {
}
void FurnaceGUI::pushPartBlend() {
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_ADD);
rend->setBlendMode(GUI_BLEND_MODE_ADD);
}
void FurnaceGUI::popPartBlend() {
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND);
rend->setBlendMode(GUI_BLEND_MODE_BLEND);
}
// draw a pattern row
@ -287,7 +287,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
if (pat->data[i][index]>0xff) {
snprintf(id,63,"??##PE%d_%d_%d",k,i,j);
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]);
} else if (pat->data[i][index]>0x10 || settings.oneDigitEffects==0) {
} else if (pat->data[i][index]>=0x10 || settings.oneDigitEffects==0) {
const unsigned char data=pat->data[i][index];
snprintf(id,63,"%.2X##PE%d_%d_%d",data,k,i,j);
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]);
@ -834,28 +834,9 @@ void FurnaceGUI::drawPattern() {
if (extraChannelButtons==2) {
DivPattern* pat=e->curPat[i].getPattern(e->curOrders->ord[i][ord],true);
ImGui::PushFont(mainFont);
if (patNameTarget==i) {
snprintf(chanID,2048,"##PatNameI%d_%d",i,ord);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(8.0f*dpiScale));
ImGui::SetCursorPosX(ImGui::GetCursorPosX()+4.0f*dpiScale);
ImGui::InputText(chanID,&pat->name);
if (wantPatName) {
wantPatName=false;
ImGui::SetItemDefaultFocus();
ImGui::SetKeyboardFocusHere(-1);
} else {
if (!ImGui::IsItemActive()) {
patNameTarget=-1;
}
}
} else {
snprintf(chanID,2048," %s##PatName%d",pat->name.c_str(),i);
if (ImGui::Selectable(chanID,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) {
patNameTarget=i;
wantPatName=true;
snprintf(chanID,2048,"##PatNameI%d_%d",i,ord);
ImGui::SetActiveID(ImGui::GetID(chanID),ImGui::GetCurrentWindow());
}
snprintf(chanID,2048," %s##PatName%d",pat->name.c_str(),i);
if (ImGui::Selectable(chanID,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) {
editStr(&pat->name);
}
ImGui::PopFont();
} else if (extraChannelButtons==1) {

View file

@ -123,6 +123,7 @@ void FurnaceGUI::drawPiano() {
pianoInputPadMode=PIANO_INPUT_PAD_SPLIT_VISIBLE;
}
ImGui::Checkbox("Share play/edit offset/range",&pianoSharePosition);
ImGui::Checkbox("Read-only (can't input notes)",&pianoReadonly);
ImGui::EndPopup();
}
@ -223,7 +224,7 @@ void FurnaceGUI::drawPiano() {
//ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) {
bool canInput=false;
if (ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay"))) {
if (!pianoReadonly && ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay"))) {
canInput=true;
ImGui::InhibitInertialScroll();
}

View file

@ -19,11 +19,11 @@
// portions based on imgui_widgets.cpp
#include "plot_nolerp.h"
#include "imgui.h"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include "plot_nolerp.h"
#include "imgui.h"
#include "imgui_internal.h"
struct FurnacePlotArrayGetterData

79
src/gui/render.cpp Normal file
View file

@ -0,0 +1,79 @@
/**
* 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 "gui.h"
#include "../ta-log.h"
#ifdef HAVE_RENDER_SDL
#include "render/renderSDL.h"
#endif
#ifdef HAVE_RENDER_GL
#include "render/renderGL.h"
#endif
#ifdef HAVE_RENDER_DX11
#include "render/renderDX11.h"
#endif
bool FurnaceGUI::initRender() {
if (rend!=NULL) return false;
if (settings.renderBackend=="OpenGL") {
renderBackend=GUI_BACKEND_GL;
} else if (settings.renderBackend=="DirectX 11") {
renderBackend=GUI_BACKEND_DX11;
} else if (settings.renderBackend=="SDL") {
renderBackend=GUI_BACKEND_SDL;
} else {
renderBackend=GUI_BACKEND_DEFAULT;
}
switch (renderBackend) {
#ifdef HAVE_RENDER_GL
case GUI_BACKEND_GL:
logI("render backend: OpenGL");
rend=new FurnaceGUIRenderGL;
break;
#endif
#ifdef HAVE_RENDER_DX11
case GUI_BACKEND_DX11:
logI("render backend: DirectX 11");
rend=new FurnaceGUIRenderDX11;
break;
#endif
#ifdef HAVE_RENDER_SDL
case GUI_BACKEND_SDL:
logI("render backend: SDL_Renderer");
rend=new FurnaceGUIRenderSDL;
break;
#endif
default:
logE("invalid render backend!");
return false;
break;
}
return true;
}
bool FurnaceGUI::quitRender() {
if (rend==NULL) return false;
bool ret=rend->quit();
delete rend;
rend=NULL;
return ret;
}

103
src/gui/render/abstract.cpp Normal file
View file

@ -0,0 +1,103 @@
/**
* 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 "../gui.h"
ImTextureID FurnaceGUIRender::getTextureID(void* which) {
return NULL;
}
bool FurnaceGUIRender::lockTexture(void* which, void** data, int* pitch) {
return false;
}
bool FurnaceGUIRender::unlockTexture(void* which) {
return false;
}
bool FurnaceGUIRender::updateTexture(void* which, void* data, int pitch) {
return false;
}
void* FurnaceGUIRender::createTexture(bool dynamic, int width, int height) {
return NULL;
}
bool FurnaceGUIRender::destroyTexture(void* which) {
return false;
}
void FurnaceGUIRender::setTextureBlendMode(void* which, FurnaceGUIBlendMode mode) {
}
void FurnaceGUIRender::setBlendMode(FurnaceGUIBlendMode mode) {
}
void FurnaceGUIRender::resized(const SDL_Event& ev) {
}
void FurnaceGUIRender::clear(ImVec4 color) {
}
bool FurnaceGUIRender::newFrame() {
return true;
}
void FurnaceGUIRender::createFontsTexture() {
}
void FurnaceGUIRender::destroyFontsTexture() {
}
void FurnaceGUIRender::renderGUI() {
}
void FurnaceGUIRender::wipe(float alpha) {
}
void FurnaceGUIRender::present() {
}
bool FurnaceGUIRender::getOutputSize(int& w, int& h) {
return false;
}
int FurnaceGUIRender::getWindowFlags() {
return 0;
}
void FurnaceGUIRender::preInit() {
}
bool FurnaceGUIRender::init(SDL_Window* win) {
return false;
}
void FurnaceGUIRender::initGUI(SDL_Window* win) {
}
bool FurnaceGUIRender::quit() {
return false;
}
void FurnaceGUIRender::quitGUI() {
}
FurnaceGUIRender::~FurnaceGUIRender() {
}

View file

@ -0,0 +1,571 @@
/**
* 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 INCLUDE_D3D11
#include "renderDX11.h"
#include <SDL_syswm.h>
#include "backends/imgui_impl_dx11.h"
#include "../../ta-log.h"
typedef HRESULT (__stdcall *D3DCompile_t)(LPCVOID,SIZE_T,LPCSTR,D3D_SHADER_MACRO*,ID3DInclude*,LPCSTR,LPCSTR,UINT,UINT,ID3DBlob**,ID3DBlob*);
const char* shD3D11_wipe_srcV=
"cbuffer WipeUniform: register(b0) {\n"
" float alpha;\n"
" float padding1;\n"
" float padding2;\n"
" float padding3;\n"
" float4 padding4;\n"
"};\n"
"\n"
"struct vsInput {\n"
" float4 pos: POSITION;\n"
"};\n"
"\n"
"struct fsInput {\n"
" float4 pos: SV_POSITION;\n"
" float4 color: COLOR0;\n"
"};\n"
"\n"
"fsInput main(vsInput input) {\n"
" fsInput output;\n"
" output.pos=input.pos;\n"
" output.color=float4(0.0f,0.0f,0.0f,alpha);\n"
" return output;\n"
"}";
const char* shD3D11_wipe_srcF=
"struct fsInput {\n"
" float4 pos: SV_POSITION;\n"
" float4 color: COLOR0;\n"
"};\n"
"\n"
"float4 main(fsInput input): SV_Target {\n"
" return input.color;\n"
"}";
const D3D11_INPUT_ELEMENT_DESC shD3D11_wipe_inputLayout={
"POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0
};
const D3D_FEATURE_LEVEL possibleFeatureLevels[2]={
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_0
};
struct FurnaceDXTexture {
ID3D11Texture2D* tex;
ID3D11ShaderResourceView* view;
int width, height;
unsigned char* lockedData;
bool dynamic;
FurnaceDXTexture():
tex(NULL),
view(NULL),
width(0),
height(0),
lockedData(NULL),
dynamic(false) {}
};
bool FurnaceGUIRenderDX11::destroyRenderTarget() {
if (renderTarget!=NULL) {
renderTarget->Release();
renderTarget=NULL;
return true;
}
return false;
}
bool FurnaceGUIRenderDX11::createRenderTarget() {
ID3D11Texture2D* screen=NULL;
HRESULT result;
destroyRenderTarget();
if (swapchain==NULL || device==NULL) {
logW("createRenderTarget: swapchain or device are NULL!");
return false;
}
DXGI_SWAP_CHAIN_DESC chainDesc;
memset(&chainDesc,0,sizeof(chainDesc));
if (swapchain->GetDesc(&chainDesc)!=S_OK) {
logW("createRenderTarget: could not get swapchain desc!");
} else {
outW=chainDesc.BufferDesc.Width;
outH=chainDesc.BufferDesc.Height;
logI("DX11: buffer desc sizes: %d, %d",chainDesc.BufferDesc.Width,chainDesc.BufferDesc.Height);
}
result=swapchain->GetBuffer(0,IID_PPV_ARGS(&screen));
if (result!=S_OK) {
logW("createRenderTarget: could not get buffer! %.8x",result);
return false;
}
if (screen==NULL) {
logW("createRenderTarget: screen is null!");
return false;
}
result=device->CreateRenderTargetView(screen,NULL,&renderTarget);
if (result!=S_OK) {
logW("createRenderTarget: could not create render target view! %.8x",result);
screen->Release();
return false;
}
if (renderTarget==NULL) {
logW("createRenderTarget: what the hell the render target is null?");
screen->Release();
return false;
}
screen->Release();
return true;
}
ImTextureID FurnaceGUIRenderDX11::getTextureID(void* which) {
FurnaceDXTexture* t=(FurnaceDXTexture*)which;
return (ImTextureID)t->view;
}
bool FurnaceGUIRenderDX11::lockTexture(void* which, void** data, int* pitch) {
FurnaceDXTexture* t=(FurnaceDXTexture*)which;
if (t->lockedData!=NULL) return false;
D3D11_MAPPED_SUBRESOURCE mappedRes;
memset(&mappedRes,0,sizeof(mappedRes));
HRESULT result=context->Map(t->tex,D3D11CalcSubresource(0,0,1),D3D11_MAP_WRITE_DISCARD,0,&mappedRes);
if (result!=S_OK) {
logW("could not map texture! %.8x",result);
return false;
}
t->lockedData=(unsigned char*)mappedRes.pData;
*data=mappedRes.pData;
*pitch=mappedRes.RowPitch;
logV("texture locked... pitch: %d",mappedRes.RowPitch);
return true;
}
bool FurnaceGUIRenderDX11::unlockTexture(void* which) {
FurnaceDXTexture* t=(FurnaceDXTexture*)which;
if (t->lockedData==NULL) return false;
context->Unmap(t->tex,D3D11CalcSubresource(0,0,1));
t->lockedData=NULL;
return true;
}
bool FurnaceGUIRenderDX11::updateTexture(void* which, void* data, int pitch) {
FurnaceDXTexture* t=(FurnaceDXTexture*)which;
if (t->dynamic) {
unsigned char* d=NULL;
int p=0;
if (!lockTexture(t,(void**)&d,&p)) return false;
if (p==pitch) {
memcpy(d,data,p*t->height);
} else {
unsigned char* ucData=(unsigned char*)data;
int srcPos=0;
int destPos=0;
for (int i=0; i<t->height; i++) {
memcpy(&d[destPos],&ucData[srcPos],pitch);
srcPos+=pitch;
destPos+=p;
}
}
unlockTexture(t);
} else {
context->UpdateSubresource(t->tex,D3D11CalcSubresource(0,0,1),NULL,data,pitch,pitch*t->height);
}
return true;
}
void* FurnaceGUIRenderDX11::createTexture(bool dynamic, int width, int height) {
D3D11_TEXTURE2D_DESC texDesc;
D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc;
ID3D11Texture2D* tex=NULL;
ID3D11ShaderResourceView* view=NULL;
HRESULT result;
memset(&texDesc,0,sizeof(texDesc));
memset(&viewDesc,0,sizeof(viewDesc));
texDesc.Width=width;
texDesc.Height=height;
texDesc.MipLevels=1;
texDesc.ArraySize=1;
texDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM; // ???
texDesc.SampleDesc.Count=1;
texDesc.SampleDesc.Quality=0;
texDesc.Usage=dynamic?D3D11_USAGE_DYNAMIC:D3D11_USAGE_DEFAULT;
texDesc.BindFlags=D3D11_BIND_SHADER_RESOURCE;
texDesc.CPUAccessFlags=dynamic?D3D11_CPU_ACCESS_WRITE:0;
texDesc.MiscFlags=0;
result=device->CreateTexture2D(&texDesc,NULL,&tex);
if (result!=S_OK) {
logW("could not create texture! %.8x",result);
return NULL;
}
viewDesc.Format=texDesc.Format=texDesc.Format;
viewDesc.ViewDimension=D3D11_SRV_DIMENSION_TEXTURE2D;
viewDesc.Texture2D.MostDetailedMip=0;
viewDesc.Texture2D.MipLevels=texDesc.MipLevels;
result=device->CreateShaderResourceView(tex,&viewDesc,&view);
if (result!=S_OK) {
logW("could not create texture view! %.8x",result);
tex->Release();
return NULL;
}
FurnaceDXTexture* ret=new FurnaceDXTexture;
ret->width=width;
ret->height=height;
ret->tex=tex;
ret->view=view;
ret->dynamic=dynamic;
textures.push_back(ret);
return ret;
}
bool FurnaceGUIRenderDX11::destroyTexture(void* which) {
FurnaceDXTexture* t=(FurnaceDXTexture*)which;
t->view->Release();
t->tex->Release();
delete t;
for (size_t i=0; i<textures.size(); i++) {
if (textures[i]==t) {
textures.erase(textures.begin()+i);
break;
}
}
return true;
}
void FurnaceGUIRenderDX11::setTextureBlendMode(void* which, FurnaceGUIBlendMode mode) {
}
void FurnaceGUIRenderDX11::setBlendMode(FurnaceGUIBlendMode mode) {
}
void FurnaceGUIRenderDX11::resized(const SDL_Event& ev) {
destroyRenderTarget();
logI("DX11: resizing buffers");
HRESULT result=swapchain->ResizeBuffers(0,(unsigned int)ev.window.data1,(unsigned int)ev.window.data2,DXGI_FORMAT_UNKNOWN,DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
if (result!=S_OK) {
logW("error while resizing swapchain buffers! %.8x",result);
}
createRenderTarget();
}
void FurnaceGUIRenderDX11::clear(ImVec4 color) {
float floatColor[4]={
color.x*color.w,
color.y*color.w,
color.z*color.w,
color.w,
};
context->OMSetRenderTargets(1,&renderTarget,NULL);
context->ClearRenderTargetView(renderTarget,floatColor);
}
bool FurnaceGUIRenderDX11::newFrame() {
ImGui_ImplDX11_NewFrame();
return true;
}
void FurnaceGUIRenderDX11::createFontsTexture() {
ImGui_ImplDX11_CreateDeviceObjects();
}
void FurnaceGUIRenderDX11::destroyFontsTexture() {
ImGui_ImplDX11_InvalidateDeviceObjects();
}
void FurnaceGUIRenderDX11::renderGUI() {
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}
const float blendFactor[4]={
1.0f, 1.0f, 1.0f, 1.0f
};
void FurnaceGUIRenderDX11::wipe(float alpha) {
D3D11_VIEWPORT viewPort;
unsigned int strides=4*sizeof(float);
unsigned int offsets=0;
memset(&viewPort,0,sizeof(viewPort));
viewPort.TopLeftX=0.0f;
viewPort.TopLeftY=0.0f;
viewPort.Width=outW;
viewPort.Height=outH;
viewPort.MinDepth=0.0f;
viewPort.MaxDepth=1.0f;
D3D11_MAPPED_SUBRESOURCE mappedUniform;
if (context->Map(sh_wipe_uniform,0,D3D11_MAP_WRITE_DISCARD,0,&mappedUniform)!=S_OK) {
logW("could not map constant");
}
WipeUniform* sh_wipe_uniformState=(WipeUniform*)mappedUniform.pData;
sh_wipe_uniformState->alpha=alpha;
context->Unmap(sh_wipe_uniform,0);
context->RSSetViewports(1,&viewPort);
context->RSSetState(rsState);
context->OMSetBlendState(omBlendState,blendFactor,0xffffffff);
context->IASetInputLayout(sh_wipe_inputLayout);
context->IASetVertexBuffers(0,1,&quadVertex,&strides,&offsets);
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
context->VSSetShader(sh_wipe_vertex,NULL,0);
context->VSSetConstantBuffers(0,1,&sh_wipe_uniform);
context->PSSetShader(sh_wipe_fragment,NULL,0);
context->Draw(4,0);
}
void FurnaceGUIRenderDX11::present() {
swapchain->Present(1,0);
}
bool FurnaceGUIRenderDX11::getOutputSize(int& w, int& h) {
w=outW;
h=outH;
return true;
}
int FurnaceGUIRenderDX11::getWindowFlags() {
return 0;
}
void FurnaceGUIRenderDX11::preInit() {
}
const float wipeVertices[4][4]={
-1.0, -1.0, 0.0, 1.0,
1.0, -1.0, 0.0, 1.0,
-1.0, 1.0, 0.0, 1.0,
1.0, 1.0, 0.0, 1.0
};
bool FurnaceGUIRenderDX11::init(SDL_Window* win) {
SDL_SysWMinfo sysWindow;
D3D_FEATURE_LEVEL featureLevel;
SDL_VERSION(&sysWindow.version);
if (SDL_GetWindowWMInfo(win,&sysWindow)==SDL_FALSE) {
logE("could not get window WM info! %s",SDL_GetError());
return false;
}
HWND window=(HWND)sysWindow.info.win.window;
DXGI_SWAP_CHAIN_DESC chainDesc;
memset(&chainDesc,0,sizeof(chainDesc));
chainDesc.BufferDesc.Width=0;
chainDesc.BufferDesc.Height=0;
chainDesc.BufferDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM;
chainDesc.BufferDesc.RefreshRate.Numerator=60;
chainDesc.BufferDesc.RefreshRate.Denominator=1;
chainDesc.SampleDesc.Count=1;
chainDesc.SampleDesc.Quality=0;
chainDesc.BufferUsage=DXGI_USAGE_RENDER_TARGET_OUTPUT;
chainDesc.BufferCount=2;
chainDesc.OutputWindow=window;
chainDesc.Windowed=TRUE; // TODO: what if we're in full screen mode?
chainDesc.SwapEffect=DXGI_SWAP_EFFECT_DISCARD;
chainDesc.Flags=DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
HRESULT result=D3D11CreateDeviceAndSwapChain(NULL,D3D_DRIVER_TYPE_HARDWARE,NULL,0,possibleFeatureLevels,2,D3D11_SDK_VERSION,&chainDesc,&swapchain,&device,&featureLevel,&context);
if (result!=S_OK) {
logE("could not create device and/or swap chain! %.8x",result);
return false;
}
// https://github.com/ocornut/imgui/pull/638
D3DCompile_t D3DCompile=NULL;
char dllBuffer[20];
for (int i=47; (i>30 && !D3DCompile); i--) {
snprintf(dllBuffer,20,"d3dcompiler_%d.dll",i);
HMODULE hDll=LoadLibraryA(dllBuffer);
if (hDll) {
D3DCompile=(D3DCompile_t)GetProcAddress(hDll,"D3DCompile");
}
}
if (!D3DCompile) {
logE("could not find D3DCompile!");
return false;
}
// create wipe shader
ID3DBlob* wipeBlobV=NULL;
ID3DBlob* wipeBlobF=NULL;
D3D11_BUFFER_DESC wipeConstantDesc;
result=D3DCompile(shD3D11_wipe_srcV,strlen(shD3D11_wipe_srcV),NULL,NULL,NULL,"main","vs_4_0",0,0,&wipeBlobV,NULL);
if (result!=S_OK) {
logE("could not compile vertex shader! %.8x",result);
return false;
}
result=D3DCompile(shD3D11_wipe_srcF,strlen(shD3D11_wipe_srcF),NULL,NULL,NULL,"main","ps_4_0",0,0,&wipeBlobF,NULL);
if (result!=S_OK) {
logE("could not compile pixel shader! %.8x",result);
return false;
}
result=device->CreateVertexShader(wipeBlobV->GetBufferPointer(),wipeBlobV->GetBufferSize(),NULL,&sh_wipe_vertex);
if (result!=S_OK) {
logE("could not create vertex shader! %.8x",result);
return false;
}
result=device->CreatePixelShader(wipeBlobF->GetBufferPointer(),wipeBlobF->GetBufferSize(),NULL,&sh_wipe_fragment);
if (result!=S_OK) {
logE("could not create pixel shader! %.8x",result);
return false;
}
result=device->CreateInputLayout(&shD3D11_wipe_inputLayout,1,wipeBlobV->GetBufferPointer(),wipeBlobV->GetBufferSize(),&sh_wipe_inputLayout);
if (result!=S_OK) {
logE("could not create input layout! %.8x",result);
return false;
}
memset(&wipeConstantDesc,0,sizeof(wipeConstantDesc));
wipeConstantDesc.ByteWidth=sizeof(WipeUniform);
wipeConstantDesc.Usage=D3D11_USAGE_DYNAMIC;
wipeConstantDesc.BindFlags=D3D11_BIND_CONSTANT_BUFFER;
wipeConstantDesc.CPUAccessFlags=D3D11_CPU_ACCESS_WRITE;
wipeConstantDesc.MiscFlags=0;
wipeConstantDesc.StructureByteStride=0;
result=device->CreateBuffer(&wipeConstantDesc,NULL,&sh_wipe_uniform);
if (result!=S_OK) {
logE("could not create constant buffer! %.8x",result);
return false;
}
// create wipe vertices
D3D11_BUFFER_DESC vertexDesc;
D3D11_SUBRESOURCE_DATA vertexRes;
memset(&vertexDesc,0,sizeof(vertexDesc));
memset(&vertexRes,0,sizeof(vertexRes));
vertexDesc.ByteWidth=4*4*sizeof(float);
vertexDesc.Usage=D3D11_USAGE_DEFAULT;
vertexDesc.BindFlags=D3D11_BIND_VERTEX_BUFFER;
vertexDesc.CPUAccessFlags=0;
vertexDesc.MiscFlags=0;
vertexDesc.StructureByteStride=0;
vertexRes.pSysMem=wipeVertices;
vertexRes.SysMemPitch=0;
vertexRes.SysMemSlicePitch=0;
result=device->CreateBuffer(&vertexDesc,&vertexRes,&quadVertex);
if (result!=S_OK) {
logE("could not create vertex buffer! %.8x",result);
return false;
}
// initialize the rest
D3D11_RASTERIZER_DESC rasterDesc;
D3D11_BLEND_DESC blendDesc;
memset(&rasterDesc,0,sizeof(rasterDesc));
memset(&blendDesc,0,sizeof(blendDesc));
rasterDesc.FillMode=D3D11_FILL_SOLID;
rasterDesc.CullMode=D3D11_CULL_NONE;
rasterDesc.FrontCounterClockwise=false;
rasterDesc.DepthBias=0;
rasterDesc.DepthBiasClamp=0.0f;
rasterDesc.SlopeScaledDepthBias=0.0f;
rasterDesc.DepthClipEnable=false;
rasterDesc.ScissorEnable=false;
rasterDesc.MultisampleEnable=false;
rasterDesc.AntialiasedLineEnable=false;
result=device->CreateRasterizerState(&rasterDesc,&rsState);
if (result!=S_OK) {
logE("could not create rasterizer state! %.8x",result);
return false;
}
blendDesc.AlphaToCoverageEnable=false;
blendDesc.IndependentBlendEnable=false;
blendDesc.RenderTarget[0].BlendEnable=true;
blendDesc.RenderTarget[0].SrcBlend=D3D11_BLEND_SRC_ALPHA;
blendDesc.RenderTarget[0].DestBlend=D3D11_BLEND_INV_SRC_ALPHA;
blendDesc.RenderTarget[0].BlendOp=D3D11_BLEND_OP_ADD;
blendDesc.RenderTarget[0].SrcBlendAlpha=D3D11_BLEND_ONE;
blendDesc.RenderTarget[0].DestBlendAlpha=D3D11_BLEND_INV_SRC_ALPHA;
blendDesc.RenderTarget[0].BlendOpAlpha=D3D11_BLEND_OP_ADD;
blendDesc.RenderTarget[0].RenderTargetWriteMask=D3D11_COLOR_WRITE_ENABLE_ALL;
result=device->CreateBlendState(&blendDesc,&omBlendState);
if (result!=S_OK) {
logE("could not create blend state! %.8x",result);
return false;
}
createRenderTarget();
return true;
}
void FurnaceGUIRenderDX11::initGUI(SDL_Window* win) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui_ImplSDL2_InitForD3D(win);
ImGui_ImplDX11_Init(device,context);
}
bool FurnaceGUIRenderDX11::quit() {
destroyRenderTarget();
for (FurnaceDXTexture* i: textures) {
i->view->Release();
i->tex->Release();
delete i;
}
textures.clear();
if (swapchain!=NULL) {
swapchain->Release();
swapchain=NULL;
}
if (context!=NULL) {
context->Release();
context=NULL;
}
if (device!=NULL) {
device->Release();
device=NULL;
}
return true;
}
void FurnaceGUIRenderDX11::quitGUI() {
ImGui_ImplDX11_Shutdown();
}

103
src/gui/render/renderDX11.h Normal file
View file

@ -0,0 +1,103 @@
/**
* 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 "../gui.h"
#ifdef INCLUDE_D3D11
#include <d3d11.h>
#else
typedef void ID3D11DeviceContext;
typedef void ID3D11RenderTargetView;
typedef void ID3D11Buffer;
typedef void ID3D11RasterizerState;
typedef void ID3D11BlendState;
typedef void ID3D11VertexShader;
typedef void ID3D11PixelShader;
typedef void ID3D11InputLayout;
typedef void IDXGISwapChain;
#endif
struct FurnaceDXTexture;
class FurnaceGUIRenderDX11: public FurnaceGUIRender {
ID3D11Device* device;
ID3D11DeviceContext* context;
ID3D11RenderTargetView* renderTarget;
IDXGISwapChain* swapchain;
ID3D11RasterizerState* rsState;
ID3D11BlendState* omBlendState;
ID3D11Buffer* quadVertex;
int outW, outH;
// SHADERS //
// -> wipe
ID3D11VertexShader* sh_wipe_vertex;
ID3D11PixelShader* sh_wipe_fragment;
ID3D11InputLayout* sh_wipe_inputLayout;
ID3D11Buffer* sh_wipe_uniform;
struct WipeUniform {
float alpha;
float padding[7];
};
bool destroyRenderTarget();
bool createRenderTarget();
std::vector<FurnaceDXTexture*> textures;
public:
ImTextureID getTextureID(void* which);
bool lockTexture(void* which, void** data, int* pitch);
bool unlockTexture(void* which);
bool updateTexture(void* which, void* data, int pitch);
void* createTexture(bool dynamic, int width, int height);
bool destroyTexture(void* which);
void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode);
void setBlendMode(FurnaceGUIBlendMode mode);
void resized(const SDL_Event& ev);
void clear(ImVec4 color);
bool newFrame();
void createFontsTexture();
void destroyFontsTexture();
void renderGUI();
void wipe(float alpha);
void present();
bool getOutputSize(int& w, int& h);
int getWindowFlags();
void preInit();
bool init(SDL_Window* win);
void initGUI(SDL_Window* win);
void quitGUI();
bool quit();
FurnaceGUIRenderDX11():
device(NULL),
context(NULL),
renderTarget(NULL),
swapchain(NULL),
rsState(NULL),
omBlendState(NULL),
quadVertex(NULL),
outW(0),
outH(0),
sh_wipe_vertex(NULL),
sh_wipe_fragment(NULL),
sh_wipe_inputLayout(NULL),
sh_wipe_uniform(NULL) {
}
};

387
src/gui/render/renderGL.cpp Normal file
View file

@ -0,0 +1,387 @@
/**
* 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 "renderGL.h"
#include "../../ta-log.h"
#ifdef USE_GLES
#include "SDL_opengles2.h"
#define PIXEL_FORMAT GL_UNSIGNED_BYTE
#else
#include "SDL_opengl.h"
#define PIXEL_FORMAT GL_UNSIGNED_INT_8_8_8_8_REV
#endif
#include "backends/imgui_impl_opengl3.h"
#define C(x) x; if (glGetError()!=GL_NO_ERROR) logW("OpenGL error in %s:%d: " #x,__FILE__,__LINE__);
PFNGLGENBUFFERSPROC furGenBuffers=NULL;
PFNGLBINDBUFFERPROC furBindBuffer=NULL;
PFNGLBUFFERDATAPROC furBufferData=NULL;
PFNGLVERTEXATTRIBPOINTERPROC furVertexAttribPointer=NULL;
PFNGLENABLEVERTEXATTRIBARRAYPROC furEnableVertexAttribArray=NULL;
PFNGLACTIVETEXTUREPROC furActiveTexture=NULL;
PFNGLCREATESHADERPROC furCreateShader=NULL;
PFNGLSHADERSOURCEPROC furShaderSource=NULL;
PFNGLCOMPILESHADERPROC furCompileShader=NULL;
PFNGLGETSHADERIVPROC furGetShaderiv=NULL;
PFNGLATTACHSHADERPROC furAttachShader=NULL;
PFNGLBINDATTRIBLOCATIONPROC furBindAttribLocation=NULL;
PFNGLCREATEPROGRAMPROC furCreateProgram=NULL;
PFNGLLINKPROGRAMPROC furLinkProgram=NULL;
PFNGLGETPROGRAMIVPROC furGetProgramiv=NULL;
PFNGLUSEPROGRAMPROC furUseProgram=NULL;
PFNGLGETUNIFORMLOCATIONPROC furGetUniformLocation=NULL;
PFNGLUNIFORM1FPROC furUniform1f=NULL;
PFNGLGETSHADERINFOLOGPROC furGetShaderInfoLog=NULL;
struct FurnaceGLTexture {
GLuint id;
int width, height;
unsigned char* lockedData;
FurnaceGLTexture():
id(0),
width(0),
height(0),
lockedData(NULL) {}
};
#ifdef USE_GLES
const char* sh_wipe_srcV=
"attribute vec4 fur_position;\n"
"void main() {\n"
" gl_Position=fur_position;\n"
"}\n";
const char* sh_wipe_srcF=
"uniform float uAlpha;\n"
"void main() {\n"
" gl_FragColor=vec4(0.0,0.0,0.0,uAlpha);\n"
"}\n";
#else
const char* sh_wipe_srcV=
"#version 130\n"
"in vec4 fur_position;\n"
"void main() {\n"
" gl_Position=fur_position;\n"
"}\n";
const char* sh_wipe_srcF=
"#version 130\n"
"uniform float uAlpha;\n"
"out vec4 fur_FragColor;\n"
"void main() {\n"
" fur_FragColor=vec4(0.0,0.0,0.0,uAlpha);\n"
"}\n";
#endif
bool FurnaceGUIRenderGL::createShader(const char* vertexS, const char* fragmentS, int& vertex, int& fragment, int& program) {
int status;
char infoLog[4096];
int infoLogLen;
if (!furCreateShader || !furShaderSource || !furCompileShader || !furGetShaderiv ||
!furGetShaderInfoLog || !furCreateProgram || !furAttachShader || !furLinkProgram ||
!furBindAttribLocation || !furGetProgramiv) {
logW("I can't compile shaders");
return false;
}
vertex=furCreateShader(GL_VERTEX_SHADER);
furShaderSource(vertex,1,&vertexS,NULL);
furCompileShader(vertex);
furGetShaderiv(vertex,GL_COMPILE_STATUS,&status);
if (!status) {
logW("failed to compile vertex shader");
furGetShaderInfoLog(vertex,4095,&infoLogLen,infoLog);
infoLog[infoLogLen]=0;
logW("%s",infoLog);
return false;
}
fragment=furCreateShader(GL_FRAGMENT_SHADER);
furShaderSource(fragment,1,&fragmentS,NULL);
furCompileShader(fragment);
furGetShaderiv(fragment,GL_COMPILE_STATUS,&status);
if (!status) {
logW("failed to compile fragment shader");
return false;
}
program=furCreateProgram();
furAttachShader(program,vertex);
furAttachShader(program,fragment);
furBindAttribLocation(program,0,"fur_position");
furLinkProgram(program);
furGetProgramiv(program,GL_LINK_STATUS,&status);
if (!status) {
logW("failed to link shader!");
return false;
}
return true;
}
ImTextureID FurnaceGUIRenderGL::getTextureID(void* which) {
intptr_t ret=((FurnaceGLTexture*)which)->id;
return (ImTextureID)ret;
}
bool FurnaceGUIRenderGL::lockTexture(void* which, void** data, int* pitch) {
FurnaceGLTexture* t=(FurnaceGLTexture*)which;
if (t->lockedData!=NULL) return false;
t->lockedData=new unsigned char[t->width*t->height*4];
*data=t->lockedData;
*pitch=t->width*4;
return true;
}
bool FurnaceGUIRenderGL::unlockTexture(void* which) {
FurnaceGLTexture* t=(FurnaceGLTexture*)which;
if (t->lockedData==NULL) return false;
C(glBindTexture(GL_TEXTURE_2D,t->id));
C(glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,t->width,t->height,0,GL_RGBA,PIXEL_FORMAT,t->lockedData));
C(glFlush());
delete[] t->lockedData;
t->lockedData=NULL;
return true;
}
bool FurnaceGUIRenderGL::updateTexture(void* which, void* data, int pitch) {
FurnaceGLTexture* t=(FurnaceGLTexture*)which;
if (t->width*4!=pitch) return false;
C(glBindTexture(GL_TEXTURE_2D,t->id));
C(glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,t->width,t->height,0,GL_RGBA,PIXEL_FORMAT,data));
return true;
}
void* FurnaceGUIRenderGL::createTexture(bool dynamic, int width, int height) {
FurnaceGLTexture* t=new FurnaceGLTexture;
C(glGenTextures(1,&t->id));
C(glBindTexture(GL_TEXTURE_2D,t->id));
C(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR));
C(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR));
C(glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,PIXEL_FORMAT,NULL));
C(furActiveTexture(GL_TEXTURE0));
t->width=width;
t->height=height;
return t;
}
bool FurnaceGUIRenderGL::destroyTexture(void* which) {
FurnaceGLTexture* t=(FurnaceGLTexture*)which;
C(glDeleteTextures(1,&t->id));
delete t;
return true;
}
void FurnaceGUIRenderGL::setTextureBlendMode(void* which, FurnaceGUIBlendMode mode) {
}
void FurnaceGUIRenderGL::setBlendMode(FurnaceGUIBlendMode mode) {
switch (mode) {
case GUI_BLEND_MODE_NONE:
C(glBlendFunc(GL_ONE,GL_ZERO));
C(glDisable(GL_BLEND));
break;
case GUI_BLEND_MODE_BLEND:
C(glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA));
C(glEnable(GL_BLEND));
break;
case GUI_BLEND_MODE_ADD:
C(glBlendFunc(GL_SRC_ALPHA,GL_ONE));
C(glEnable(GL_BLEND));
break;
case GUI_BLEND_MODE_MULTIPLY:
C(glBlendFunc(GL_ZERO,GL_SRC_COLOR));
C(glEnable(GL_BLEND));
break;
}
}
void FurnaceGUIRenderGL::clear(ImVec4 color) {
SDL_GL_MakeCurrent(sdlWin,context);
C(glClearColor(color.x,color.y,color.z,color.w));
C(glClear(GL_COLOR_BUFFER_BIT));
}
bool FurnaceGUIRenderGL::newFrame() {
ImGui_ImplOpenGL3_NewFrame();
return true;
}
void FurnaceGUIRenderGL::createFontsTexture() {
ImGui_ImplOpenGL3_CreateFontsTexture();
}
void FurnaceGUIRenderGL::destroyFontsTexture() {
ImGui_ImplOpenGL3_DestroyFontsTexture();
}
void FurnaceGUIRenderGL::renderGUI() {
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
void FurnaceGUIRenderGL::wipe(float alpha) {
C(glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA));
C(glEnable(GL_BLEND));
quadVertex[0][0]=-1.0f;
quadVertex[0][1]=-1.0f;
quadVertex[0][2]=0.0f;
quadVertex[1][0]=1.0f;
quadVertex[1][1]=-1.0f;
quadVertex[1][2]=0.0f;
quadVertex[2][0]=-1.0f;
quadVertex[2][1]=1.0f;
quadVertex[2][2]=0.0f;
quadVertex[3][0]=1.0f;
quadVertex[3][1]=1.0f;
quadVertex[3][2]=0.0f;
C(furBindBuffer(GL_ARRAY_BUFFER,quadBuf));
C(furBufferData(GL_ARRAY_BUFFER,sizeof(quadVertex),quadVertex,GL_STATIC_DRAW));
C(furVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,NULL));
C(furEnableVertexAttribArray(0));
C(furActiveTexture(GL_TEXTURE0));
C(glBindTexture(GL_TEXTURE_2D,0));
if (furUseProgram && furUniform1f) {
C(furUseProgram(sh_wipe_program));
C(furUniform1f(sh_wipe_uAlpha,alpha));
}
C(glDrawArrays(GL_TRIANGLE_STRIP,0,4));
}
void FurnaceGUIRenderGL::present() {
SDL_GL_SwapWindow(sdlWin);
C(glFlush());
}
bool FurnaceGUIRenderGL::getOutputSize(int& w, int& h) {
SDL_GL_GetDrawableSize(sdlWin,&w,&h);
return true;
}
int FurnaceGUIRenderGL::getWindowFlags() {
return SDL_WINDOW_OPENGL;
}
void FurnaceGUIRenderGL::preInit() {
#if defined(USE_GLES)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,0);
#elif defined(__APPLE__)
// not recommended...
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,2);
#else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS,0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,0);
#endif
SDL_GL_SetAttribute(SDL_GL_RED_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,24);
}
#define LOAD_PROC_MANDATORY(_v,_t,_s) \
_v=(_t)SDL_GL_GetProcAddress(_s); \
if (!_v) { \
logE(_s " not found"); \
return false; \
}
#define LOAD_PROC_OPTIONAL(_v,_t,_s) \
_v=(_t)SDL_GL_GetProcAddress(_s); \
if (!_v) { \
logW(_s " not found"); \
}
bool FurnaceGUIRenderGL::init(SDL_Window* win) {
sdlWin=win;
context=SDL_GL_CreateContext(win);
if (context==NULL) {
return false;
}
SDL_GL_MakeCurrent(win,context);
SDL_GL_SetSwapInterval(1);
LOAD_PROC_MANDATORY(furGenBuffers,PFNGLGENBUFFERSPROC,"glGenBuffers");
LOAD_PROC_MANDATORY(furBindBuffer,PFNGLBINDBUFFERPROC,"glBindBuffer");
LOAD_PROC_MANDATORY(furBufferData,PFNGLBUFFERDATAPROC,"glBufferData");
LOAD_PROC_MANDATORY(furVertexAttribPointer,PFNGLVERTEXATTRIBPOINTERPROC,"glVertexAttribPointer");
LOAD_PROC_MANDATORY(furEnableVertexAttribArray,PFNGLENABLEVERTEXATTRIBARRAYPROC,"glEnableVertexAttribArray");
LOAD_PROC_MANDATORY(furActiveTexture,PFNGLACTIVETEXTUREPROC,"glActiveTexture");
LOAD_PROC_OPTIONAL(furCreateShader,PFNGLCREATESHADERPROC,"glCreateShader");
LOAD_PROC_OPTIONAL(furShaderSource,PFNGLSHADERSOURCEPROC,"glShaderSource");
LOAD_PROC_OPTIONAL(furCompileShader,PFNGLCOMPILESHADERPROC,"glCompileShader");
LOAD_PROC_OPTIONAL(furGetShaderiv,PFNGLGETSHADERIVPROC,"glGetShaderiv");
LOAD_PROC_OPTIONAL(furAttachShader,PFNGLATTACHSHADERPROC,"glAttachShader");
LOAD_PROC_OPTIONAL(furBindAttribLocation,PFNGLBINDATTRIBLOCATIONPROC,"glBindAttribLocation");
LOAD_PROC_OPTIONAL(furCreateProgram,PFNGLCREATEPROGRAMPROC,"glCreateProgram");
LOAD_PROC_OPTIONAL(furLinkProgram,PFNGLLINKPROGRAMPROC,"glLinkProgram");
LOAD_PROC_OPTIONAL(furGetProgramiv,PFNGLGETPROGRAMIVPROC,"glGetProgramiv");
LOAD_PROC_OPTIONAL(furUseProgram,PFNGLUSEPROGRAMPROC,"glUseProgram");
LOAD_PROC_OPTIONAL(furGetUniformLocation,PFNGLGETUNIFORMLOCATIONPROC,"glGetUniformLocation");
LOAD_PROC_OPTIONAL(furUniform1f,PFNGLUNIFORM1FPROC,"glUniform1f");
LOAD_PROC_OPTIONAL(furGetShaderInfoLog,PFNGLGETSHADERINFOLOGPROC,"glGetShaderInfoLog");
if (createShader(sh_wipe_srcV,sh_wipe_srcF,sh_wipe_vertex,sh_wipe_fragment,sh_wipe_program)) {
sh_wipe_uAlpha=furGetUniformLocation(sh_wipe_program,"uAlpha");
}
C(furGenBuffers(1,&quadBuf));
return true;
}
void FurnaceGUIRenderGL::initGUI(SDL_Window* win) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui_ImplSDL2_InitForOpenGL(win,context);
ImGui_ImplOpenGL3_Init();
}
bool FurnaceGUIRenderGL::quit() {
if (context==NULL) return false;
SDL_GL_DeleteContext(context);
context=NULL;
return true;
}
void FurnaceGUIRenderGL::quitGUI() {
ImGui_ImplOpenGL3_Shutdown();
}

65
src/gui/render/renderGL.h Normal file
View file

@ -0,0 +1,65 @@
/**
* 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 "../gui.h"
class FurnaceGUIRenderGL: public FurnaceGUIRender {
SDL_GLContext context;
SDL_Window* sdlWin;
float quadVertex[4][3];
unsigned int quadBuf;
// SHADERS //
// -> wipe
int sh_wipe_vertex;
int sh_wipe_fragment;
int sh_wipe_program;
int sh_wipe_uAlpha;
bool createShader(const char* vertexS, const char* fragmentS, int& vertex, int& fragment, int& program);
public:
ImTextureID getTextureID(void* which);
bool lockTexture(void* which, void** data, int* pitch);
bool unlockTexture(void* which);
bool updateTexture(void* which, void* data, int pitch);
void* createTexture(bool dynamic, int width, int height);
bool destroyTexture(void* which);
void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode);
void setBlendMode(FurnaceGUIBlendMode mode);
void clear(ImVec4 color);
bool newFrame();
void createFontsTexture();
void destroyFontsTexture();
void renderGUI();
void wipe(float alpha);
void present();
bool getOutputSize(int& w, int& h);
int getWindowFlags();
void preInit();
bool init(SDL_Window* win);
void initGUI(SDL_Window* win);
void quitGUI();
bool quit();
FurnaceGUIRenderGL():
context(NULL),
sdlWin(NULL) {
memset(quadVertex,0,4*3*sizeof(float));
}
};

View file

@ -0,0 +1,147 @@
/**
* 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 "renderSDL.h"
#include "backends/imgui_impl_sdlrenderer2.h"
ImTextureID FurnaceGUIRenderSDL::getTextureID(void* which) {
return which;
}
bool FurnaceGUIRenderSDL::lockTexture(void* which, void** data, int* pitch) {
return SDL_LockTexture((SDL_Texture*)which,NULL,data,pitch)==0;
}
bool FurnaceGUIRenderSDL::unlockTexture(void* which) {
SDL_UnlockTexture((SDL_Texture*)which);
return true;
}
bool FurnaceGUIRenderSDL::updateTexture(void* which, void* data, int pitch) {
return SDL_UpdateTexture((SDL_Texture*)which,NULL,data,pitch)==0;
}
void* FurnaceGUIRenderSDL::createTexture(bool dynamic, int width, int height) {
return SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,dynamic?SDL_TEXTUREACCESS_STREAMING:SDL_TEXTUREACCESS_STATIC,width,height);
}
bool FurnaceGUIRenderSDL::destroyTexture(void* which) {
SDL_DestroyTexture((SDL_Texture*)which);
return true;
}
void FurnaceGUIRenderSDL::setTextureBlendMode(void* which, FurnaceGUIBlendMode mode) {
switch (mode) {
case GUI_BLEND_MODE_NONE:
SDL_SetTextureBlendMode((SDL_Texture*)which,SDL_BLENDMODE_NONE);
break;
case GUI_BLEND_MODE_BLEND:
SDL_SetTextureBlendMode((SDL_Texture*)which,SDL_BLENDMODE_BLEND);
break;
case GUI_BLEND_MODE_ADD:
SDL_SetTextureBlendMode((SDL_Texture*)which,SDL_BLENDMODE_ADD);
break;
case GUI_BLEND_MODE_MULTIPLY:
SDL_SetTextureBlendMode((SDL_Texture*)which,SDL_BLENDMODE_MOD);
break;
}
}
void FurnaceGUIRenderSDL::setBlendMode(FurnaceGUIBlendMode mode) {
switch (mode) {
case GUI_BLEND_MODE_NONE:
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_NONE);
break;
case GUI_BLEND_MODE_BLEND:
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND);
break;
case GUI_BLEND_MODE_ADD:
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_ADD);
break;
case GUI_BLEND_MODE_MULTIPLY:
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_MOD);
break;
}
}
void FurnaceGUIRenderSDL::clear(ImVec4 color) {
SDL_SetRenderDrawColor(sdlRend,color.x*255,color.y*255,color.z*255,color.w*255);
SDL_RenderClear(sdlRend);
}
bool FurnaceGUIRenderSDL::newFrame() {
return ImGui_ImplSDLRenderer2_NewFrame();
}
void FurnaceGUIRenderSDL::createFontsTexture() {
ImGui_ImplSDLRenderer2_CreateFontsTexture();
}
void FurnaceGUIRenderSDL::destroyFontsTexture() {
ImGui_ImplSDLRenderer2_DestroyFontsTexture();
}
void FurnaceGUIRenderSDL::renderGUI() {
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData());
}
void FurnaceGUIRenderSDL::wipe(float alpha) {
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(sdlRend,0,0,0,255*alpha);
SDL_RenderFillRect(sdlRend,NULL);
}
void FurnaceGUIRenderSDL::present() {
SDL_RenderPresent(sdlRend);
}
bool FurnaceGUIRenderSDL::getOutputSize(int& w, int& h) {
return SDL_GetRendererOutputSize(sdlRend,&w,&h)==0;
}
int FurnaceGUIRenderSDL::getWindowFlags() {
return 0;
}
void FurnaceGUIRenderSDL::preInit() {
}
bool FurnaceGUIRenderSDL::init(SDL_Window* win) {
sdlRend=SDL_CreateRenderer(win,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE);
return (sdlRend!=NULL);
}
void FurnaceGUIRenderSDL::initGUI(SDL_Window* win) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui_ImplSDL2_InitForSDLRenderer(win,sdlRend);
ImGui_ImplSDLRenderer2_Init(sdlRend);
}
void FurnaceGUIRenderSDL::quitGUI() {
ImGui_ImplSDLRenderer2_Shutdown();
}
bool FurnaceGUIRenderSDL::quit() {
if (sdlRend==NULL) return false;
SDL_DestroyRenderer(sdlRend);
sdlRend=NULL;
return true;
}

View file

@ -0,0 +1,49 @@
/**
* 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 "../gui.h"
class FurnaceGUIRenderSDL: public FurnaceGUIRender {
SDL_Renderer* sdlRend;
public:
ImTextureID getTextureID(void* which);
bool lockTexture(void* which, void** data, int* pitch);
bool unlockTexture(void* which);
bool updateTexture(void* which, void* data, int pitch);
void* createTexture(bool dynamic, int width, int height);
bool destroyTexture(void* which);
void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode);
void setBlendMode(FurnaceGUIBlendMode mode);
void clear(ImVec4 color);
bool newFrame();
void createFontsTexture();
void destroyFontsTexture();
void renderGUI();
void wipe(float alpha);
void present();
bool getOutputSize(int& w, int& h);
int getWindowFlags();
void preInit();
bool init(SDL_Window* win);
void initGUI(SDL_Window* win);
void quitGUI();
bool quit();
FurnaceGUIRenderSDL():
sdlRend(NULL) {}
};

View file

@ -133,6 +133,12 @@ void FurnaceGUI::drawSampleEdit() {
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);
@ -140,15 +146,23 @@ void FurnaceGUI::drawSampleEdit() {
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();
@ -176,6 +190,8 @@ void FurnaceGUI::drawSampleEdit() {
for (int i=0; i<DIV_MAX_CHIPS; i++) {
if (isChipVisible[i]) selColumns++;
}
int targetRate=sampleCompatRate?sample->rate:sample->centerRate;
if (ImGui::BeginTable("SampleProps",(selColumns>1)?4:3,ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_BordersV|ImGuiTableFlags_BordersOuterH)) {
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
@ -201,16 +217,21 @@ void FurnaceGUI::drawSampleEdit() {
}
popToggleColors();
ImGui::TableNextColumn();
bool doLoop=(sample->isLoopable());
bool doLoop=(sample->loop);
if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED
if (doLoop) {
sample->loop=true;
sample->loopStart=0;
sample->loopEnd=sample->samples;
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;
sample->loopEnd=sample->samples;*/
}
updateSampleTex=true;
if (e->getSampleFormatMask()&(1U<<DIV_SAMPLE_DEPTH_BRR)) {
@ -238,8 +259,7 @@ void FurnaceGUI::drawSampleEdit() {
if (ImGui::Selectable(sampleDepths[i])) {
sample->prepareUndo(true);
e->lockEngine([this,sample,i]() {
sample->render();
sample->depth=(DivSampleDepth)i;
sample->convert((DivSampleDepth)i);
e->renderSamples();
});
updateSampleTex=true;
@ -273,8 +293,20 @@ void FurnaceGUI::drawSampleEdit() {
}
}
}
if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && e->getSampleFormatMask()&(1L<<DIV_SAMPLE_DEPTH_8BIT)) {
bool di=sample->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 targetRate=sampleCompatRate?sample->rate:sample->centerRate;
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;
@ -608,7 +640,7 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::SameLine();
ImGui::Button(ICON_FA_EXPAND "##SResample");
if (ImGui::IsItemClicked()) {
resampleTarget=sample->rate;
resampleTarget=targetRate;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Resample");
@ -629,23 +661,23 @@ void FurnaceGUI::drawSampleEdit() {
}
ImGui::SameLine();
if (ImGui::Button("==")) {
resampleTarget=sample->rate;
resampleTarget=targetRate;
}
ImGui::SameLine();
if (ImGui::Button("2.0x")) {
resampleTarget*=2.0;
}
double factor=resampleTarget/(double)sample->rate;
double factor=resampleTarget/(double)targetRate;
if (ImGui::InputDouble("Factor",&factor,0.125,0.5,"%g")) {
resampleTarget=(double)sample->rate*factor;
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]() {
if (!sample->resample(resampleTarget,resampleStrat)) {
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();
@ -658,7 +690,7 @@ void FurnaceGUI::drawSampleEdit() {
}
ImGui::EndPopup();
} else {
resampleTarget=sample->rate;
resampleTarget=targetRate;
}
ImGui::SameLine();
ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale));
@ -1093,12 +1125,12 @@ void FurnaceGUI::drawSampleEdit() {
if (sampleTex==NULL || sampleTexW!=avail.x || sampleTexH!=avail.y) {
if (sampleTex!=NULL) {
SDL_DestroyTexture(sampleTex);
rend->destroyTexture(sampleTex);
sampleTex=NULL;
}
if (avail.x>=1 && avail.y>=1) {
logD("recreating sample texture.");
sampleTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STREAMING,avail.x,avail.y);
sampleTex=rend->createTexture(true,avail.x,avail.y);
sampleTexW=avail.x;
sampleTexH=avail.y;
if (sampleTex==NULL) {
@ -1114,7 +1146,7 @@ void FurnaceGUI::drawSampleEdit() {
unsigned int* dataT=NULL;
int pitch=0;
logD("updating sample texture.");
if (SDL_LockTexture(sampleTex,NULL,(void**)&dataT,&pitch)!=0) {
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];
@ -1199,19 +1231,54 @@ void FurnaceGUI::drawSampleEdit() {
}
}
memcpy(dataT,data,sampleTexW*sampleTexH*sizeof(unsigned int));
SDL_UnlockTexture(sampleTex);
if ((pitch>>2)==sampleTexW) {
memcpy(dataT,data,sampleTexW*sampleTexH*sizeof(unsigned int));
} else {
int srcY=0;
int destY=0;
for (int i=0; i<sampleTexH; i++) {
memcpy(&dataT[destY],&data[srcY],sampleTexW*sizeof(unsigned int));
srcY+=sampleTexW;
destY+=pitch>>2;
}
}
rend->unlockTexture(sampleTex);
delete[] data;
}
updateSampleTex=false;
}
ImGui::ImageButton(sampleTex,avail,ImVec2(0,0),ImVec2(1,1),0);
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)) {
@ -1238,9 +1305,23 @@ void FurnaceGUI::drawSampleEdit() {
}
sampleDragLen=sample->samples;
sampleDragActive=true;
sampleSelStart=-1;
sampleSelEnd=-1;
if (sampleDragMode) sample->prepareUndo(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);
}
}

View file

@ -543,6 +543,16 @@ void FurnaceGUI::drawSettings() {
settings.stepOnDelete=stepOnDeleteB;
}
bool insertBehaviorB=settings.insertBehavior;
if (ImGui::Checkbox("Insert pushes entire channel row",&insertBehaviorB)) {
settings.insertBehavior=insertBehaviorB;
}
bool pullDeleteRowB=settings.pullDeleteRow;
if (ImGui::Checkbox("Pull delete affects entire channel row",&pullDeleteRowB)) {
settings.pullDeleteRow=pullDeleteRowB;
}
bool absorbInsInputB=settings.absorbInsInput;
if (ImGui::Checkbox("Change current instrument when changing instrument column (absorb)",&absorbInsInputB)) {
settings.absorbInsInput=absorbInsInputB;
@ -626,6 +636,14 @@ void FurnaceGUI::drawSettings() {
ImGui::SetTooltip("saves power by lowering the frame rate to 2fps when idle.\nmay cause issues under Mesa drivers!");
}
bool renderClearPosB=settings.renderClearPos;
if (ImGui::Checkbox("Late render clear",&renderClearPosB)) {
settings.renderClearPos=renderClearPosB;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("calls rend->clear() after rend->present(). might reduce UI latency by one frame in some drivers.");
}
#ifndef IS_MOBILE
bool noThreadedInputB=settings.noThreadedInput;
if (ImGui::Checkbox("Disable threaded input (restart after changing!)",&noThreadedInputB)) {
@ -654,6 +672,22 @@ void FurnaceGUI::drawSettings() {
settings.saveUnusedPatterns=saveUnusedPatternsB;
}
bool compressB=settings.compress;
if (ImGui::Checkbox("Compress when saving",&compressB)) {
settings.compress=compressB;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("use zlib to compress saved songs.");
}
bool newPatternFormatB=settings.newPatternFormat;
if (ImGui::Checkbox("Use new pattern format when saving",&newPatternFormatB)) {
settings.newPatternFormat=newPatternFormatB;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("use a packed format which saves space when saving songs.\ndisable if you need compatibility with older Furnace and/or tools\nwhich do not support this format.");
}
bool cursorFollowsOrderB=settings.cursorFollowsOrder;
if (ImGui::Checkbox("Cursor follows current order when moving it",&cursorFollowsOrderB)) {
settings.cursorFollowsOrder=cursorFollowsOrderB;
@ -1146,14 +1180,38 @@ void FurnaceGUI::drawSettings() {
settings.midiOutMode=2;
}*/
bool midiOutProgramChangeB=settings.midiOutProgramChange;
if (ImGui::Checkbox("Send Program Change",&midiOutProgramChangeB)) {
settings.midiOutProgramChange=midiOutProgramChangeB;
}
bool midiOutClockB=settings.midiOutClock;
if (ImGui::Checkbox("Send MIDI clock",&midiOutClockB)) {
settings.midiOutClock=midiOutClockB;
}
bool midiOutProgramChangeB=settings.midiOutProgramChange;
if (ImGui::Checkbox("Send Program Change",&midiOutProgramChangeB)) {
settings.midiOutProgramChange=midiOutProgramChangeB;
bool midiOutTimeB=settings.midiOutTime;
if (ImGui::Checkbox("Send MIDI timecode",&midiOutTimeB)) {
settings.midiOutTime=midiOutTimeB;
}
if (settings.midiOutTime) {
ImGui::Text("Timecode frame rate:");
if (ImGui::RadioButton("Closest to Tick Rate",settings.midiOutTimeRate==0)) {
settings.midiOutTimeRate=0;
}
if (ImGui::RadioButton("Film (24fps)",settings.midiOutTimeRate==1)) {
settings.midiOutTimeRate=1;
}
if (ImGui::RadioButton("PAL (25fps)",settings.midiOutTimeRate==2)) {
settings.midiOutTimeRate=2;
}
if (ImGui::RadioButton("NTSC drop (29.97fps)",settings.midiOutTimeRate==3)) {
settings.midiOutTimeRate=3;
}
if (ImGui::RadioButton("NTSC non-drop (30fps)",settings.midiOutTimeRate==4)) {
settings.midiOutTimeRate=4;
}
}
ImGui::TreePop();
@ -1238,17 +1296,38 @@ void FurnaceGUI::drawSettings() {
ImVec2 settingsViewSize=ImGui::GetContentRegionAvail();
settingsViewSize.y-=ImGui::GetFrameHeight()+ImGui::GetStyle().WindowPadding.y;
if (ImGui::BeginChild("SettingsView",settingsViewSize)) {
if (ImGui::BeginCombo("Render driver",settings.renderDriver.empty()?"Automatic":settings.renderDriver.c_str())) {
if (ImGui::Selectable("Automatic",settings.renderDriver.empty())) {
settings.renderDriver="";
String curRenderBackend=settings.renderBackend.empty()?GUI_BACKEND_DEFAULT_NAME:settings.renderBackend;
if (ImGui::BeginCombo("Render backend",curRenderBackend.c_str())) {
#ifdef HAVE_RENDER_SDL
if (ImGui::Selectable("SDL Renderer",curRenderBackend=="SDL")) {
settings.renderBackend="SDL";
}
for (String& i: availRenderDrivers) {
if (ImGui::Selectable(i.c_str(),i==settings.renderDriver)) {
settings.renderDriver=i;
}
#endif
#ifdef HAVE_RENDER_DX11
if (ImGui::Selectable("DirectX 11",curRenderBackend=="DirectX 11")) {
settings.renderBackend="DirectX 11";
}
#endif
#ifdef HAVE_RENDER_GL
if (ImGui::Selectable("OpenGL",curRenderBackend=="OpenGL")) {
settings.renderBackend="OpenGL";
}
#endif
ImGui::EndCombo();
}
if (curRenderBackend=="SDL") {
if (ImGui::BeginCombo("Render driver",settings.renderDriver.empty()?"Automatic":settings.renderDriver.c_str())) {
if (ImGui::Selectable("Automatic",settings.renderDriver.empty())) {
settings.renderDriver="";
}
for (String& i: availRenderDrivers) {
if (ImGui::Selectable(i.c_str(),i==settings.renderDriver)) {
settings.renderDriver=i;
}
}
ImGui::EndCombo();
}
}
bool dpiScaleAuto=(settings.dpiScale<0.5f);
if (ImGui::Checkbox("Automatic UI scaling factor",&dpiScaleAuto)) {
@ -1811,13 +1890,33 @@ void FurnaceGUI::drawSettings() {
}
UI_COLOR_CONFIG(GUI_COLOR_BACKGROUND,"Background");
UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND,"Window background");
UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND_CHILD,"Sub-window background");
UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND_POPUP,"Pop-up background");
UI_COLOR_CONFIG(GUI_COLOR_MODAL_BACKDROP,"Modal backdrop");
UI_COLOR_CONFIG(GUI_COLOR_HEADER,"Header");
UI_COLOR_CONFIG(GUI_COLOR_TEXT,"Text");
UI_COLOR_CONFIG(GUI_COLOR_ACCENT_PRIMARY,"Primary");
UI_COLOR_CONFIG(GUI_COLOR_ACCENT_SECONDARY,"Secondary");
UI_COLOR_CONFIG(GUI_COLOR_TITLE_INACTIVE,"Title bar (inactive)");
UI_COLOR_CONFIG(GUI_COLOR_TITLE_COLLAPSED,"Title bar (collapsed)");
UI_COLOR_CONFIG(GUI_COLOR_MENU_BAR,"Menu bar");
UI_COLOR_CONFIG(GUI_COLOR_BORDER,"Border");
UI_COLOR_CONFIG(GUI_COLOR_BORDER_SHADOW,"Border shadow");
UI_COLOR_CONFIG(GUI_COLOR_SCROLL,"Scroll bar");
UI_COLOR_CONFIG(GUI_COLOR_SCROLL_HOVER,"Scroll bar (hovered)");
UI_COLOR_CONFIG(GUI_COLOR_SCROLL_ACTIVE,"Scroll bar (clicked)");
UI_COLOR_CONFIG(GUI_COLOR_SCROLL_BACKGROUND,"Scroll bar background");
UI_COLOR_CONFIG(GUI_COLOR_SEPARATOR,"Separator");
UI_COLOR_CONFIG(GUI_COLOR_SEPARATOR_HOVER,"Separator (hover)");
UI_COLOR_CONFIG(GUI_COLOR_SEPARATOR_ACTIVE,"Separator (active)");
UI_COLOR_CONFIG(GUI_COLOR_DOCKING_PREVIEW,"Docking preview");
UI_COLOR_CONFIG(GUI_COLOR_DOCKING_EMPTY,"Docking empty");
UI_COLOR_CONFIG(GUI_COLOR_TABLE_HEADER,"Table header");
UI_COLOR_CONFIG(GUI_COLOR_TABLE_BORDER_HARD,"Table border (hard)");
UI_COLOR_CONFIG(GUI_COLOR_TABLE_BORDER_SOFT,"Table border (soft)");
UI_COLOR_CONFIG(GUI_COLOR_DRAG_DROP_TARGET,"Drag and drop target");
UI_COLOR_CONFIG(GUI_COLOR_NAV_WIN_HIGHLIGHT,"Window switcher (highlight)");
UI_COLOR_CONFIG(GUI_COLOR_NAV_WIN_BACKDROP,"Window switcher backdrop");
UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_ON,"Toggle on");
UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_OFF,"Toggle off");
UI_COLOR_CONFIG(GUI_COLOR_EDITING,"Editing");
@ -2328,6 +2427,7 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_EDIT);
UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_UP);
UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_DOWN);
UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_DIR_VIEW);
KEYBIND_CONFIG_END;
ImGui::TreePop();
@ -2345,6 +2445,7 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_EDIT);
UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_UP);
UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DOWN);
UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DIR_VIEW);
KEYBIND_CONFIG_END;
ImGui::TreePop();
@ -2364,6 +2465,7 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DOWN);
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_PREVIEW);
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW);
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DIR_VIEW);
KEYBIND_CONFIG_END;
ImGui::TreePop();
@ -2630,8 +2732,10 @@ void FurnaceGUI::syncSettings() {
settings.channelTextCenter=e->getConfInt("channelTextCenter",1);
settings.maxRecentFile=e->getConfInt("maxRecentFile",10);
settings.midiOutClock=e->getConfInt("midiOutClock",0);
settings.midiOutTime=e->getConfInt("midiOutTime",0);
settings.midiOutProgramChange=e->getConfInt("midiOutProgramChange",0);
settings.midiOutMode=e->getConfInt("midiOutMode",1);
settings.midiOutTimeRate=e->getConfInt("midiOutTimeRate",0);
settings.centerPattern=e->getConfInt("centerPattern",0);
settings.ordersCursor=e->getConfInt("ordersCursor",1);
settings.persistFadeOut=e->getConfInt("persistFadeOut",1);
@ -2645,6 +2749,12 @@ void FurnaceGUI::syncSettings() {
settings.cursorFollowsOrder=e->getConfInt("cursorFollowsOrder",1);
settings.iCannotWait=e->getConfInt("iCannotWait",0);
settings.orderButtonPos=e->getConfInt("orderButtonPos",2);
settings.compress=e->getConfInt("compress",1);
settings.newPatternFormat=e->getConfInt("newPatternFormat",1);
settings.renderBackend=e->getConfString("renderBackend","SDL");
settings.renderClearPos=e->getConfInt("renderClearPos",0);
settings.insertBehavior=e->getConfInt("insertBehavior",1);
settings.pullDeleteRow=e->getConfInt("pullDeleteRow",1);
clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.patFontSize,2,96);
@ -2750,8 +2860,10 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.channelTextCenter,0,1);
clampSetting(settings.maxRecentFile,0,30);
clampSetting(settings.midiOutClock,0,1);
clampSetting(settings.midiOutTime,0,1);
clampSetting(settings.midiOutProgramChange,0,1);
clampSetting(settings.midiOutMode,0,2);
clampSetting(settings.midiOutTimeRate,0,4);
clampSetting(settings.centerPattern,0,1);
clampSetting(settings.ordersCursor,0,1);
clampSetting(settings.persistFadeOut,0,1);
@ -2763,6 +2875,11 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.cursorFollowsOrder,0,1);
clampSetting(settings.iCannotWait,0,1);
clampSetting(settings.orderButtonPos,0,2);
clampSetting(settings.compress,0,1);
clampSetting(settings.newPatternFormat,0,1);
clampSetting(settings.renderClearPos,0,1);
clampSetting(settings.insertBehavior,0,1);
clampSetting(settings.pullDeleteRow,0,1);
if (settings.exportLoops<0.0) settings.exportLoops=0.0;
if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0;
@ -2961,8 +3078,10 @@ void FurnaceGUI::commitSettings() {
e->setConf("channelTextCenter",settings.channelTextCenter);
e->setConf("maxRecentFile",settings.maxRecentFile);
e->setConf("midiOutClock",settings.midiOutClock);
e->setConf("midiOutTime",settings.midiOutTime);
e->setConf("midiOutProgramChange",settings.midiOutProgramChange);
e->setConf("midiOutMode",settings.midiOutMode);
e->setConf("midiOutTimeRate",settings.midiOutTimeRate);
e->setConf("centerPattern",settings.centerPattern);
e->setConf("ordersCursor",settings.ordersCursor);
e->setConf("persistFadeOut",settings.persistFadeOut);
@ -2976,6 +3095,12 @@ void FurnaceGUI::commitSettings() {
e->setConf("cursorFollowsOrder",settings.cursorFollowsOrder);
e->setConf("iCannotWait",settings.iCannotWait);
e->setConf("orderButtonPos",settings.orderButtonPos);
e->setConf("compress",settings.compress);
e->setConf("newPatternFormat",settings.newPatternFormat);
e->setConf("renderBackend",settings.renderBackend);
e->setConf("renderClearPos",settings.renderClearPos);
e->setConf("insertBehavior",settings.insertBehavior);
e->setConf("pullDeleteRow",settings.pullDeleteRow);
// colors
for (int i=0; i<GUI_COLOR_MAX; i++) {
@ -3015,17 +3140,21 @@ void FurnaceGUI::commitSettings() {
applyUISettings();
ImGui_ImplSDLRenderer_DestroyFontsTexture();
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error while building font atlas!");
showError("error while loading fonts! please check your settings.");
ImGui::GetIO().Fonts->Clear();
mainFont=ImGui::GetIO().Fonts->AddFontDefault();
patFont=mainFont;
ImGui_ImplSDLRenderer_DestroyFontsTexture();
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error again while building font atlas!");
} else {
rend->createFontsTexture();
}
} else {
rend->createFontsTexture();
}
}
@ -3494,7 +3623,28 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
}
sty.Colors[ImGuiCol_WindowBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND];
sty.Colors[ImGuiCol_ChildBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND_CHILD];
sty.Colors[ImGuiCol_PopupBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND_POPUP];
sty.Colors[ImGuiCol_TitleBg]=uiColors[GUI_COLOR_TITLE_INACTIVE];
sty.Colors[ImGuiCol_TitleBgCollapsed]=uiColors[GUI_COLOR_TITLE_COLLAPSED];
sty.Colors[ImGuiCol_MenuBarBg]=uiColors[GUI_COLOR_MENU_BAR];
sty.Colors[ImGuiCol_ModalWindowDimBg]=uiColors[GUI_COLOR_MODAL_BACKDROP];
sty.Colors[ImGuiCol_ScrollbarBg]=uiColors[GUI_COLOR_SCROLL_BACKGROUND];
sty.Colors[ImGuiCol_ScrollbarGrab]=uiColors[GUI_COLOR_SCROLL];
sty.Colors[ImGuiCol_ScrollbarGrabHovered]=uiColors[GUI_COLOR_SCROLL_HOVER];
sty.Colors[ImGuiCol_ScrollbarGrabActive]=uiColors[GUI_COLOR_SCROLL_ACTIVE];
sty.Colors[ImGuiCol_Separator]=uiColors[GUI_COLOR_SEPARATOR];
sty.Colors[ImGuiCol_SeparatorHovered]=uiColors[GUI_COLOR_SEPARATOR_HOVER];
sty.Colors[ImGuiCol_SeparatorActive]=uiColors[GUI_COLOR_SEPARATOR_ACTIVE];
sty.Colors[ImGuiCol_DockingPreview]=uiColors[GUI_COLOR_DOCKING_PREVIEW];
sty.Colors[ImGuiCol_DockingEmptyBg]=uiColors[GUI_COLOR_DOCKING_EMPTY];
sty.Colors[ImGuiCol_TableHeaderBg]=uiColors[GUI_COLOR_TABLE_HEADER];
sty.Colors[ImGuiCol_TableBorderStrong]=uiColors[GUI_COLOR_TABLE_BORDER_HARD];
sty.Colors[ImGuiCol_TableBorderLight]=uiColors[GUI_COLOR_TABLE_BORDER_SOFT];
sty.Colors[ImGuiCol_DragDropTarget]=uiColors[GUI_COLOR_DRAG_DROP_TARGET];
sty.Colors[ImGuiCol_NavHighlight]=uiColors[GUI_COLOR_NAV_HIGHLIGHT];
sty.Colors[ImGuiCol_NavWindowingHighlight]=uiColors[GUI_COLOR_NAV_WIN_HIGHLIGHT];
sty.Colors[ImGuiCol_NavWindowingDimBg]=uiColors[GUI_COLOR_NAV_WIN_BACKDROP];
sty.Colors[ImGuiCol_Text]=uiColors[GUI_COLOR_TEXT];
sty.Colors[ImGuiCol_Button]=primary;
@ -3710,7 +3860,8 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
}
mainFont->FallbackChar='?';
mainFont->DotChar='.';
mainFont->EllipsisChar='.';
mainFont->EllipsisCharCount=3;
}
// TODO: allow changing these colors.
@ -3732,6 +3883,10 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttc",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".mod",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fc13",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fc14",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fc",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".smod",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ftm",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);

View file

@ -56,7 +56,7 @@ void FurnaceGUI::drawSpeed(bool asChild) {
if (tempoView) setHz/=2.5;
if (setHz<1) setHz=1;
if (setHz>999) setHz=999;
e->setSongRate(setHz,setHz<52);
e->setSongRate(setHz);
}
if (tempoView) {
ImGui::SameLine();

Some files were not shown because too many files have changed in this diff Show more