Merge branch 'tildearrow:master' into wave-edit-tooltip

This commit is contained in:
freq-mod 2022-08-06 17:44:18 +02:00 committed by GitHub
commit b5fa556a38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 903 additions and 274 deletions

View file

@ -22,7 +22,8 @@ jobs:
- { name: 'Windows MSVC x86_64', os: windows-latest, compiler: msvc, arch: x86_64 }
- { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 }
- { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 }
- { name: 'macOS', os: macos-latest }
- { name: 'macOS x86_64', os: macos-latest, arch: x86_64 }
- { name: 'macOS ARM', os: macos-latest, arch: arm64 }
- { name: 'Ubuntu', os: ubuntu-18.04 }
fail-fast: false
@ -76,7 +77,7 @@ jobs:
package_name="${package_name}-${{ matrix.config.arch }}"
package_ext="" # Directory, uploading will automatically zip it
elif [ '${{ runner.os }}' == 'macOS' ]; then
package_name="${package_name}-macOS"
package_name="${package_name}-macOS-${{ matrix.config.arch }}"
package_ext=".dmg"
else
package_name="${package_name}-Linux"
@ -201,7 +202,11 @@ jobs:
elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake')
elif [ '${{ runner.os }}' == 'macOS' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"')
if [ '${{ matrix.config.arch }}' == 'arm64' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="11.0"' '-DCMAKE_OSX_ARCHITECTURES=arm64')
else
CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"')
fi
fi
cmake \

View file

@ -66,6 +66,8 @@ option(SYSTEM_RTMIDI "Use a system-installed version of RtMidi instead of the ve
option(SYSTEM_ZLIB "Use a system-installed version of zlib instead of the vendored one" OFF)
option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendored one" ${SYSTEM_SDL2_DEFAULT})
option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF)
option(WITH_DEMOS "Install demo songs" ON)
option(WITH_INSTRUMENTS "Install instruments" ON)
set(DEPENDENCIES_INCLUDE_DIRS "")
@ -709,8 +711,12 @@ if (NOT ANDROID OR TERMUX)
install(FILES res/furnace.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace)
install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
if (WITH_DEMOS)
install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
endif()
if (WITH_INSTRUMENTS)
install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
endif()
foreach(num 16 32 64 128 256 512)
set(res ${num}x${num})
install(FILES res/icon.iconset/icon_${res}.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}/apps)

View file

@ -203,6 +203,8 @@ Available options:
| `SYSTEM_ZLIB` | `OFF` | Use a system-installed version of zlib instead of the vendored one |
| `SYSTEM_SDL2` | `OFF` | Use a system-installed version of SDL2 instead of the vendored one |
| `WARNINGS_ARE_ERRORS` | `OFF` (but consider enabling this & reporting any errors that arise from it!) | Whether warnings in furnace's C++ code should be treated as errors |
| `WITH_DEMOS` | `ON` | Install demo songs on `make install` |
| `WITH_INSTRUMENTS` | `ON` | Install demo instruments on `make install` |
## console usage

Binary file not shown.

View file

@ -26,7 +26,8 @@ this is a list of systems that Furnace supports, including each system's effects
- [Seta/Allumer X1-010](x1-010.md)
- [WonderSwan](wonderswan.md)
- [Bubble System WSG](bubblesystem.md)
- [Namco 163](n163.md)
- [Namco C163](n163.md)
- [Namco WSG](namco.md)
- [Yamaha OPL](opl.md)
- [PC Speaker](pcspkr.md)
- [Commodore PET](pet.md)

View file

@ -25,7 +25,7 @@ This is a fantasy sound chip, used in the specs2 fantasy computer designed by ti
- `17xx`: set volume sweep period low byte
- `18xx`: set volume sweep period high byte
- `19xx`: set cutoff sweep period low byte
- `1Axx`: set cutoff sweep period low byte
- `1Axx`: set cutoff sweep period high byte
- `1Bxx`: set frequency sweep boundary
- `1Cxx`: set volume sweep boundary
- `1Dxx`: set cutoff sweep boundary

View file

@ -4,28 +4,23 @@
TODO
## pattern data
## macro data
read sequentially.
read length, loop and then release (1 byte).
if it is a 2-byte macro, read a dummy byte.
first byte determines what to read next:
then read data.
## binary command stream
read channel, command and values.
if channel is 80 or higher, then it is a special command:
```
NVI..EEE
N: note
V: volume
I: instrument
EEE: effect count (0-7)
fb xx xx xx xx: set tick rate
fc xx xx: wait xxxx ticks
fd xx: wait xx ticks
fe: wait one tick
ff: stop
```
if you read 0, end of pattern.
otherwise read in following order:
1. note
2. volume
3. instrument
4. effect and effect value
then read number of rows until next value, minus 1.

View file

@ -810,6 +810,9 @@ size | description
1 | vib depth
1 | am depth
23 | reserved
--- | **Sound Unit data** (>=104)
1 | use sample
1 | switch roles of phase reset timer and frequency
```
# wavetable

View file

@ -55,6 +55,18 @@ enum DivDispatchCmds {
DIV_CMD_PRE_PORTA, // (inPorta, isPortaOrSlide)
DIV_CMD_PRE_NOTE, // used in C64 (note)
// these will be used in ROM export.
// do NOT implement!
DIV_CMD_HINT_VIBRATO, // (speed, depth)
DIV_CMD_HINT_VIBRATO_RANGE, // (range)
DIV_CMD_HINT_VIBRATO_SHAPE, // (shape)
DIV_CMD_HINT_PITCH, // (pitch)
DIV_CMD_HINT_ARPEGGIO, // (note1, note2)
DIV_CMD_HINT_VOLUME, // (vol)
DIV_CMD_HINT_VOL_SLIDE, // (amount, oneTick)
DIV_CMD_HINT_PORTA, // (target, speed)
DIV_CMD_HINT_LEGATO, // (note)
DIV_CMD_SAMPLE_MODE, // (enabled)
DIV_CMD_SAMPLE_FREQ, // (frequency)
DIV_CMD_SAMPLE_BANK, // (bank)
@ -399,6 +411,12 @@ class DivDispatch {
*/
virtual bool getDCOffRequired();
/**
* check whether PRE_NOTE command is desired.
* @return truth.
*/
virtual bool getWantPreNote();
/**
* get a description of a dispatch-specific effect.
* @param effect the effect.

View file

@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "dispatch.h"
#define _USE_MATH_DEFINES
#include "engine.h"
#include "instrument.h"
@ -234,6 +235,251 @@ double DivEngine::benchmarkSeek() {
return tAvg;
}
#define WRITE_TICK \
if (!wroteTick) { \
wroteTick=true; \
if (binary) { \
if (tick-lastTick>255) { \
w->writeC(0xfc); \
w->writeS(tick-lastTick); \
} else if (tick-lastTick>1) { \
w->writeC(0xfd); \
w->writeC(tick-lastTick); \
} else { \
w->writeC(0xfe); \
} \
} else { \
w->writeText(fmt::sprintf(">> TICK %d\n",tick)); \
} \
lastTick=tick; \
}
void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
w->writeC(c.cmd);
switch (c.cmd) {
case DIV_CMD_NOTE_ON:
case DIV_CMD_HINT_LEGATO:
if (c.value==DIV_NOTE_NULL) {
w->writeC(0xff);
} else {
w->writeC(c.value+60);
}
break;
case DIV_CMD_NOTE_OFF:
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
break;
case DIV_CMD_INSTRUMENT:
case DIV_CMD_HINT_VIBRATO_RANGE:
case DIV_CMD_HINT_VIBRATO_SHAPE:
case DIV_CMD_HINT_PITCH:
case DIV_CMD_HINT_VOLUME:
case DIV_CMD_SAMPLE_MODE:
case DIV_CMD_SAMPLE_FREQ:
case DIV_CMD_SAMPLE_BANK:
case DIV_CMD_SAMPLE_POS:
case DIV_CMD_SAMPLE_DIR:
case DIV_CMD_FM_HARD_RESET:
case DIV_CMD_FM_LFO:
case DIV_CMD_FM_LFO_WAVE:
case DIV_CMD_FM_FB:
case DIV_CMD_FM_EXTCH:
case DIV_CMD_FM_AM_DEPTH:
case DIV_CMD_FM_PM_DEPTH:
case DIV_CMD_STD_NOISE_FREQ:
case DIV_CMD_STD_NOISE_MODE:
case DIV_CMD_WAVE:
case DIV_CMD_GB_SWEEP_TIME:
case DIV_CMD_GB_SWEEP_DIR:
case DIV_CMD_PCE_LFO_MODE:
case DIV_CMD_PCE_LFO_SPEED:
case DIV_CMD_NES_DMC:
case DIV_CMD_C64_CUTOFF:
case DIV_CMD_C64_RESONANCE:
case DIV_CMD_C64_FILTER_MODE:
case DIV_CMD_C64_RESET_TIME:
case DIV_CMD_C64_RESET_MASK:
case DIV_CMD_C64_FILTER_RESET:
case DIV_CMD_C64_DUTY_RESET:
case DIV_CMD_C64_EXTENDED:
case DIV_CMD_AY_ENVELOPE_SET:
case DIV_CMD_AY_ENVELOPE_LOW:
case DIV_CMD_AY_ENVELOPE_HIGH:
case DIV_CMD_AY_ENVELOPE_SLIDE:
case DIV_CMD_AY_NOISE_MASK_AND:
case DIV_CMD_AY_NOISE_MASK_OR:
case DIV_CMD_AY_AUTO_ENVELOPE:
w->writeC(c.value);
break;
case DIV_CMD_PANNING:
case DIV_CMD_HINT_VIBRATO:
case DIV_CMD_HINT_ARPEGGIO:
case DIV_CMD_HINT_PORTA:
case DIV_CMD_FM_TL:
case DIV_CMD_FM_AM:
case DIV_CMD_FM_AR:
case DIV_CMD_FM_DR:
case DIV_CMD_FM_SL:
case DIV_CMD_FM_D2R:
case DIV_CMD_FM_RR:
case DIV_CMD_FM_DT:
case DIV_CMD_FM_DT2:
case DIV_CMD_FM_RS:
case DIV_CMD_FM_KSR:
case DIV_CMD_FM_VIB:
case DIV_CMD_FM_SUS:
case DIV_CMD_FM_WS:
case DIV_CMD_FM_SSG:
case DIV_CMD_FM_REV:
case DIV_CMD_FM_EG_SHIFT:
case DIV_CMD_FM_MULT:
case DIV_CMD_FM_FINE:
case DIV_CMD_AY_IO_WRITE:
case DIV_CMD_AY_AUTO_PWM:
w->writeC(c.value);
w->writeC(c.value2);
break;
case DIV_CMD_PRE_PORTA:
w->writeC((c.value?0x80:0)|(c.value2?0x40:0));
break;
case DIV_CMD_HINT_VOL_SLIDE:
case DIV_CMD_C64_FINE_DUTY:
case DIV_CMD_C64_FINE_CUTOFF:
w->writeS(c.value);
break;
case DIV_CMD_FM_FIXFREQ:
w->writeS((c.value<<12)|(c.value2&0x7ff));
break;
case DIV_CMD_NES_SWEEP:
w->writeC((c.value?8:0)|(c.value2&0x77));
break;
default:
logW("unimplemented command %s!",cmdName[c.cmd]);
break;
}
}
SafeWriter* DivEngine::saveCommand(bool binary) {
stop();
repeatPattern=false;
setOrder(0);
BUSY_BEGIN_SOFT;
// determine loop point
int loopOrder=0;
int loopRow=0;
int loopEnd=0;
walkSong(loopOrder,loopRow,loopEnd);
logI("loop point: %d %d",loopOrder,loopRow);
SafeWriter* w=new SafeWriter;
w->init();
// write header
if (binary) {
w->write("FCS",4);
} else {
w->writeText("# Furnace Command Stream\n\n");
w->writeText("[Information]\n");
w->writeText(fmt::sprintf("name: %s\n",song.name));
w->writeText(fmt::sprintf("author: %s\n",song.author));
w->writeText(fmt::sprintf("category: %s\n",song.category));
w->writeText(fmt::sprintf("system: %s\n",song.systemName));
w->writeText("\n");
w->writeText("[SubSongInformation]\n");
w->writeText(fmt::sprintf("name: %s\n",curSubSong->name));
w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz));
w->writeText("\n");
w->writeText("[SysDefinition]\n");
// TODO
w->writeText("\n");
}
// play the song ourselves
bool done=false;
playSub(false);
if (!binary) {
w->writeText("[Stream]\n");
}
int tick=0;
bool oldCmdStreamEnabled=cmdStreamEnabled;
cmdStreamEnabled=true;
double curDivider=divider;
int lastTick=0;
while (!done) {
if (nextTick(false,true) || !playing) {
done=true;
}
// get command stream
bool wroteTick=false;
if (curDivider!=divider) {
curDivider=divider;
WRITE_TICK;
if (binary) {
w->writeC(0xfb);
w->writeI((int)(curDivider*65536));
} else {
w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider));
}
}
for (DivCommand& i: cmdStream) {
switch (i.cmd) {
// strip away hinted/useless commands
case DIV_ALWAYS_SET_VOLUME:
break;
case DIV_CMD_GET_VOLUME:
break;
case DIV_CMD_VOLUME:
break;
case DIV_CMD_NOTE_PORTA:
break;
case DIV_CMD_LEGATO:
break;
case DIV_CMD_PITCH:
break;
case DIV_CMD_PRE_NOTE:
break;
default:
WRITE_TICK;
if (binary) {
w->writeC(i.chan);
writePackedCommandValues(w,i);
} else {
w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2));
}
break;
}
}
cmdStream.clear();
tick++;
}
cmdStreamEnabled=oldCmdStreamEnabled;
if (binary) {
w->writeC(0xff);
} else {
if (!playing) {
w->writeText(">> END\n");
} else {
w->writeText(">> LOOP 0\n");
}
}
remainingLoops=-1;
playing=false;
freelance=false;
extValuePresent=false;
BUSY_END;
return w;
}
void _runExportThread(DivEngine* caller) {
caller->runExportThread();
}

View file

@ -45,8 +45,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev103"
#define DIV_ENGINE_VERSION 103
#define DIV_VERSION "dev104"
#define DIV_ENGINE_VERSION 104
// for imports
#define DIV_VERSION_MOD 0xff01
@ -280,6 +280,8 @@ enum DivChanTypes {
DIV_CH_OP=5
};
extern const char* cmdName[];
class DivEngine {
DivDispatchContainer disCont[32];
TAAudio* output;
@ -472,7 +474,9 @@ class DivEngine {
// specify system to build ROM for.
SafeWriter* buildROM(int sys);
// dump to VGM.
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171);
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false);
// dump command stream.
SafeWriter* saveCommand(bool binary=false);
// export to an audio file
bool saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime=0.0);
// wait for audio export to finish

View file

@ -528,6 +528,10 @@ void DivInstrument::putInsData(SafeWriter* w) {
w->writeC(0);
}
// Sound Unit
w->writeC(su.useSample);
w->writeC(su.switchRoles);
blockEndSeek=w->tell();
w->seek(blockStartSeek,SEEK_SET);
w->writeI(blockEndSeek-blockStartSeek-4);
@ -1075,6 +1079,12 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
for (int k=0; k<23; k++) reader.readC();
}
// Sound Unit
if (version>=104) {
su.useSample=reader.readC();
su.switchRoles=reader.readC();
}
return DIV_DATA_SUCCESS;
}

View file

@ -437,6 +437,14 @@ struct DivInstrumentWaveSynth {
param4(0) {}
};
struct DivInstrumentSoundUnit {
bool useSample;
bool switchRoles;
DivInstrumentSoundUnit():
useSample(false),
switchRoles(false) {}
};
struct DivInstrument {
String name;
bool mode;
@ -450,6 +458,7 @@ struct DivInstrument {
DivInstrumentFDS fds;
DivInstrumentMultiPCM multipcm;
DivInstrumentWaveSynth ws;
DivInstrumentSoundUnit su;
/**
* save the instrument to a SafeWriter.

View file

@ -90,6 +90,10 @@ bool DivDispatch::getDCOffRequired() {
return false;
}
bool DivDispatch::getWantPreNote() {
return false;
}
const char* DivDispatch::getEffectName(unsigned char effect) {
return NULL;
}

View file

@ -513,6 +513,10 @@ bool DivPlatformC64::getDCOffRequired() {
return true;
}
bool DivPlatformC64::getWantPreNote() {
return true;
}
void DivPlatformC64::reset() {
for (int i=0; i<3; i++) {
chan[i]=DivPlatformC64::Channel();

View file

@ -97,6 +97,7 @@ class DivPlatformC64: public DivDispatch {
void setFlags(unsigned int flags);
void notifyInsChange(int ins);
bool getDCOffRequired();
bool getWantPreNote();
DivMacroInt* getChanMacroInt(int ch);
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);

View file

@ -97,10 +97,11 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len)
void DivPlatformGB::updateWave() {
rWrite(0x1a,0);
for (int i=0; i<16; i++) {
int nibble1=15-ws.output[i<<1];
int nibble2=15-ws.output[1+(i<<1)];
int nibble1=15-ws.output[((i<<1)+antiClickWavePos)&31];
int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos)&31];
rWrite(0x30+i,(nibble1<<4)|nibble2);
}
antiClickWavePos&=31;
}
static unsigned char chanMuteMask[4]={
@ -151,6 +152,12 @@ static unsigned char noiseTable[256]={
};
void DivPlatformGB::tick(bool sysTick) {
if (antiClickEnabled && sysTick && chan[2].freq>0) {
antiClickPeriodCount+=((chipClock>>1)/MAX(parent->getCurHz(),1.0f));
antiClickWavePos+=antiClickPeriodCount/chan[2].freq;
antiClickPeriodCount%=chan[2].freq;
}
for (int i=0; i<4; i++) {
chan[i].std.next();
if (chan[i].std.arp.had) {
@ -213,6 +220,10 @@ void DivPlatformGB::tick(bool sysTick) {
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].keyOn=true;
if (i==2) {
antiClickWavePos=0;
antiClickPeriodCount=0;
}
}
}
if (i==2) {
@ -471,6 +482,9 @@ void DivPlatformGB::reset() {
lastPan=0xff;
immWrite(0x25,procMute());
immWrite(0x24,0x77);
antiClickPeriodCount=0;
antiClickWavePos=0;
}
bool DivPlatformGB::isStereo() {
@ -507,6 +521,10 @@ void DivPlatformGB::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
}
void DivPlatformGB::setFlags(unsigned int flags) {
antiClickEnabled=!(flags&8);
}
int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
chipClock=4194304;
rate=chipClock/16;
@ -519,6 +537,7 @@ int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int fl
dumpWrites=false;
skipRegisterWrites=false;
gb=new GB_gameboy_t;
setFlags(flags);
reset();
return 4;
}

View file

@ -59,9 +59,12 @@ class DivPlatformGB: public DivDispatch {
Channel chan[4];
DivDispatchOscBuffer* oscBuf[4];
bool isMuted[4];
bool antiClickEnabled;
unsigned char lastPan;
DivWaveSynth ws;
int antiClickPeriodCount, antiClickWavePos;
GB_gameboy_t* gb;
unsigned char regPool[128];
@ -88,6 +91,7 @@ class DivPlatformGB: public DivDispatch {
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
void setFlags(unsigned int flags);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformGB();

View file

@ -24,6 +24,8 @@
#define CHIP_FREQBASE fmFreqBase
#define CHIP_DIVIDER fmDivBase
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
int DivPlatformGenesisExt::dispatch(DivCommand c) {
if (c.chan<2) {
return DivPlatformGenesis::dispatch(c);
@ -542,7 +544,7 @@ void DivPlatformGenesisExt::forceIns() {
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
if (chan[i].active) {
chan[i].keyOn=true;
chan[i].freqChanged=true;

View file

@ -133,14 +133,22 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
}
void DivPlatformPCE::updateWave(int ch) {
if (chan[ch].pcm) {
chan[ch].deferredWaveUpdate=true;
return;
}
chWrite(ch,0x04,0x5f);
chWrite(ch,0x04,0x1f);
for (int i=0; i<32; i++) {
chWrite(ch,0x06,chan[ch].ws.output[i]);
chWrite(ch,0x06,chan[ch].ws.output[(i+chan[ch].antiClickWavePos)&31]);
}
chan[ch].antiClickWavePos&=31;
if (chan[ch].active) {
chWrite(ch,0x04,0x80|chan[ch].outVol);
}
if (chan[ch].deferredWaveUpdate) {
chan[ch].deferredWaveUpdate=false;
}
}
// TODO: in octave 6 the noise table changes to a tonal one
@ -150,6 +158,13 @@ static unsigned char noiseFreq[12]={
void DivPlatformPCE::tick(bool sysTick) {
for (int i=0; i<6; i++) {
// anti-click
if (antiClickEnabled && sysTick && chan[i].freq>0) {
chan[i].antiClickPeriodCount+=(chipClock/MAX(parent->getCurHz(),1.0f));
chan[i].antiClickWavePos+=chan[i].antiClickPeriodCount/chan[i].freq;
chan[i].antiClickPeriodCount%=chan[i].freq;
}
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=VOL_SCALE_LOG(chan[i].vol&31,MIN(31,chan[i].std.vol.val),31);
@ -218,8 +233,12 @@ void DivPlatformPCE::tick(bool sysTick) {
}
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
chan[i].antiClickWavePos=0;
chan[i].antiClickPeriodCount=0;
}
if (chan[i].active) {
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) {
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) || chan[i].deferredWaveUpdate) {
updateWave(i);
}
}
@ -555,10 +574,18 @@ void DivPlatformPCE::setFlags(unsigned int flags) {
} else {
chipClock=COLOR_NTSC;
}
// flags&4 will be chip revision
antiClickEnabled=!(flags&8);
rate=chipClock/12;
for (int i=0; i<6; i++) {
oscBuf[i]->rate=rate;
}
if (pce!=NULL) {
delete pce;
pce=NULL;
}
pce=new PCE_PSG(tempL,tempR,(flags&4)?PCE_PSG::REVISION_HUC6280A:PCE_PSG::REVISION_HUC6280);
}
void DivPlatformPCE::poke(unsigned int addr, unsigned short val) {
@ -577,8 +604,8 @@ int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate, unsigned int f
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
pce=NULL;
setFlags(flags);
pce=new PCE_PSG(tempL,tempR,PCE_PSG::REVISION_HUC6280A);
reset();
return 6;
}
@ -587,7 +614,10 @@ void DivPlatformPCE::quit() {
for (int i=0; i<6; i++) {
delete oscBuf[i];
}
delete pce;
if (pce!=NULL) {
delete pce;
pce=NULL;
}
}
DivPlatformPCE::~DivPlatformPCE() {

View file

@ -28,12 +28,12 @@
class DivPlatformPCE: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2, note;
int freq, baseFreq, pitch, pitch2, note, antiClickPeriodCount, antiClickWavePos;
int dacPeriod, dacRate;
unsigned int dacPos;
int dacSample, ins;
unsigned char pan;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac, deferredWaveUpdate;
signed char vol, outVol, wave;
DivMacroInt std;
DivWaveSynth ws;
@ -47,6 +47,8 @@ class DivPlatformPCE: public DivDispatch {
pitch(0),
pitch2(0),
note(0),
antiClickPeriodCount(0),
antiClickWavePos(0),
dacPeriod(0),
dacRate(0),
dacPos(0),
@ -62,6 +64,7 @@ class DivPlatformPCE: public DivDispatch {
noise(false),
pcm(false),
furnaceDac(false),
deferredWaveUpdate(false),
vol(31),
outVol(31),
wave(-1) {}
@ -69,6 +72,7 @@ class DivPlatformPCE: public DivDispatch {
Channel chan[6];
DivDispatchOscBuffer* oscBuf[6];
bool isMuted[6];
bool antiClickEnabled;
struct QueuedWrite {
unsigned char addr;
unsigned char val;

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900

View file

@ -26,37 +26,11 @@
#include <algorithm>
#include <limits>
namespace Lynx
{
#if defined( _MSC_VER )
namespace
{
#include <intrin.h>
static constexpr int64_t CNT_MAX = std::numeric_limits<int64_t>::max() & ~15;
#if defined ( __cpp_lib_bitops )
#define popcnt(X) std::popcount(X)
#elif defined( _MSC_VER )
# include <intrin.h>
uint32_t popcnt( uint32_t x )
{
return __popcnt( x );
}
#elif defined( __GNUC__ )
uint32_t popcnt( uint32_t x )
{
return __builtin_popcount( x );
}
#else
uint32_t popcnt( uint32_t x )
static uint32_t popcnt_generic( uint32_t x )
{
int v = 0;
while ( x != 0 )
@ -67,8 +41,61 @@ uint32_t popcnt( uint32_t x )
return v;
}
#if defined( _M_IX86 ) || defined( _M_X64 )
static uint32_t popcnt_intrinsic( uint32_t x )
{
return __popcnt( x );
}
static uint32_t( *popcnt )( uint32_t );
//detecting popcnt availability on msvc intel
static void selectPOPCNT()
{
int info[4];
__cpuid( info, 1 );
if ( ( info[2] & ( (int)1 << 23 ) ) != 0 )
{
popcnt = &popcnt_intrinsic;
}
else
{
popcnt = &popcnt_generic;
}
}
#else //defined( _M_IX86 ) || defined( _M_X64 )
//MSVC non INTEL should use generic implementation
inline void selectPOPCNT()
{
}
#define popcnt popcnt_generic
#endif
#else //defined( _MSC_VER )
//non MVSC should use builtin implementation
inline void selectPOPCNT()
{
}
#define popcnt __builtin_popcount
#endif
namespace Lynx
{
namespace
{
static constexpr int64_t CNT_MAX = std::numeric_limits<int64_t>::max() & ~15;
int32_t clamp( int32_t v, int32_t lo, int32_t hi )
{
return v < lo ? lo : ( v > hi ? hi : v );
@ -513,6 +540,7 @@ private:
Mikey::Mikey( uint32_t sampleRate ) : mMikey{ std::make_unique<MikeyPimpl>() }, mQueue{ std::make_unique<ActionQueue>() }, mTick{}, mNextTick{}, mSampleRate{ sampleRate }, mSamplesRemainder{}, mTicksPerSample{ 16000000 / mSampleRate, 16000000 % mSampleRate }
{
selectPOPCNT();
enqueueSampling();
}

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: tildearrow

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: tildearrow

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: tildearrow

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: tildearrow

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst

View file

@ -54,7 +54,7 @@ void SoundUnit::NextSample(short* l, short* r) {
chan[i].pcmpos=chan[i].pcmrst;
}
}
chan[i].pcmpos&=(SOUNDCHIP_PCM_SIZE-1);
chan[i].pcmpos&=(pcmSize-1);
} else if (chan[i].flags.pcmloop) {
chan[i].pcmpos=chan[i].pcmrst;
}
@ -228,9 +228,10 @@ void SoundUnit::NextSample(short* l, short* r) {
*r=minval(32767,maxval(-32767,tnsR));
}
void SoundUnit::Init() {
void SoundUnit::Init(int sampleMemSize) {
pcmSize=sampleMemSize;
Reset();
memset(pcm,0,SOUNDCHIP_PCM_SIZE);
memset(pcm,0,pcmSize);
for (int i=0; i<256; i++) {
SCsine[i]=sin((i/128.0f)*M_PI)*127;
SCtriangle[i]=(i>127)?(255-i):(i);
@ -242,9 +243,6 @@ void SoundUnit::Init() {
SCpantabR[128+i]=i-1;
}
SCpantabR[128]=0;
for (int i=0; i<8; i++) {
muted[i]=false;
}
}
void SoundUnit::Reset() {
@ -282,6 +280,8 @@ void SoundUnit::Write(unsigned char addr, unsigned char data) {
}
SoundUnit::SoundUnit() {
Init();
memset(pcm,0,SOUNDCHIP_PCM_SIZE);
Init(65536); // default
for (int i=0; i<8; i++) {
muted[i]=false;
}
}

View file

@ -3,8 +3,6 @@
#include <math.h>
#include <assert.h>
#define SOUNDCHIP_PCM_SIZE 8192
class SoundUnit {
signed char SCsine[256];
signed char SCtriangle[256];
@ -24,6 +22,7 @@ class SoundUnit {
int tnsL, tnsR;
unsigned short oldfreq[8];
unsigned short oldflags[8];
unsigned int pcmSize;
public:
unsigned short resetfreq[8];
unsigned short voldcycles[8];
@ -84,7 +83,7 @@ class SoundUnit {
unsigned short wc;
unsigned short restimer;
} chan[8];
signed char pcm[SOUNDCHIP_PCM_SIZE];
signed char pcm[65536];
bool muted[8];
void Write(unsigned char addr, unsigned char data);
void NextSample(short* l, short* r);
@ -94,7 +93,7 @@ class SoundUnit {
if (ret>32767) ret=32767;
return ret;
}
void Init();
void Init(int sampleMemSize=8192);
void Reset();
SoundUnit();
};

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow

View file

@ -1,6 +1,6 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow

View file

@ -256,12 +256,12 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SU);
if (chan[c.chan].pcm && ins->type!=DIV_INS_AMIGA) {
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA);
if (chan[c.chan].pcm && !(ins->type==DIV_INS_AMIGA || ins->su.useSample)) {
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample);
writeControl(c.chan);
writeControlUpper(c.chan);
}
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA);
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].freqChanged=true;
@ -555,6 +555,11 @@ void DivPlatformSoundUnit::setFlags(unsigned int flags) {
for (int i=0; i<8; i++) {
oscBuf[i]->rate=rate;
}
sampleMemSize=flags&16;
su->Init(sampleMemSize?65536:8192);
renderSamples();
}
void DivPlatformSoundUnit::poke(unsigned int addr, unsigned short val) {
@ -570,7 +575,7 @@ const void* DivPlatformSoundUnit::getSampleMem(int index) {
}
size_t DivPlatformSoundUnit::getSampleMemCapacity(int index) {
return (index==0)?8192:0;
return (index==0)?(sampleMemSize?65536:8192):0;
}
size_t DivPlatformSoundUnit::getSampleMemUsage(int index) {
@ -583,6 +588,7 @@ void DivPlatformSoundUnit::renderSamples() {
size_t memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
if (s->data8==NULL) continue;
int paddedLen=s->samples;
if (memPos>=getSampleMemCapacity(0)) {
logW("out of PCM memory for sample %d!",i);
@ -609,9 +615,8 @@ int DivPlatformSoundUnit::init(DivEngine* p, int channels, int sugRate, unsigned
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
su=new SoundUnit();
su->Init();
setFlags(flags);
reset();
return 6;
}

View file

@ -96,6 +96,7 @@ class DivPlatformSoundUnit: public DivDispatch {
};
std::queue<QueuedWrite> writes;
unsigned char lastPan;
bool sampleMemSize;
int cycles, curChan, delay;
short tempL;

View file

@ -57,6 +57,16 @@ const char* cmdName[]={
"PRE_PORTA",
"PRE_NOTE",
"HINT_VIBRATO",
"HINT_VIBRATO_RANGE",
"HINT_VIBRATO_SHAPE",
"HINT_PITCH",
"HINT_ARPEGGIO",
"HINT_VOLUME",
"HINT_VOL_SLIDE",
"HINT_PORTA",
"HINT_LEGATO",
"SAMPLE_MODE",
"SAMPLE_FREQ",
"SAMPLE_BANK",
@ -330,14 +340,15 @@ void DivEngine::processRow(int i, bool afterDelay) {
// instrument
bool insChanged=false;
if (pat->data[whatRow][2]!=-1) {
dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2]));
if (chan[i].lastIns!=pat->data[whatRow][2]) {
dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2]));
chan[i].lastIns=pat->data[whatRow][2];
insChanged=true;
if (song.legacyVolumeSlides && chan[i].volume==chan[i].volMax+1) {
logV("forcing volume");
chan[i].volume=chan[i].volMax;
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
}
}
}
@ -350,11 +361,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (chan[i].stopOnOff) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
chan[i].stopOnOff=false;
}
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
/*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) {
chan[i+1].portaNote=-1;
chan[i+1].portaSpeed=-1;
@ -371,11 +384,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (chan[i].stopOnOff) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
chan[i].stopOnOff=false;
}
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
/*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) {
chan[i+1].portaNote=-1;
chan[i+1].portaSpeed=-1;
@ -392,6 +407,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (!chan[i].keyOn) {
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsArp(dispatchChanOfChan[i])) {
chan[i].arp=0;
dispatchCmd(DivCommand(DIV_CMD_HINT_ARPEGGIO,i,chan[i].arp));
}
}
chan[i].doNote=true;
@ -408,6 +424,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
}
chan[i].volume=pat->data[whatRow][3]<<8;
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
}
}
@ -452,11 +469,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (effectVal==0) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
chan[i].inPorta=false;
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
} else {
chan[i].portaNote=song.limitSlides?0x60:255;
chan[i].portaSpeed=effectVal;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
chan[i].portaStop=true;
chan[i].nowYouCanStop=false;
chan[i].stopOnOff=false;
@ -472,11 +491,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (effectVal==0) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
chan[i].inPorta=false;
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
} else {
chan[i].portaNote=song.limitSlides?disCont[dispatchOfChan[i]].dispatch->getPortaFloor(dispatchChanOfChan[i]):-60;
chan[i].portaSpeed=effectVal;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
chan[i].portaStop=true;
chan[i].nowYouCanStop=false;
chan[i].stopOnOff=false;
@ -490,6 +511,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (effectVal==0) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
chan[i].inPorta=false;
dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
} else {
@ -503,6 +525,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
chan[i].inPorta=true;
chan[i].wasShorthandPorta=false;
}
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
chan[i].portaStop=true;
if (chan[i].keyOn) chan[i].doNote=false;
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
@ -514,6 +537,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
case 0x04: // vibrato
chan[i].vibratoDepth=effectVal&15;
chan[i].vibratoRate=effectVal>>4;
dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate));
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
break;
case 0x07: // tremolo
@ -537,12 +561,14 @@ void DivEngine::processRow(int i, bool afterDelay) {
} else {
chan[i].volSpeed=0;
}
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
break;
case 0x00: // arpeggio
chan[i].arp=effectVal;
if (chan[i].arp==0 && song.arp0Reset) {
chan[i].resetArp=true;
}
dispatchCmd(DivCommand(DIV_CMD_HINT_ARPEGGIO,i,chan[i].arp));
break;
case 0x0c: // retrigger
if (effectVal!=0) {
@ -574,6 +600,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
case 0xe1: // portamento up
chan[i].portaNote=chan[i].note+(effectVal&15);
chan[i].portaSpeed=(effectVal>>4)*4;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
chan[i].portaStop=true;
chan[i].nowYouCanStop=false;
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
@ -592,6 +619,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
case 0xe2: // portamento down
chan[i].portaNote=chan[i].note-(effectVal&15);
chan[i].portaSpeed=(effectVal>>4)*4;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
chan[i].portaStop=true;
chan[i].nowYouCanStop=false;
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
@ -609,9 +637,11 @@ void DivEngine::processRow(int i, bool afterDelay) {
break;
case 0xe3: // vibrato direction
chan[i].vibratoDir=effectVal;
dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO_SHAPE,i,chan[i].vibratoDir));
break;
case 0xe4: // vibrato fine
chan[i].vibratoFine=effectVal;
dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO_RANGE,i,chan[i].vibratoFine));
break;
case 0xe5: // pitch
chan[i].pitch=effectVal-0x80;
@ -622,6 +652,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
}
//chan[i].pitch+=globalPitch;
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
dispatchCmd(DivCommand(DIV_CMD_HINT_PITCH,i,chan[i].pitch));
break;
case 0xea: // legato mode
chan[i].legato=effectVal;
@ -671,17 +702,21 @@ void DivEngine::processRow(int i, bool afterDelay) {
break;
case 0xf3: // fine volume ramp up
chan[i].volSpeed=effectVal;
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
break;
case 0xf4: // fine volume ramp down
chan[i].volSpeed=-effectVal;
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
break;
case 0xf8: // single volume ramp up
chan[i].volume=MIN(chan[i].volume+effectVal*256,chan[i].volMax);
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
break;
case 0xf9: // single volume ramp down
chan[i].volume=MAX(chan[i].volume-effectVal*256,0);
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
break;
case 0xfa: // fast volume ramp
if (effectVal!=0) {
@ -693,6 +728,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
} else {
chan[i].volSpeed=0;
}
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
break;
case 0xff: // stop song
@ -723,15 +759,18 @@ void DivEngine::processRow(int i, bool afterDelay) {
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
if (chan[i].legato) {
dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note));
} else {
if (chan[i].inPorta && chan[i].keyOn && !chan[i].shorthandPorta) {
if (song.e1e2StopOnSameNote && chan[i].wasShorthandPorta) {
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
chan[i].wasShorthandPorta=false;
chan[i].inPorta=false;
} else {
chan[i].portaNote=chan[i].note;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
}
} else if (!chan[i].noteOnInhibit) {
dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,chan[i].note,chan[i].volume>>8));
@ -742,12 +781,14 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (!chan[i].keyOn && chan[i].scheduledSlideReset) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
chan[i].scheduledSlideReset=false;
chan[i].inPorta=false;
}
if (!chan[i].keyOn && chan[i].volume>chan[i].volMax) {
chan[i].volume=chan[i].volMax;
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
}
chan[i].keyOn=true;
chan[i].keyOff=false;
@ -866,7 +907,9 @@ void DivEngine::nextRow() {
if (!(pat->data[curRow][0]==0 && pat->data[curRow][1]==0)) {
if (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) {
if (!chan[i].legato) {
dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks));
if (disCont[dispatchOfChan[i]].dispatch!=NULL) {
if (disCont[dispatchOfChan[i]].dispatch->getWantPreNote()) dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks));
}
if (song.oneTickCut) {
bool doPrepareCut=true;
@ -914,7 +957,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
// MIDI clock
if (output) if (!skipping && output->midiOut!=NULL) {
output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0));
//output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0));
}
while (!pendingNotes.empty()) {
@ -985,15 +1028,19 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
if (chan[i].volume>chan[i].volMax) {
chan[i].volume=chan[i].volMax;
chan[i].volSpeed=0;
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0));
} else if (chan[i].volume<0) {
chan[i].volSpeed=0;
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0));
if (song.legacyVolumeSlides) {
chan[i].volume=chan[i].volMax+1;
} else {
chan[i].volume=0;
}
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
} else {
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
}
@ -1022,10 +1069,12 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) {
if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) {
chan[i].portaSpeed=0;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
chan[i].oldNote=chan[i].note;
chan[i].note=chan[i].portaNote;
chan[i].inPorta=false;
dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note));
}
}
}
@ -1039,11 +1088,13 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
if (chan[i].stopOnOff) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
chan[i].stopOnOff=false;
}
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
/*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) {
chan[i+1].portaNote=-1;
chan[i+1].portaSpeed=-1;
@ -1057,6 +1108,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
}
if (chan[i].resetArp) {
dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note));
chan[i].resetArp=false;
}
if (song.rowResetsArpPos && firstTick) {

View file

@ -120,6 +120,9 @@ int SafeWriter::writeWString(WString val, bool pascal) {
return 2+val.size()*2;
}
}
int SafeWriter::writeText(String val) {
return write(val.c_str(),val.size());
}
void SafeWriter::init() {
if (operative) return;

View file

@ -57,6 +57,7 @@ class SafeWriter {
int writeD_BE(double val);
int writeWString(WString val, bool pascal);
int writeString(String val, bool pascal);
int writeText(String val);
void init();
SafeReader* toReader();

View file

@ -802,7 +802,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
}
}
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints) {
if (version<0x150) {
lastError="VGM version is too low";
return NULL;
@ -1795,6 +1795,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
playSub(false);
size_t tickCount=0;
bool writeLoop=false;
int ord=-1;
int exportChans=0;
for (int i=0; i<chans; i++) {
if (!willExport[dispatchOfChan[i]]) continue;
exportChans++;
}
while (!done) {
if (loopPos==-1) {
if (loopOrder==curOrder && loopRow==curRow && ticks==1) {
@ -1820,6 +1826,26 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
writeLoop=false;
loopPos=-1;
}
} else {
// check for pattern change
if (prevOrder!=ord) {
logI("registering order change %d on %d",prevOrder, prevRow);
ord=prevOrder;
if (patternHints) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0xfe);
w->writeI(3+exportChans);
w->writeC(0x01);
w->writeC(prevOrder);
w->writeC(prevRow);
for (int i=0; i<chans; i++) {
if (!willExport[dispatchOfChan[i]]) continue;
w->writeC(curSubSong->orders.ord[i][prevOrder]);
}
}
}
}
// get register dumps
for (int i=0; i<song.systemLen; i++) {

View file

@ -1401,6 +1401,17 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
dpiScale
);
break;
case GUI_FILE_EXPORT_CMDSTREAM:
if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir();
hasOpened=fileDialog->openSave(
"Export Command Stream",
{"text file", "*.txt",
"binary file", "*.bin"},
"text file{.txt},binary file{.bin}",
workingDirROMExport,
dpiScale
);
break;
case GUI_FILE_EXPORT_ROM:
showError("Coming soon!");
break;
@ -2901,6 +2912,22 @@ bool FurnaceGUI::loop() {
ImGui::EndCombo();
}
ImGui::Checkbox("loop",&vgmExportLoop);
ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"inserts data blocks on pattern changes.\n"
"useful if you are writing a playback routine.\n\n"
"the format of a pattern change data block is:\n"
"67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n"
"- ll: length, a 32-bit little-endian number\n"
"- oo: order\n"
"- rr: initial row (a 0Dxx effect is able to select a different row)\n"
"- pp: pattern index (one per channel)\n\n"
"pattern indexes are ordered as they appear in the song."
);
}
ImGui::Text("systems to export:");
bool hasOneAtLeast=false;
for (int i=0; i<e->song.systemLen; i++) {
@ -2931,6 +2958,19 @@ bool FurnaceGUI::loop() {
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("export command stream...")) {
ImGui::Text(
"this option exports a text or binary file which\n"
"contains a dump of the internal command stream\n"
"produced when playing the song.\n\n"
"technical/development use only!"
);
if (ImGui::Button("export")) {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM);
}
ImGui::EndMenu();
}
ImGui::Separator();
if (ImGui::BeginMenu("add system...")) {
for (int j=0; availableSystems[j]; j++) {
@ -3242,9 +3282,12 @@ bool FurnaceGUI::loop() {
workingDirAudioExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_EXPORT_VGM:
case GUI_FILE_EXPORT_ROM:
workingDirVGMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_EXPORT_ROM:
case GUI_FILE_EXPORT_CMDSTREAM:
workingDirROMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_LOAD_MAIN_FONT:
case GUI_FILE_LOAD_PAT_FONT:
workingDirFont=fileDialog->getPath()+DIR_SEPARATOR_STR;
@ -3309,6 +3352,11 @@ bool FurnaceGUI::loop() {
if (curFileDialog==GUI_FILE_EXPORT_VGM) {
checkExtension(".vgm");
}
if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM) {
// we can't tell whether the user chose .txt or .bin in the system file picker
const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="text file")?".txt":".bin";
checkExtensionDual(".txt",".bin",fallbackExt);
}
if (curFileDialog==GUI_FILE_EXPORT_COLORS) {
checkExtension(".cfgc");
}
@ -3468,7 +3516,7 @@ bool FurnaceGUI::loop() {
}
break;
case GUI_FILE_EXPORT_VGM: {
SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion);
SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion,vgmExportPatternHints);
if (w!=NULL) {
FILE* f=ps_fopen(copyOfName.c_str(),"wb");
if (f!=NULL) {
@ -3490,6 +3538,35 @@ bool FurnaceGUI::loop() {
case GUI_FILE_EXPORT_ROM:
showError("Coming soon!");
break;
case GUI_FILE_EXPORT_CMDSTREAM: {
String lowerCase=fileName;
for (char& i: lowerCase) {
if (i>='A' && i<='Z') i+='a'-'A';
}
bool isBinary=true;
if ((lowerCase.size()<4 || lowerCase.rfind(".bin")!=lowerCase.size()-4)) {
isBinary=false;
}
SafeWriter* w=e->saveCommand(isBinary);
if (w!=NULL) {
FILE* f=ps_fopen(copyOfName.c_str(),"wb");
if (f!=NULL) {
fwrite(w->getFinalBuf(),1,w->size(),f);
fclose(f);
} else {
showError("could not open file!");
}
w->finish();
delete w;
if (!e->getWarnings().empty()) {
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
}
} else {
showError(fmt::sprintf("could not write command stream! (%s)",e->getLastError()));
}
break;
}
case GUI_FILE_LOAD_MAIN_FONT:
settings.mainFontPath=copyOfName;
break;
@ -4083,6 +4160,7 @@ bool FurnaceGUI::init() {
workingDirSample=e->getConfString("lastDirSample",workingDir);
workingDirAudioExport=e->getConfString("lastDirAudioExport",workingDir);
workingDirVGMExport=e->getConfString("lastDirVGMExport",workingDir);
workingDirROMExport=e->getConfString("lastDirROMExport",workingDir);
workingDirFont=e->getConfString("lastDirFont",workingDir);
workingDirColors=e->getConfString("lastDirColors",workingDir);
workingDirKeybinds=e->getConfString("lastDirKeybinds",workingDir);
@ -4323,6 +4401,7 @@ bool FurnaceGUI::finish() {
e->setConf("lastDirSample",workingDirSample);
e->setConf("lastDirAudioExport",workingDirAudioExport);
e->setConf("lastDirVGMExport",workingDirVGMExport);
e->setConf("lastDirROMExport",workingDirROMExport);
e->setConf("lastDirFont",workingDirFont);
e->setConf("lastDirColors",workingDirColors);
e->setConf("lastDirKeybinds",workingDirKeybinds);
@ -4431,6 +4510,7 @@ FurnaceGUI::FurnaceGUI():
displayError(false),
displayExporting(false),
vgmExportLoop(true),
vgmExportPatternHints(false),
wantCaptureKeyboard(false),
oldWantCaptureKeyboard(false),
displayMacroMenu(false),

View file

@ -266,6 +266,7 @@ enum FurnaceGUIFileDialogs {
GUI_FILE_EXPORT_AUDIO_PER_SYS,
GUI_FILE_EXPORT_AUDIO_PER_CHANNEL,
GUI_FILE_EXPORT_VGM,
GUI_FILE_EXPORT_CMDSTREAM,
GUI_FILE_EXPORT_ROM,
GUI_FILE_LOAD_MAIN_FONT,
GUI_FILE_LOAD_PAT_FONT,
@ -948,11 +949,14 @@ class FurnaceGUI {
bool updateSampleTex;
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile;
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirROM, workingDirTest;
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport;
String workingDirVGMExport, workingDirROMExport, workingDirFont, workingDirColors, workingDirKeybinds;
String workingDirLayout, workingDirROM, workingDirTest;
String mmlString[32];
String mmlStringW;
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints;
bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly;
bool displayPendingIns, pendingInsSingle;
bool willExport[32];

View file

@ -43,24 +43,91 @@ const char* fmParamShortNames[3][32]={
{"ALG", "FB", "FMS", "AMS", "A", "D", "D2", "R", "S", "TL", "RS", "ML", "DT", "DT2", "SSG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM", "EGS", "REV", "Fine", "FMS2", "AMS2"}
};
const char* opllInsNames[17]={
"User",
"Violin",
"Guitar",
"Piano",
"Flute",
"Clarinet",
"Oboe",
"Trumpet",
"Organ",
"Horn",
"Synth",
"Harpsichord",
"Vibraphone",
"Synth Bass",
"Acoustic Bass",
"Electric Guitar",
"Drums"
const char* opllVariants[4]={
"OPLL",
"YMF281",
"YM2423",
"VRC7"
};
const char* opllInsNames[4][17]={
/* YM2413 */ {
"User",
"Violin",
"Guitar",
"Piano",
"Flute",
"Clarinet",
"Oboe",
"Trumpet",
"Organ",
"Horn",
"Synth",
"Harpsichord",
"Vibraphone",
"Synth Bass",
"Acoustic Bass",
"Electric Guitar",
"Drums"
},
/* YMF281 */ {
"User",
"Electric String",
"Bow wow",
"Electric Guitar",
"Organ",
"Clarinet",
"Saxophone",
"Trumpet",
"Street Organ",
"Synth Brass",
"Electric Piano",
"Bass",
"Vibraphone",
"Chime",
"Tom Tom II",
"Noise",
"Drums"
},
/* YM2423 */ {
"User",
"Strings",
"Guitar",
"Electric Guitar",
"Electric Piano",
"Flute",
"Marimba",
"Trumpet",
"Harmonica",
"Tuba",
"Synth Brass",
"Short Saw",
"Vibraphone",
"Electric Guitar 2",
"Synth Bass",
"Sitar",
"Drums"
},
// stolen from FamiTracker
/* VRC7 */ {
"User",
"Bell",
"Guitar",
"Piano",
"Flute",
"Clarinet",
"Rattling Bell",
"Trumpet",
"Reed Organ",
"Soft Bell",
"Xylophone",
"Vibraphone",
"Brass",
"Bass Guitar",
"Synth",
"Chorus",
"Drums"
}
};
const char* oplWaveforms[8]={
@ -1572,10 +1639,59 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn();
drawAlgorithm(0,FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,24.0*dpiScale));
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::BeginCombo("##LLPreset",opllInsNames[ins->fm.opllPreset])) {
for (int i=0; i<17; i++) {
if (ImGui::Selectable(opllInsNames[i])) {
ins->fm.opllPreset=i;
bool isPresent[4];
int isPresentCount=0;
memset(isPresent,0,4*sizeof(bool));
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_VRC7) {
isPresent[3]=true;
} else if (e->song.system[i]==DIV_SYSTEM_OPLL || e->song.system[i]==DIV_SYSTEM_OPLL_DRUMS) {
isPresent[(e->song.systemFlags[i]>>4)&3]=true;
}
}
if (!isPresent[0] && !isPresent[1] && !isPresent[2] && !isPresent[3]) {
isPresent[0]=true;
}
for (int i=0; i<4; i++) {
if (isPresent[i]) isPresentCount++;
}
int presentWhich=0;
for (int i=0; i<4; i++) {
if (isPresent[i]) {
presentWhich=i;
break;
}
}
if (ImGui::BeginCombo("##LLPreset",opllInsNames[presentWhich][ins->fm.opllPreset])) {
if (isPresentCount>1) {
if (ImGui::BeginTable("LLPresetList",isPresentCount)) {
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
for (int i=0; i<4; i++) {
if (!isPresent[i]) continue;
ImGui::TableNextColumn();
ImGui::Text("%s name",opllVariants[i]);
}
for (int i=0; i<17; i++) {
ImGui::TableNextRow();
for (int j=0; j<4; j++) {
if (!isPresent[j]) continue;
ImGui::TableNextColumn();
ImGui::PushID(j*17+i);
if (ImGui::Selectable(opllInsNames[j][i])) {
ins->fm.opllPreset=i;
}
ImGui::PopID();
}
}
ImGui::EndTable();
}
} else {
for (int i=0; i<17; i++) {
if (ImGui::Selectable(opllInsNames[presentWhich][i])) {
ins->fm.opllPreset=i;
}
}
}
ImGui::EndCombo();
@ -2977,13 +3093,17 @@ void FurnaceGUI::drawInsEdit() {
P(ImGui::Checkbox("Don't test/gate before new note",&ins->c64.noTest));
ImGui::EndTabItem();
}
if (ins->type==DIV_INS_AMIGA) if (ImGui::BeginTabItem("Sample")) {
if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SU) if (ImGui::BeginTabItem((ins->type==DIV_INS_SU)?"Sound Unit":"Sample")) {
String sName;
if (ins->amiga.initSample<0 || ins->amiga.initSample>=e->song.sampleLen) {
sName="none selected";
} else {
sName=e->song.sample[ins->amiga.initSample]->name;
}
if (ins->type==DIV_INS_SU) {
P(ImGui::Checkbox("Use sample",&ins->su.useSample));
P(ImGui::Checkbox("Switch roles of frequency and phase reset timer",&ins->su.switchRoles));
}
if (ImGui::BeginCombo("Initial Sample",sName.c_str())) {
String id;
for (int i=0; i<e->song.sampleLen; i++) {
@ -2994,14 +3114,16 @@ void FurnaceGUI::drawInsEdit() {
}
ImGui::EndCombo();
}
P(ImGui::Checkbox("Use wavetable (Amiga only)",&ins->amiga.useWave));
if (ins->amiga.useWave) {
int len=ins->amiga.waveLen+1;
if (ImGui::InputInt("Width",&len,2,16)) {
if (len<2) len=2;
if (len>256) len=256;
ins->amiga.waveLen=(len&(~1))-1;
PARAMETER
if (ins->type==DIV_INS_AMIGA) {
P(ImGui::Checkbox("Use wavetable (Amiga only)",&ins->amiga.useWave));
if (ins->amiga.useWave) {
int len=ins->amiga.waveLen+1;
if (ImGui::InputInt("Width",&len,2,16)) {
if (len<2) len=2;
if (len>256) len=256;
ins->amiga.waveLen=(len&(~1))-1;
PARAMETER
}
}
}
ImGui::BeginDisabled(ins->amiga.useWave);

View file

@ -1712,7 +1712,7 @@ void FurnaceGUI::drawSettings() {
ImGui::Text("%s",SDL_GetScancodeName((SDL_Scancode)i.scan));
ImGui::TableNextColumn();
if (i.val==102) {
snprintf(id,4095,"Envelope release##SNType_%d",i.scan);
snprintf(id,4095,"Macro release##SNType_%d",i.scan);
if (ImGui::Button(id)) {
noteKeys[i.scan]=0;
}

View file

@ -226,8 +226,6 @@ void FurnaceGUI::drawSongInfo() {
}
ImGui::EndTable();
}
ImGui::TextWrapped("if this feels incomplete, go to Subsongs.\nthe outcome of this Song Information window will be determined by a poll on the Furnace Discord.");
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SONG_INFO;
ImGui::End();

View file

@ -94,136 +94,6 @@ void FurnaceGUI::drawSubSongs() {
if (ImGui::InputText("##SubSongName",&e->curSubSong->name)) {
MARK_MODIFIED;
}
if (ImGui::BeginTable("OtherSubProps",3,ImGuiTableFlags_SizingStretchProp)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("TimeBase");
ImGui::TableNextColumn();
float avail=ImGui::GetContentRegionAvail().x;
ImGui::SetNextItemWidth(avail);
unsigned char realTB=e->curSubSong->timeBase+1;
if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { MARK_MODIFIED
if (realTB<1) realTB=1;
if (realTB>16) realTB=16;
e->curSubSong->timeBase=realTB-1;
}
ImGui::TableNextColumn();
ImGui::Text("%.2f BPM",calcBPM(e->curSubSong->speed1,e->curSubSong->speed2,e->curSubSong->hz,e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD));
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Speed");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->curSubSong->speed1,&_ONE,&_THREE)) { MARK_MODIFIED
if (e->curSubSong->speed1<1) e->curSubSong->speed1=1;
if (e->isPlaying()) play();
}
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->curSubSong->speed2,&_ONE,&_THREE)) { MARK_MODIFIED
if (e->curSubSong->speed2<1) e->curSubSong->speed2=1;
if (e->isPlaying()) play();
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Virtual Tempo");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##VTempoN",ImGuiDataType_S16,&e->curSubSong->virtualTempoN,&_ONE,&_THREE)) { MARK_MODIFIED
if (e->curSubSong->virtualTempoN<1) e->curSubSong->virtualTempoN=1;
if (e->curSubSong->virtualTempoN>255) e->curSubSong->virtualTempoN=255;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Numerator");
}
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##VTempoD",ImGuiDataType_S16,&e->curSubSong->virtualTempoD,&_ONE,&_THREE)) { MARK_MODIFIED
if (e->curSubSong->virtualTempoD<1) e->curSubSong->virtualTempoD=1;
if (e->curSubSong->virtualTempoD>255) e->curSubSong->virtualTempoD=255;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Denominator (set to base tempo)");
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Highlight");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->curSubSong->hilightA,&_ONE,&_THREE)) {
MARK_MODIFIED;
}
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->curSubSong->hilightB,&_ONE,&_THREE)) {
MARK_MODIFIED;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Pattern Length");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
int patLen=e->curSubSong->patLen;
if (ImGui::InputInt("##PatLength",&patLen,1,3)) { MARK_MODIFIED
if (patLen<1) patLen=1;
if (patLen>256) patLen=256;
e->curSubSong->patLen=patLen;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Song Length");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
int ordLen=e->curSubSong->ordersLen;
if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { MARK_MODIFIED
if (ordLen<1) ordLen=1;
if (ordLen>256) ordLen=256;
e->curSubSong->ordersLen=ordLen;
if (curOrder>=ordLen) {
setOrder(ordLen-1);
}
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(tempoView?"Base Tempo##TempoOrHz":"Tick Rate##TempoOrHz")) {
tempoView=!tempoView;
}
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
float setHz=tempoView?e->curSubSong->hz*2.5:e->curSubSong->hz;
if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED
if (tempoView) setHz/=2.5;
if (setHz<10) setHz=10;
if (setHz>999) setHz=999;
e->setSongRate(setHz,setHz<52);
}
if (tempoView) {
ImGui::TableNextColumn();
ImGui::Text("= %gHz",e->curSubSong->hz);
} else {
if (e->curSubSong->hz>=49.98 && e->curSubSong->hz<=50.02) {
ImGui::TableNextColumn();
ImGui::Text("PAL");
}
if (e->curSubSong->hz>=59.9 && e->curSubSong->hz<=60.11) {
ImGui::TableNextColumn();
ImGui::Text("NTSC");
}
}
ImGui::EndTable();
}
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS;
ImGui::End();

View file

@ -109,6 +109,48 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
}
break;
}
case DIV_SYSTEM_PCE: {
sysPal=flags&1;
if (ImGui::Checkbox("Pseudo-PAL",&sysPal)) {
copyOfFlags=(flags&(~1))|(unsigned int)sysPal;
}
bool antiClick=flags&8;
if (ImGui::Checkbox("Disable anti-click",&antiClick)) {
copyOfFlags=(flags&(~8))|(antiClick<<3);
}
ImGui::Text("Chip revision:");
if (ImGui::RadioButton("HuC6280 (original)",(flags&4)==0)) {
copyOfFlags=(flags&(~4))|0;
}
if (ImGui::RadioButton("HuC6280A (SuperGrafx)",(flags&4)==4)) {
copyOfFlags=(flags&(~4))|4;
}
break;
}
case DIV_SYSTEM_SOUND_UNIT: {
ImGui::Text("CPU rate:");
if (ImGui::RadioButton("6.18MHz (NTSC)",(flags&15)==0)) {
copyOfFlags=(flags&(~15))|0;
}
if (ImGui::RadioButton("5.95MHz (PAL)",(flags&15)==1)) {
copyOfFlags=(flags&(~15))|1;
}
ImGui::Text("Chip revision (sample memory):");
if (ImGui::RadioButton("A/B/E (8K)",(flags&16)==0)) {
copyOfFlags=(flags&(~16))|0;
}
if (ImGui::RadioButton("D/F (64K)",(flags&16)==16)) {
copyOfFlags=(flags&(~16))|16;
}
break;
}
case DIV_SYSTEM_GB: {
bool antiClick=flags&8;
if (ImGui::Checkbox("Disable anti-click",&antiClick)) {
copyOfFlags=(flags&(~8))|(antiClick<<3);
}
break;
}
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7: {
@ -631,7 +673,6 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
}
break;
}
case DIV_SYSTEM_GB:
case DIV_SYSTEM_SWAN:
case DIV_SYSTEM_VERA:
case DIV_SYSTEM_BUBSYS_WSG:

View file

@ -52,6 +52,7 @@ FurnaceCLI cli;
String outName;
String vgmOutName;
String cmdOutName;
int loops=1;
int benchMode=0;
DivAudioExportModes outMode=DIV_EXPORT_MODE_ONE;
@ -63,6 +64,7 @@ bool consoleMode=true;
#endif
bool displayEngineFailError=false;
bool cmdOutBinary=false;
std::vector<TAParam> params;
@ -114,6 +116,11 @@ TAParamResult pConsole(String val) {
return TA_PARAM_SUCCESS;
}
TAParamResult pBinary(String val) {
cmdOutBinary=true;
return TA_PARAM_SUCCESS;
}
TAParamResult pLogLevel(String val) {
if (val=="trace") {
logLevel=LOGLEVEL_TRACE;
@ -250,6 +257,12 @@ TAParamResult pVGMOut(String val) {
return TA_PARAM_SUCCESS;
}
TAParamResult pCmdOut(String val) {
cmdOutName=val;
e.setAudio(DIV_AUDIO_DUMMY);
return TA_PARAM_SUCCESS;
}
bool needsValue(String param) {
for (size_t i=0; i<params.size(); i++) {
if (params[i].name==param) {
@ -265,6 +278,8 @@ void initParams() {
params.push_back(TAParam("a","audio",true,pAudio,"jack|sdl","set audio engine (SDL by default)"));
params.push_back(TAParam("o","output",true,pOutput,"<filename>","output audio to file"));
params.push_back(TAParam("O","vgmout",true,pVGMOut,"<filename>","output .vgm data"));
params.push_back(TAParam("C","cmdout",true,pCmdOut,"<filename>","output command stream"));
params.push_back(TAParam("b","binary",false,pBinary,"","set command stream output format to binary"));
params.push_back(TAParam("L","loglevel",true,pLogLevel,"debug|info|warning|error","set the log level (info by default)"));
params.push_back(TAParam("v","view",true,pView,"pattern|commands|nothing","set visualization (pattern by default)"));
params.push_back(TAParam("c","console",false,pConsole,"","enable console mode"));
@ -307,6 +322,7 @@ int main(int argc, char** argv) {
#endif
outName="";
vgmOutName="";
cmdOutName="";
initParams();
@ -443,7 +459,23 @@ int main(int argc, char** argv) {
}
return 0;
}
if (outName!="" || vgmOutName!="") {
if (outName!="" || vgmOutName!="" || cmdOutName!="") {
if (cmdOutName!="") {
SafeWriter* w=e.saveCommand(cmdOutBinary);
if (w!=NULL) {
FILE* f=fopen(cmdOutName.c_str(),"wb");
if (f!=NULL) {
fwrite(w->getFinalBuf(),1,w->size(),f);
fclose(f);
} else {
reportError(fmt::sprintf("could not open file! (%s)",e.getLastError()));
}
w->finish();
delete w;
} else {
reportError("could not write command stream!");
}
}
if (vgmOutName!="") {
SafeWriter* w=e.saveVGM();
if (w!=NULL) {