Merge branch 'master' into es5506_alt

This commit is contained in:
cam900 2022-10-09 20:05:15 +09:00 committed by GitHub
commit f8c494e1dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 3520 additions and 397 deletions

View File

@ -433,6 +433,8 @@ src/engine/platform/sound/vic20sound.c
src/engine/platform/sound/ymz280b.cpp
src/engine/platform/sound/vsu.cpp
src/engine/platform/sound/rf5c68.cpp
src/engine/platform/sound/oki/msm5232.cpp
@ -504,6 +506,7 @@ src/engine/platform/x1_010.cpp
src/engine/platform/lynx.cpp
src/engine/platform/su.cpp
src/engine/platform/swan.cpp
src/engine/platform/vb.cpp
src/engine/platform/vera.cpp
src/engine/platform/zxbeeper.cpp
src/engine/platform/bubsyswsg.cpp

11
TODO.md Normal file
View File

@ -0,0 +1,11 @@
# to-do for 0.6pre2
- POKEY
- Pokémon Mini
- Virtual Boy
- T6W28
- (maybe) YM2612 CSM (no DualPCM)
- port presets to new format
- bug fixes
- (maybe) ExtCh FM macros?
- (maybe) advanced linear arpeggio? (run arp+slide simultaneously)

BIN
demos/Solar_Man_Genesis.fur Normal file

Binary file not shown.

View File

@ -13,14 +13,70 @@ then read data.
## binary command stream
read channel, command and values.
if channel is 80 or higher, then it is a special command:
Furnace Command Stream, split version.
```
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
size | description
-----|------------------------------------
4 | "FCS\0" format magic
4 | channel count
4?? | pointers to channel data
1?? | preset delays
| - 16 values
1?? | speed dial commands
| - 16 values
??? | channel data
```
read command and values (if any).
the list of commands follows.
```
hex | description
----|------------------------------------
00 | note on: C-(-5)
01 | note on: C#(-5)
02 | note on: D-(-5)
.. | ...
b1 | note on: A-9
b2 | note on: A#9
b3 | note on: B-9
b4 | note on: null
----|------------------------------------
b5 | note off
b6 | note off env
b7 | env release
b8 | instrument // (ins, force)
be | panning // (left, right)
c0 | pre porta // (inporta, isportaorslide)
c2 | vibrato // (speed, depth)
c3 | vibrato range // (range)
c4 | vibrato shape // (shape)
c5 | pitch // (pitch)
c6 | arpeggio // (note1, note2)
c7 | volume // (vol)
c8 | vol slide // (amount, onetick)
c9 | porta // (target, speed)
ca | legato // (note)
----|------------------------------------
d0 | speed dial command 0
d1 | speed dial command 1
.. | ...
df | speed dial command 15
----|------------------------------------
e0 | preset delay 0
e1 | preset delay 1
.. | ...
ef | preset delay 15
----|------------------------------------
f7 | full command (command and data follows)
f8 | go to sub-block (offset follows)
f9 | return from sub-block
fa | jump (offset follows)
fb | set tick rate (4 bytes)
fc | wait (16-bit)
fd | wait (8-bit)
fe | wait one tick
ff | stop
```

View File

@ -32,7 +32,9 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
the format versions are:
- 119: Furnace dev119 (still not released)
- 121: Furnace dev121
- 120: Furnace dev120
- 119: Furnace dev119
- 118: Furnace dev118
- 117: Furnace dev117
- 116: Furnace 0.6pre1.5
@ -350,7 +352,9 @@ size | description
1 | 0B/0D effect treatment (>=113) or reserved
1 | automatic system name detection (>=115) or reserved
| - this one isn't a compatibility flag, but it's here for convenience...
3 | reserved
1 | disable sample macro (>=117) or reserved
1 | broken outVol episode 2 (>=121) or reserved
1 | reserved
--- | **virtual tempo data**
2 | virtual tempo numerator of first song (>=96) or reserved
2 | virtual tempo denominator of first song (>=96) or reserved
@ -437,6 +441,11 @@ notes:
- the entire instrument is stored, regardless of instrument type.
- the macro range varies depending on the instrument type.
- "macro open" indicates whether the macro is collapsed or not in the instrument editor.
- as of format version 120, bit 1-2 indicates macro mode:
- 0: sequence (normal)
- 1: ADSR
- 2: LFO
- see sub-section for information on how to interpret parameters.
- FM operator order is:
- 1/3/2/4 (internal order) for OPN, OPM, OPZ and OPL 4-op
- 1/2/?/? (? = unused) for OPL 2-op and OPLL
@ -1024,6 +1033,29 @@ size | description
1 | KSR macro delay
```
## interpreting macro mode values
- sequence (normal): I think this is obvious...
- ADSR:
- `val[0]`: bottom
- `val[1]`: top
- `val[2]`: attack
- `val[3]`: hold time
- `val[4]`: decay
- `val[5]`: sustain level
- `val[6]`: sustain hold time
- `val[7]`: decay 2
- `val[8]`: release
- LFO:
- `val[11]`: speed
- `val[12]`: waveform
- 0: triangle
- 1: saw
- 2: pulse
- `val[13]`: phase
- `val[14]`: loop
- `val[15]`: global (not sure how will I implement this)
# wavetable
```

View File

@ -68,6 +68,7 @@
#include "platform/ymz280b.h"
#include "platform/rf5c68.h"
#include "platform/snes.h"
#include "platform/vb.h"
#include "platform/pcmdac.h"
#include "platform/dummy.h"
#include "../ta-log.h"
@ -343,6 +344,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_SWAN:
dispatch=new DivPlatformSwan;
break;
case DIV_SYSTEM_VBOY:
dispatch=new DivPlatformVB;
break;
case DIV_SYSTEM_VERA:
dispatch=new DivPlatformVERA;
break;

View File

@ -274,29 +274,61 @@ 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); \
#define WRITE_TICK(x) \
if (binary) { \
if (!wroteTick[x]) { \
wroteTick[x]=true; \
if (tick-lastTick[x]>255) { \
chanStream[x]->writeC(0xfc); \
chanStream[x]->writeS(tick-lastTick[x]); \
} else if (tick-lastTick[x]>1) { \
delayPopularity[tick-lastTick[x]]++; \
chanStream[x]->writeC(0xfd); \
chanStream[x]->writeC(tick-lastTick[x]); \
} else if (tick-lastTick[x]>0) { \
chanStream[x]->writeC(0xfe); \
} \
} else { \
lastTick[x]=tick; \
} \
} else { \
if (!wroteTickGlobal) { \
wroteTickGlobal=true; \
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:
if (c.value==DIV_NOTE_NULL) {
w->writeC(0xb4);
} else {
w->writeC(CLAMP(c.value+60,0,0xb3));
}
break;
case DIV_CMD_NOTE_OFF:
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
case DIV_CMD_INSTRUMENT:
case DIV_CMD_PANNING:
case DIV_CMD_PRE_PORTA:
case DIV_CMD_HINT_VIBRATO:
case DIV_CMD_HINT_VIBRATO_RANGE:
case DIV_CMD_HINT_VIBRATO_SHAPE:
case DIV_CMD_HINT_PITCH:
case DIV_CMD_HINT_ARPEGGIO:
case DIV_CMD_HINT_VOLUME:
case DIV_CMD_HINT_PORTA:
case DIV_CMD_HINT_VOL_SLIDE:
case DIV_CMD_HINT_LEGATO:
w->writeC((unsigned char)c.cmd+0xb4);
break;
default:
w->writeC(0xf0); // unoptimized extended command
w->writeC(c.cmd);
break;
}
switch (c.cmd) {
case DIV_CMD_HINT_LEGATO:
if (c.value==DIV_NOTE_NULL) {
w->writeC(0xff);
@ -304,6 +336,7 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
w->writeC(c.value+60);
}
break;
case DIV_CMD_NOTE_ON:
case DIV_CMD_NOTE_OFF:
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
@ -313,6 +346,21 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
case DIV_CMD_HINT_VIBRATO_SHAPE:
case DIV_CMD_HINT_PITCH:
case DIV_CMD_HINT_VOLUME:
w->writeC(c.value);
break;
case DIV_CMD_PANNING:
case DIV_CMD_HINT_VIBRATO:
case DIV_CMD_HINT_ARPEGGIO:
case DIV_CMD_HINT_PORTA:
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:
w->writeS(c.value);
break;
case DIV_CMD_SAMPLE_MODE:
case DIV_CMD_SAMPLE_FREQ:
case DIV_CMD_SAMPLE_BANK:
@ -348,12 +396,18 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
case DIV_CMD_AY_NOISE_MASK_AND:
case DIV_CMD_AY_NOISE_MASK_OR:
case DIV_CMD_AY_AUTO_ENVELOPE:
case DIV_CMD_FDS_MOD_DEPTH:
case DIV_CMD_FDS_MOD_HIGH:
case DIV_CMD_FDS_MOD_LOW:
case DIV_CMD_FDS_MOD_POS:
case DIV_CMD_FDS_MOD_WAVE:
case DIV_CMD_SAA_ENVELOPE:
case DIV_CMD_AMIGA_FILTER:
case DIV_CMD_AMIGA_AM:
case DIV_CMD_AMIGA_PM:
w->writeC(1); // length
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:
@ -375,25 +429,27 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
case DIV_CMD_FM_FINE:
case DIV_CMD_AY_IO_WRITE:
case DIV_CMD_AY_AUTO_PWM:
w->writeC(2); // length
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:
case DIV_CMD_LYNX_LFSR_LOAD:
w->writeC(2); // length
w->writeS(c.value);
break;
case DIV_CMD_FM_FIXFREQ:
w->writeC(2); // length
w->writeS((c.value<<12)|(c.value2&0x7ff));
break;
case DIV_CMD_NES_SWEEP:
w->writeC(1); // length
w->writeC((c.value?8:0)|(c.value2&0x77));
break;
default:
logW("unimplemented command %s!",cmdName[c.cmd]);
w->writeC(0); // length
break;
}
}
@ -411,12 +467,44 @@ SafeWriter* DivEngine::saveCommand(bool binary) {
walkSong(loopOrder,loopRow,loopEnd);
logI("loop point: %d %d",loopOrder,loopRow);
int cmdPopularity[256];
int delayPopularity[256];
int sortedCmdPopularity[16];
int sortedDelayPopularity[16];
unsigned char sortedCmd[16];
unsigned char sortedDelay[16];
SafeWriter* chanStream[DIV_MAX_CHANS];
unsigned int chanStreamOff[DIV_MAX_CHANS];
bool wroteTick[DIV_MAX_CHANS];
memset(cmdPopularity,0,256*sizeof(int));
memset(delayPopularity,0,256*sizeof(int));
memset(chanStream,0,DIV_MAX_CHANS*sizeof(void*));
memset(chanStreamOff,0,DIV_MAX_CHANS*sizeof(unsigned int));
memset(sortedCmdPopularity,0,16*sizeof(int));
memset(sortedDelayPopularity,0,16*sizeof(int));
memset(sortedCmd,0,16);
memset(sortedDelay,0,16);
SafeWriter* w=new SafeWriter;
w->init();
// write header
if (binary) {
w->write("FCS",4);
w->writeI(chans);
// offsets
for (int i=0; i<chans; i++) {
chanStream[i]=new SafeWriter;
chanStream[i]->init();
w->writeI(0);
}
// preset delays and speed dial
for (int i=0; i<32; i++) {
w->writeC(0);
}
} else {
w->writeText("# Furnace Command Stream\n\n");
@ -451,19 +539,22 @@ SafeWriter* DivEngine::saveCommand(bool binary) {
bool oldCmdStreamEnabled=cmdStreamEnabled;
cmdStreamEnabled=true;
double curDivider=divider;
int lastTick=0;
int lastTick[DIV_MAX_CHANS];
memset(lastTick,0,DIV_MAX_CHANS*sizeof(int));
while (!done) {
if (nextTick(false,true) || !playing) {
done=true;
}
// get command stream
bool wroteTick=false;
bool wroteTickGlobal=false;
memset(wroteTick,0,DIV_MAX_CHANS*sizeof(bool));
if (curDivider!=divider) {
curDivider=divider;
WRITE_TICK;
WRITE_TICK(0);
if (binary) {
w->writeC(0xfb);
w->writeI((int)(curDivider*65536));
chanStream[0]->writeC(0xfb);
chanStream[0]->writeI((int)(curDivider*65536));
} else {
w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider));
}
@ -486,10 +577,10 @@ SafeWriter* DivEngine::saveCommand(bool binary) {
case DIV_CMD_PRE_NOTE:
break;
default:
WRITE_TICK;
WRITE_TICK(i.chan);
if (binary) {
w->writeC(i.chan);
writePackedCommandValues(w,i);
cmdPopularity[i.cmd]++;
writePackedCommandValues(chanStream[i.chan],i);
} else {
w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2));
}
@ -502,7 +593,185 @@ SafeWriter* DivEngine::saveCommand(bool binary) {
cmdStreamEnabled=oldCmdStreamEnabled;
if (binary) {
w->writeC(0xff);
int sortCand=-1;
int sortPos=0;
while (sortPos<16) {
sortCand=-1;
for (int i=DIV_CMD_SAMPLE_MODE; i<256; i++) {
if (cmdPopularity[i]) {
if (sortCand==-1) {
sortCand=i;
} else if (cmdPopularity[sortCand]<cmdPopularity[i]) {
sortCand=i;
}
}
}
if (sortCand==-1) break;
sortedCmdPopularity[sortPos]=cmdPopularity[sortCand];
sortedCmd[sortPos]=sortCand;
cmdPopularity[sortCand]=0;
sortPos++;
}
sortCand=-1;
sortPos=0;
while (sortPos<16) {
sortCand=-1;
for (int i=0; i<256; i++) {
if (delayPopularity[i]) {
if (sortCand==-1) {
sortCand=i;
} else if (delayPopularity[sortCand]<delayPopularity[i]) {
sortCand=i;
}
}
}
if (sortCand==-1) break;
sortedDelayPopularity[sortPos]=delayPopularity[sortCand];
sortedDelay[sortPos]=sortCand;
delayPopularity[sortCand]=0;
sortPos++;
}
for (int i=0; i<chans; i++) {
chanStream[i]->writeC(0xff);
// optimize stream
SafeWriter* oldStream=chanStream[i];
SafeReader* reader=oldStream->toReader();
chanStream[i]=new SafeWriter;
chanStream[i]->init();
while (1) {
try {
unsigned char next=reader->readC();
switch (next) {
case 0xb8: // instrument
case 0xc0: // pre porta
case 0xc3: // vibrato range
case 0xc4: // vibrato shape
case 0xc5: // pitch
case 0xc7: // volume
case 0xca: // legato
chanStream[i]->writeC(next);
next=reader->readC();
chanStream[i]->writeC(next);
break;
case 0xbe: // panning
case 0xc2: // vibrato
case 0xc6: // arpeggio
case 0xc8: // vol slide
case 0xc9: // porta
chanStream[i]->writeC(next);
next=reader->readC();
chanStream[i]->writeC(next);
next=reader->readC();
chanStream[i]->writeC(next);
break;
case 0xf0: { // full command (pre)
unsigned char cmd=reader->readC();
bool foundShort=false;
for (int j=0; j<16; j++) {
if (sortedCmd[j]==cmd) {
chanStream[i]->writeC(0xd0+j);
foundShort=true;
break;
}
}
if (!foundShort) {
chanStream[i]->writeC(0xf7); // full command
chanStream[i]->writeC(cmd);
}
unsigned char cmdLen=reader->readC();
logD("cmdLen: %d",cmdLen);
for (unsigned char j=0; j<cmdLen; j++) {
next=reader->readC();
chanStream[i]->writeC(next);
}
break;
}
case 0xfb: // tick rate
chanStream[i]->writeC(next);
next=reader->readC();
chanStream[i]->writeC(next);
next=reader->readC();
chanStream[i]->writeC(next);
next=reader->readC();
chanStream[i]->writeC(next);
next=reader->readC();
chanStream[i]->writeC(next);
break;
case 0xfc: { // 16-bit wait
unsigned short delay=reader->readS();
bool foundShort=false;
for (int j=0; j<16; j++) {
if (sortedDelay[j]==delay) {
chanStream[i]->writeC(0xe0+j);
foundShort=true;
break;
}
}
if (!foundShort) {
chanStream[i]->writeC(next);
chanStream[i]->writeS(delay);
}
break;
}
case 0xfd: { // 8-bit wait
unsigned char delay=reader->readC();
bool foundShort=false;
for (int j=0; j<16; j++) {
if (sortedDelay[j]==delay) {
chanStream[i]->writeC(0xe0+j);
foundShort=true;
break;
}
}
if (!foundShort) {
chanStream[i]->writeC(next);
chanStream[i]->writeC(delay);
}
break;
}
default:
chanStream[i]->writeC(next);
break;
}
} catch (EndOfFileException& e) {
break;
}
}
oldStream->finish();
delete oldStream;
}
for (int i=0; i<chans; i++) {
chanStreamOff[i]=w->tell();
logI("- %d: off %x size %ld",i,chanStreamOff[i],chanStream[i]->size());
w->write(chanStream[i]->getFinalBuf(),chanStream[i]->size());
chanStream[i]->finish();
delete chanStream[i];
}
w->seek(8,SEEK_SET);
for (int i=0; i<chans; i++) {
w->writeI(chanStreamOff[i]);
}
logD("delay popularity:");
for (int i=0; i<16; i++) {
w->writeC(sortedDelay[i]);
if (sortedDelayPopularity[i]) logD("- %d: %d",sortedDelay[i],sortedDelayPopularity[i]);
}
logD("command popularity:");
for (int i=0; i<16; i++) {
w->writeC(sortedCmd[i]);
if (sortedCmdPopularity[i]) logD("- %s: %d",cmdName[sortedCmd[i]],sortedCmdPopularity[i]);
}
} else {
if (!playing) {
w->writeText(">> END\n");
@ -985,6 +1254,16 @@ int DivEngine::loadSampleROM(String path, ssize_t expectedSize, unsigned char*&
return 0;
}
unsigned int DivEngine::getSampleFormatMask() {
unsigned int formatMask=1U<<16; // 16-bit is always on
for (int i=0; i<song.systemLen; i++) {
const DivSysDef* s=getSystemDef(song.system[i]);
if (s==NULL) continue;
formatMask|=s->sampleFormatMask;
}
return formatMask;
}
int DivEngine::loadSampleROMs() {
if (yrw801ROM!=NULL) {
delete[] yrw801ROM;
@ -1016,6 +1295,8 @@ void DivEngine::renderSamples() {
sPreview.pos=0;
sPreview.dir=false;
logD("rendering samples...");
// step 0: make sample format mask
unsigned int formatMask=1U<<16; // 16-bit is always on
for (int i=0; i<song.systemLen; i++) {
@ -1578,6 +1859,40 @@ String DivEngine::getWarnings() {
return warnings;
}
String DivEngine::getPlaybackDebugInfo() {
return fmt::sprintf(
"curOrder: %d\n"
"prevOrder: %d\n"
"curRow: %d\n"
"prevRow: %d\n"
"ticks: %d\n"
"subticks: %d\n"
"totalLoops: %d\n"
"lastLoopPos: %d\n"
"nextSpeed: %d\n"
"divider: %f\n"
"cycles: %d\n"
"clockDrift: %f\n"
"changeOrd: %d\n"
"changePos: %d\n"
"totalSeconds: %d\n"
"totalTicks: %d\n"
"totalTicksR: %d\n"
"totalCmds: %d\n"
"lastCmds: %d\n"
"cmdsPerSecond: %d\n"
"globalPitch: %d\n"
"extValue: %d\n"
"speed1: %d\n"
"speed2: %d\n"
"tempoAccum: %d\n"
"totalProcessed: %d\n",
curOrder,prevOrder,curRow,prevRow,ticks,subticks,totalLoops,lastLoopPos,nextSpeed,divider,cycles,clockDrift,
changeOrd,changePos,totalSeconds,totalTicks,totalTicksR,totalCmds,lastCmds,cmdsPerSecond,globalPitch,
(int)extValue,(int)speed1,(int)speed2,(int)tempoAccum,(int)totalProcessed
);
}
DivInstrument* DivEngine::getIns(int index, DivInstrumentType fallbackType) {
if (index==-2 && tempIns!=NULL) {
return tempIns;
@ -1701,6 +2016,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
skipping=true;
memset(walked,0,8192);
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(true);
logV("goal: %d goalRow: %d",goal,goalRow);
while (playing && curOrder<goal) {
if (nextTick(preserveDrift)) {
skipping=false;
@ -1714,6 +2030,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
return;
}
if (oldOrder!=curOrder) break;
if (ticks-((tempoAccum+curSubSong->virtualTempoN)/MAX(1,curSubSong->virtualTempoD))<1 && curRow>=goalRow) break;
}
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(false);
if (goal>0 || goalRow>0) {
@ -1734,6 +2051,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
subticks=1;
prevOrder=curOrder;
prevRow=curRow;
tempoAccum=0;
}
skipping=false;
cmdStream.clear();
@ -3256,7 +3574,7 @@ void DivEngine::exchangeIns(int one, int two) {
for (size_t j=0; j<song.subsong.size(); j++) {
for (int k=0; k<256; k++) {
if (song.subsong[j]->pat[i].data[k]==NULL) continue;
for (int l=0; l<curSubSong->patLen; l++) {
for (int l=0; l<song.subsong[j]->patLen; l++) {
if (song.subsong[j]->pat[i].data[k]->data[l][2]==one) {
song.subsong[j]->pat[i].data[k]->data[l][2]=two;
} else if (song.subsong[j]->pat[i].data[k]->data[l][2]==two) {
@ -3304,6 +3622,7 @@ bool DivEngine::moveSampleUp(int which) {
song.sample[which]=song.sample[which-1];
song.sample[which-1]=prev;
saveLock.unlock();
renderSamples();
BUSY_END;
return true;
}
@ -3344,6 +3663,7 @@ bool DivEngine::moveSampleDown(int which) {
song.sample[which]=song.sample[which+1];
song.sample[which+1]=prev;
saveLock.unlock();
renderSamples();
BUSY_END;
return true;
}

View File

@ -47,8 +47,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev119"
#define DIV_ENGINE_VERSION 119
#define DIV_VERSION "dev121"
#define DIV_ENGINE_VERSION 121
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -906,6 +906,9 @@ class DivEngine {
// load sample ROMs
int loadSampleROMs();
// get the sample format mask
unsigned int getSampleFormatMask();
// UNSAFE render samples - only execute when locked
void renderSamples();
@ -955,6 +958,9 @@ class DivEngine {
// get warnings
String getWarnings();
// get debug info
String getPlaybackDebugInfo();
// switch master
bool switchMaster();

View File

@ -174,7 +174,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.noOPN2Vol=true;
ds.newVolumeScaling=false;
ds.volMacroLinger=false;
ds.brokenOutVol=true; // ???
ds.brokenOutVol=true;
ds.brokenOutVol2=true;
ds.e1e2StopOnSameNote=true;
ds.brokenPortaArp=false;
ds.snNoLowPeriods=true;
@ -206,6 +207,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.tuning=433.2;
}*/
// Game Boy arp+soundLen screwery
ds.systemFlags[0].set("enoughAlready",true);
logI("reading module data...");
if (ds.version>0x0c) {
ds.subsong[0]->hilightA=reader.readC();
@ -490,6 +494,19 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ins->fm.op[j].ssgEnv
);
}
// swap alg operator 2 and 3 if YMU759
if (ds.system[0]==DIV_SYSTEM_YMU759 && ins->fm.ops==4) {
DivInstrumentFM::Operator oldOp=ins->fm.op[2];
ins->fm.op[2]=ins->fm.op[1];
ins->fm.op[1]=oldOp;
if (ins->fm.alg==1) {
ins->fm.alg=2;
} else if (ins->fm.alg==2) {
ins->fm.alg=1;
}
}
} else { // STD
if (ds.system[0]!=DIV_SYSTEM_GB || ds.version<0x12) {
ins->std.volMacro.len=reader.readC();
@ -1686,6 +1703,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<117) {
ds.disableSampleMacro=true;
}
if (ds.version<121) {
ds.brokenOutVol2=false;
}
ds.isDMF=false;
reader.readS(); // reserved
@ -2123,7 +2143,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
} else {
reader.readC();
}
for (int i=0; i<2; i++) {
if (ds.version>=121) {
ds.brokenOutVol2=reader.readC();
} else {
reader.readC();
}
for (int i=0; i<1; i++) {
reader.readC();
}
}
@ -4461,7 +4486,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeC(song.jumpTreatment);
w->writeC(song.autoSystem);
w->writeC(song.disableSampleMacro);
for (int i=0; i<2; i++) {
w->writeC(song.brokenOutVol2);
for (int i=0; i<1; i++) {
w->writeC(0);
}
@ -4977,8 +5003,6 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
}
}
// TODO: take care of new arp macro format
w->writeC(i->std.arpMacro.len);
bool arpMacroMode=false;
int arpMacroHowManyFixed=0;
int realArpMacroLen=i->std.arpMacro.len;
@ -4996,13 +5020,25 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
}
}
if (realArpMacroLen>127) realArpMacroLen=127;
w->writeC(realArpMacroLen);
if (arpMacroMode) {
for (int j=0; j<realArpMacroLen; j++) {
w->writeI(i->std.arpMacro.val[j]);
if ((i->std.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) {
w->writeI(i->std.arpMacro.val[j]^0x40000000);
} else {
w->writeI(i->std.arpMacro.val[j]);
}
}
} else {
for (int j=0; j<realArpMacroLen; j++) {
w->writeI(i->std.arpMacro.val[j]+12);
if ((i->std.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) {
w->writeI((i->std.arpMacro.val[j]^0x40000000)+12);
} else {
w->writeI(i->std.arpMacro.val[j]+12);
}
}
}
if (realArpMacroLen>0) {

View File

@ -1501,12 +1501,11 @@ bool DivInstrument::saveDMP(const char* path) {
if (std.volMacro.len>0) w->writeC(std.volMacro.loop);
}
w->writeC(std.arpMacro.len);
bool arpMacroMode=false;
int arpMacroHowManyFixed=0;
int realArpMacroLen=std.arpMacro.len;
for (int i=0; i<std.arpMacro.len; i++) {
if ((std.arpMacro.val[i]&0xc0000000)==0x40000000 || (std.arpMacro.val[i]&0xc0000000)==0x80000000) {
for (int j=0; j<std.arpMacro.len; j++) {
if ((std.arpMacro.val[j]&0xc0000000)==0x40000000 || (std.arpMacro.val[j]&0xc0000000)==0x80000000) {
arpMacroHowManyFixed++;
}
}
@ -1519,13 +1518,25 @@ bool DivInstrument::saveDMP(const char* path) {
}
}
if (realArpMacroLen>127) realArpMacroLen=127;
w->writeC(realArpMacroLen);
if (arpMacroMode) {
for (int i=0; i<realArpMacroLen; i++) {
w->writeI(std.arpMacro.val[i]);
for (int j=0; j<realArpMacroLen; j++) {
if ((std.arpMacro.val[j]&0xc0000000)==0x40000000 || (std.arpMacro.val[j]&0xc0000000)==0x80000000) {
w->writeI(std.arpMacro.val[j]^0x40000000);
} else {
w->writeI(std.arpMacro.val[j]);
}
}
} else {
for (int i=0; i<realArpMacroLen; i++) {
w->writeI(std.arpMacro.val[i]+12);
for (int j=0; j<realArpMacroLen; j++) {
if ((std.arpMacro.val[j]&0xc0000000)==0x40000000 || (std.arpMacro.val[j]&0xc0000000)==0x80000000) {
w->writeI((std.arpMacro.val[j]^0x40000000)+12);
} else {
w->writeI(std.arpMacro.val[j]+12);
}
}
}
if (realArpMacroLen>0) {

View File

@ -179,11 +179,13 @@ struct DivInstrumentMacro {
String name;
int val[256];
unsigned int mode;
bool open;
unsigned char open;
unsigned char len, delay, speed, loop, rel;
// the following variables are used by the GUI and not saved in the file
int vScroll, vZoom;
int typeMemory[16];
unsigned char lenMemory;
explicit DivInstrumentMacro(const String& n, bool initOpen=false):
name(n),
@ -195,8 +197,10 @@ struct DivInstrumentMacro {
loop(255),
rel(255),
vScroll(0),
vZoom(-1) {
vZoom(-1),
lenMemory(0) {
memset(val,0,256*sizeof(int));
memset(typeMemory,0,16*sizeof(int));
}
};

View File

@ -21,10 +21,28 @@
#include "instrument.h"
#include "engine.h"
#define ADSR_LOW source.val[0]
#define ADSR_HIGH source.val[1]
#define ADSR_AR source.val[2]
#define ADSR_HT source.val[3]
#define ADSR_DR source.val[4]
#define ADSR_SL source.val[5]
#define ADSR_ST source.val[6]
#define ADSR_SR source.val[7]
#define ADSR_RR source.val[8]
#define LFO_SPEED source.val[11]
#define LFO_WAVE source.val[12]
#define LFO_PHASE source.val[13]
#define LFO_LOOP source.val[14]
#define LFO_GLOBAL source.val[15]
void DivMacroStruct::prepare(DivInstrumentMacro& source, DivEngine* e) {
has=had=actualHad=will=true;
mode=source.mode;
type=(source.open>>1)&3;
linger=(source.name=="vol" && e->song.volMacroLinger);
lfoPos=LFO_PHASE;
}
void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tick) {
@ -53,24 +71,85 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic
}
actualHad=has;
had=actualHad;
if (has) {
lastPos=pos;
val=source.val[pos++];
if (pos>source.rel && !released) {
if (source.loop<source.len && source.loop<source.rel) {
pos=source.loop;
} else {
pos--;
if (type==0) { // sequence
lastPos=pos;
val=source.val[pos++];
if (pos>source.rel && !released) {
if (source.loop<source.len && source.loop<source.rel) {
pos=source.loop;
} else {
pos--;
}
}
if (pos>=source.len) {
if (source.loop<source.len && (source.loop>=source.rel || source.rel>=source.len)) {
pos=source.loop;
} else if (linger) {
pos--;
} else {
has=false;
}
}
}
if (pos>=source.len) {
if (source.loop<source.len && (source.loop>=source.rel || source.rel>=source.len)) {
pos=source.loop;
} else if (linger) {
pos--;
} else {
has=false;
if (type==1) { // ADSR
if (released && lastPos<3) lastPos=3;
switch (lastPos) {
case 0: // attack
pos+=ADSR_AR;
if (pos>255) {
pos=255;
lastPos=1;
delay=ADSR_HT;
}
break;
case 1: // decay
pos-=ADSR_DR;
if (pos<=ADSR_SL) {
pos=ADSR_SL;
lastPos=2;
delay=ADSR_ST;
}
break;
case 2: // sustain
pos-=ADSR_SR;
if (pos<0) {
pos=0;
lastPos=4;
}
break;
case 3: // release
pos-=ADSR_RR;
if (pos<0) {
pos=0;
lastPos=4;
}
break;
case 4: // end
pos=0;
if (!linger) has=false;
break;
}
val=ADSR_LOW+((pos+(ADSR_HIGH-ADSR_LOW)*pos)>>8);
}
if (type==2) { // LFO
lfoPos+=LFO_SPEED;
lfoPos&=1023;
int lfoOut=0;
switch (LFO_WAVE&3) {
case 0: // triangle
lfoOut=((lfoPos&512)?(1023-lfoPos):(lfoPos))>>1;
break;
case 1: // saw
lfoOut=lfoPos>>2;
break;
case 2: // pulse
lfoOut=(lfoPos&512)?255:0;
break;
}
val=ADSR_LOW+((lfoOut+(ADSR_HIGH-ADSR_LOW)*lfoOut)>>8);
}
}
}
@ -253,7 +332,14 @@ void DivMacroInt::init(DivInstrument* which) {
for (size_t i=0; i<macroListLen; i++) {
if (macroSource[i]!=NULL) {
macroList[i]->prepare(*macroSource[i],e);
hasRelease=(macroSource[i]->rel<macroSource[i]->len);
// check ADSR mode
if ((macroSource[i]->open&6)==4) {
hasRelease=false;
} else if ((macroSource[i]->open&6)==2) {
hasRelease=true;
} else {
hasRelease=(macroSource[i]->rel<macroSource[i]->len);
}
} else {
hasRelease=false;
}

View File

@ -25,13 +25,13 @@
class DivEngine;
struct DivMacroStruct {
int pos, lastPos, delay;
int pos, lastPos, lfoPos, delay;
int val;
bool has, had, actualHad, finished, will, linger, began;
unsigned int mode;
unsigned int mode, type;
void doMacro(DivInstrumentMacro& source, bool released, bool tick);
void init() {
pos=lastPos=mode=delay=0;
pos=lastPos=lfoPos=mode=type=delay=0;
has=had=actualHad=will=false;
linger=false;
began=true;
@ -42,6 +42,7 @@ struct DivMacroStruct {
DivMacroStruct():
pos(0),
lastPos(0),
lfoPos(0),
delay(0),
val(0),
has(false),
@ -51,7 +52,8 @@ struct DivMacroStruct {
will(false),
linger(false),
began(true),
mode(0) {}
mode(0),
type(0) {}
};
class DivMacroInt {

View File

@ -776,6 +776,7 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) {
if (extMode) {
chipClock=extClock;
rate=chipClock/extDiv;
clockSel=false;
} else {
clockSel=flags.getBool("halfClock",false);
switch (flags.getInt("clockSel",0)) {

View File

@ -129,18 +129,6 @@ void DivPlatformC64::tick(bool sysTick) {
rWrite(i*7+2,chan[i].duty&0xff);
rWrite(i*7+3,chan[i].duty>>8);
}
if (sysTick) {
if (chan[i].testWhen>0) {
if (--chan[i].testWhen<1) {
if (!chan[i].resetMask && !chan[i].inPorta) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64);
rWrite(i*7+5,0);
rWrite(i*7+6,0);
rWrite(i*7+4,(chan[i].wave<<4)|(ins->c64.noTest?0:8)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1));
}
}
}
}
if (chan[i].std.wave.had) {
chan[i].wave=chan[i].std.wave.val;
rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active));
@ -173,6 +161,19 @@ void DivPlatformC64::tick(bool sysTick) {
rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active));
}
if (sysTick) {
if (chan[i].testWhen>0) {
if (--chan[i].testWhen<1) {
if (!chan[i].resetMask && !chan[i].inPorta) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64);
rWrite(i*7+5,0);
rWrite(i*7+6,0);
rWrite(i*7+4,(chan[i].wave<<4)|(ins->c64.noTest?0:8)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1));
}
}
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].freq>0xffff) chan[i].freq=0xffff;

View File

@ -169,7 +169,9 @@ void DivPlatformGB::tick(bool sysTick) {
if (chan[i].baseFreq>255) chan[i].baseFreq=255;
if (chan[i].baseFreq<0) chan[i].baseFreq=0;
} else {
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val,24));
if (!chan[i].inPorta) {
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val,24));
}
}
chan[i].freqChanged=true;
}
@ -313,6 +315,9 @@ void DivPlatformGB::tick(bool sysTick) {
rWrite(16+i*5+3,(2048-chan[i].freq)&0xff);
rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<63)<<6));
}
if (enoughAlready) { // more compat garbage
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63)));
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
@ -645,6 +650,7 @@ void DivPlatformGB::setFlags(const DivConfig& flags) {
model=GB_MODEL_AGB;
break;
}
enoughAlready=flags.getBool("enoughAlready",false);
}
int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {

View File

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

View File

@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _USE_MATH_DEFINES
#include "msm5232.h"
#include "../engine.h"
#include <math.h>
@ -52,13 +53,26 @@ void DivPlatformMSM5232::acquire(short* bufL, short* bufR, size_t start, size_t
regPool[w.addr&0x0f]=w.val;
writes.pop();
}
memset(temp,0,16*sizeof(short));
/*for (int i=0; i<8; 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);
}*/
for (int i=0; i<8; i++) {
int o=(
((regPool[12+(i>>4)]&1)?((msm->vo16[i]*partVolume[3+(i&4)])>>8):0)+
((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;
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(o,-32768,32767);
}
msm->sound_stream_update(temp);
clockDriftLFOPos+=clockDriftLFOSpeed;
clockDriftLFOPos&=(1U<<21)-1;
clockDriftAccum+=clockDriftLFOWave[clockDriftLFOPos>>13];
if (clockDriftAccum>=2048) {
clockDriftAccum-=2048;
} else {
memset(temp,0,16*sizeof(short));
msm->sound_stream_update(temp);
}
//printf("tempL: %d tempR: %d\n",tempL,tempR);
bufL[h]=0;
@ -229,10 +243,22 @@ int DivPlatformMSM5232::dispatch(DivCommand c) {
}
break;
}
case DIV_CMD_WAVE:
groupControl[c.chan>>2]=c.value&0x1f;
updateGroup[c.chan>>2]=true;
break;
case DIV_CMD_STD_NOISE_MODE:
chan[c.chan].noise=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_FM_AR:
groupAR[c.chan>>2]=attackMap[c.value&7];
updateGroupAR[c.chan>>2]=true;
break;
case DIV_CMD_FM_DR:
groupDR[c.chan>>2]=decayMap[c.value&15];
updateGroupDR[c.chan>>2]=true;
break;
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=NOTE_LINEAR(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
chan[c.chan].freqChanged=true;
@ -310,6 +336,8 @@ void DivPlatformMSM5232::reset() {
cycles=0;
curChan=-1;
delay=500;
clockDriftLFOPos=0;
clockDriftAccum=0;
for (int i=0; i<2; i++) {
groupControl[i]=15|(groupEnv[i]?0x20:0);
@ -381,6 +409,11 @@ void DivPlatformMSM5232::setFlags(const DivConfig& flags) {
capacitance[6]*0.000000001,
capacitance[7]*0.000000001
);
for (int i=0; i<256; i++) {
clockDriftLFOWave[i]=(1.0+sin(M_PI*(double)i/128.0))*flags.getInt("vibDepth",0.0f);
}
clockDriftLFOSpeed=flags.getInt("vibSpeed",0);
}
void DivPlatformMSM5232::poke(unsigned int addr, unsigned short val) {

View File

@ -57,6 +57,7 @@ class DivPlatformMSM5232: public DivDispatch {
DivDispatchOscBuffer* oscBuf[8];
int partVolume[8];
int initPartVolume[8];
int clockDriftLFOWave[256];
double capacitance[8];
bool isMuted[8];
bool updateGroup[2];
@ -73,7 +74,8 @@ class DivPlatformMSM5232: public DivDispatch {
};
std::queue<QueuedWrite> writes;
int cycles, curChan, delay, detune;
int cycles, curChan, delay, detune, clockDriftAccum;
unsigned int clockDriftLFOPos, clockDriftLFOSpeed;
short temp[16];
msm5232_device* msm;
unsigned char regPool[128];

View File

@ -167,7 +167,7 @@ int DivPlatformMSM6258::dispatch(DivCommand c) {
chan[c.chan].sample=-1;
chan[c.chan].macroInit(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
if ((12*sampleBank+c.value%12)<0 || (12*sampleBank+c.value%12)>=parent->song.sampleLen) {
break;
}
//DivSample* s=parent->getSample(12*sampleBank+c.value%12);

View File

@ -158,7 +158,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
chan[c.chan].sample=-1;
chan[c.chan].macroInit(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
if ((12*sampleBank+c.value%12)<0 || (12*sampleBank+c.value%12)>=parent->song.sampleLen) {
break;
}
//DivSample* s=parent->getSample(12*sampleBank+c.value%12);

View File

@ -416,10 +416,12 @@ int DivPlatformNES::dispatch(DivCommand c) {
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (c.chan==2) {
rWrite(0x4000+c.chan*4,0xff);
} else {
rWrite(0x4000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
if (!parent->song.brokenOutVol2) {
if (c.chan==2) {
rWrite(0x4000+c.chan*4,0xff);
} else {
rWrite(0x4000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
}
}
break;
case DIV_CMD_NOTE_OFF:

View File

@ -19,6 +19,7 @@
#include "sms.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <math.h>
#define rWrite(a,v) {if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(0x200+a,v);}}}
@ -248,7 +249,9 @@ int DivPlatformSMS::dispatch(DivCommand c) {
chan[c.chan].actualNote=c.value;
}
chan[c.chan].active=true;
rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
if (!parent->song.brokenOutVol2) {
rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
}
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;

View File

@ -69,14 +69,16 @@ void DivPlatformSNES::acquire(short* bufL, short* bufR, size_t start, size_t len
short out[2];
short chOut[16];
for (size_t h=start; h<start+len; h++) {
// TODO: delay
if (!writes.empty()) {
QueuedWrite w=writes.front();
dsp.write(w.addr,w.val);
regPool[w.addr&0x7f]=w.val;
writes.pop();
if (--delay<=0) {
delay=0;
if (!writes.empty()) {
QueuedWrite w=writes.front();
dsp.write(w.addr,w.val);
regPool[w.addr&0x7f]=w.val;
writes.pop();
delay=(w.addr==0x5c)?8:1;
}
}
dsp.set_output(out,1);
dsp.run(32);
dsp.get_voice_outputs(chOut);
@ -159,7 +161,30 @@ void DivPlatformSNES::tick(bool sysTick) {
}
}
if (chan[i].std.vol.had || chan[i].std.panL.had || chan[i].std.panR.had || hasInverted) {
writeOutVol(i);
chan[i].shallWriteVol=true;
}
if (chan[i].std.ex2.had) {
if (chan[i].std.ex2.val&0x80) {
switch (chan[i].std.ex2.val&0x60) {
case 0x00:
chan[i].state.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LINEAR;
break;
case 0x20:
chan[i].state.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LOG;
break;
case 0x40:
chan[i].state.gainMode=DivInstrumentSNES::GAIN_MODE_INC_LINEAR;
break;
case 0x60:
chan[i].state.gainMode=DivInstrumentSNES::GAIN_MODE_INC_INVLOG;
break;
}
chan[i].state.gain=chan[i].std.ex2.val&31;
} else {
chan[i].state.gainMode=DivInstrumentSNES::GAIN_MODE_DIRECT;
chan[i].state.gain=chan[i].std.ex2.val&127;
}
chan[i].shallWriteEnv=true;
}
if (chan[i].setPos) {
// force keyon
@ -173,6 +198,8 @@ void DivPlatformSNES::tick(bool sysTick) {
updateWave(i);
}
}
}
for (int i=0; i<8; i++) {
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;
@ -205,6 +232,7 @@ void DivPlatformSNES::tick(bool sysTick) {
sampleMem[tabAddr+2]=loop&0xff;
sampleMem[tabAddr+3]=loop>>8;
kon|=(1<<i);
koff|=(1<<i);
chan[i].keyOn=false;
}
if (chan[i].keyOff) {
@ -220,6 +248,9 @@ void DivPlatformSNES::tick(bool sysTick) {
}
}
}
if (koff!=0) {
rWrite(0x5c,koff);
}
if (writeControl) {
unsigned char control=(noiseFreq&0x1f)|(echoOn?0:0x20);
rWrite(0x6c,control);
@ -267,11 +298,24 @@ void DivPlatformSNES::tick(bool sysTick) {
rWrite(0x4d,echoBits);
writeEcho=false;
}
for (int i=0; i<8; i++) {
if (chan[i].shallWriteEnv) {
writeEnv(i);
chan[i].shallWriteEnv=false;
}
}
if (koff!=0) {
rWrite(0x5c,0);
}
if (kon!=0) {
rWrite(0x4c,kon);
}
// always write KOFF as it's constantly polled
rWrite(0x5c,koff);
for (int i=0; i<8; i++) {
if (chan[i].shallWriteVol) {
writeOutVol(i);
chan[i].shallWriteVol=false;
}
}
}
int DivPlatformSNES::dispatch(DivCommand c) {
@ -301,7 +345,7 @@ int DivPlatformSNES::dispatch(DivCommand c) {
}
chan[c.chan].active=true;
if (chan[c.chan].insChanged || chan[c.chan].state.sus) {
writeEnv(c.chan);
chan[c.chan].shallWriteEnv=true;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=round(NOTE_FREQUENCY(c.value));
@ -318,7 +362,7 @@ int DivPlatformSNES::dispatch(DivCommand c) {
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
if (chan[c.chan].state.sus) {
writeEnv(c.chan);
chan[c.chan].shallWriteEnv=true;
} else {
chan[c.chan].macroInit(NULL);
}
@ -328,7 +372,7 @@ int DivPlatformSNES::dispatch(DivCommand c) {
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
if (chan[c.chan].state.sus) {
writeEnv(c.chan);
chan[c.chan].shallWriteEnv=true;
}
chan[c.chan].std.release();
break;
@ -346,7 +390,7 @@ int DivPlatformSNES::dispatch(DivCommand c) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
writeOutVol(c.chan);
chan[c.chan].shallWriteVol=true;
}
}
break;
@ -356,7 +400,7 @@ int DivPlatformSNES::dispatch(DivCommand c) {
case DIV_CMD_PANNING:
chan[c.chan].panL=c.value>>1;
chan[c.chan].panR=c.value2>>1;
writeOutVol(c.chan);
chan[c.chan].shallWriteVol=true;
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
@ -418,7 +462,7 @@ int DivPlatformSNES::dispatch(DivCommand c) {
case DIV_CMD_SNES_INVERT:
chan[c.chan].invertL=(c.value>>4);
chan[c.chan].invertR=c.chan&15;
writeOutVol(c.chan);
chan[c.chan].shallWriteVol=true;
break;
case DIV_CMD_SNES_GAIN_MODE:
if (c.value) {
@ -443,7 +487,7 @@ int DivPlatformSNES::dispatch(DivCommand c) {
} else {
chan[c.chan].state.useEnv=true;
}
writeEnv(c.chan);
chan[c.chan].shallWriteEnv=true;
break;
case DIV_CMD_SNES_GAIN:
if (chan[c.chan].state.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT) {
@ -451,7 +495,7 @@ int DivPlatformSNES::dispatch(DivCommand c) {
} else {
chan[c.chan].state.gain=c.value&0x1f;
}
if (!chan[c.chan].state.useEnv) writeEnv(c.chan);
if (!chan[c.chan].state.useEnv) chan[c.chan].shallWriteEnv=true;
break;
case DIV_CMD_STD_NOISE_FREQ:
noiseFreq=c.value&0x1f;
@ -459,19 +503,19 @@ int DivPlatformSNES::dispatch(DivCommand c) {
break;
case DIV_CMD_FM_AR:
chan[c.chan].state.a=c.value&15;
if (chan[c.chan].state.useEnv) writeEnv(c.chan);
if (chan[c.chan].state.useEnv) chan[c.chan].shallWriteEnv=true;
break;
case DIV_CMD_FM_DR:
chan[c.chan].state.d=c.value&7;
if (chan[c.chan].state.useEnv) writeEnv(c.chan);
if (chan[c.chan].state.useEnv) chan[c.chan].shallWriteEnv=true;
break;
case DIV_CMD_FM_SL:
chan[c.chan].state.s=c.value&7;
if (chan[c.chan].state.useEnv) writeEnv(c.chan);
if (chan[c.chan].state.useEnv) chan[c.chan].shallWriteEnv=true;
break;
case DIV_CMD_FM_RR:
chan[c.chan].state.r=c.value&0x1f;
if (chan[c.chan].state.useEnv) writeEnv(c.chan);
if (chan[c.chan].state.useEnv) chan[c.chan].shallWriteEnv=true;
break;
case DIV_CMD_SNES_ECHO:
chan[c.chan].echo=c.value;
@ -582,7 +626,7 @@ void DivPlatformSNES::writeEnv(int ch) {
void DivPlatformSNES::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
writeOutVol(ch);
chan[ch].shallWriteVol=true;
}
void DivPlatformSNES::forceIns() {
@ -760,6 +804,10 @@ void DivPlatformSNES::renderSamples() {
if (actualLength>0) {
sampleOff[i]=memPos;
memcpy(&copyOfSampleMem[memPos],s->dataBRR,actualLength);
// inject loop if needed
if (s->loop) {
copyOfSampleMem[memPos+actualLength-9]|=3;
}
memPos+=actualLength;
}
if (actualLength<length) {

View File

@ -33,7 +33,7 @@ class DivPlatformSNES: public DivDispatch {
int sample, wave, ins;
int note;
int panL, panR;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos, noise, echo, pitchMod, invertL, invertR;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos, noise, echo, pitchMod, invertL, invertR, shallWriteVol, shallWriteEnv;
int vol, outVol;
int wtLen;
DivInstrumentSNES state;
@ -68,6 +68,8 @@ class DivPlatformSNES: public DivDispatch {
pitchMod(false),
invertL(false),
invertR(false),
shallWriteVol(false),
shallWriteEnv(false),
vol(127),
outVol(127),
wtLen(16) {}
@ -77,6 +79,7 @@ class DivPlatformSNES: public DivDispatch {
bool isMuted[8];
int globalVolL, globalVolR;
unsigned char noiseFreq;
signed char delay;
signed char echoVolL, echoVolR, echoFeedback;
signed char echoFIR[8];
unsigned char echoDelay;

View File

@ -596,10 +596,10 @@ void msm5232_device::TG_group_advance(int groupidx)
/* calculate signed output */
if (!voi->mute) {
o16 += ( (out16-(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH;
o8 += ( (out8 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH;
o4 += ( (out4 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH;
o2 += ( (out2 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH;
o16 += vo16[groupidx*4+(4-i)] = ( (out16-(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH;
o8 += vo8 [groupidx*4+(4-i)] = ( (out8 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH;
o4 += vo4 [groupidx*4+(4-i)] = ( (out4 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH;
o2 += vo2 [groupidx*4+(4-i)] = ( (out2 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH;
if (i == 1 && groupidx == 1)
{

View File

@ -33,6 +33,11 @@ public:
int get_rate();
int vo16[8];
int vo8[8];
int vo4[8];
int vo2[8];
private:
struct VOICE {
uint8_t mode;

View File

@ -0,0 +1,361 @@
// T6W28_Snd_Emu
#include "T6W28_Apu.h"
#undef require
#define require( expr ) assert( expr )
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for
more details. You should have received a copy of the GNU Lesser General
Public License along with this module; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
// T6W28_Osc
namespace MDFN_IEN_NGP
{
T6W28_Osc::T6W28_Osc()
{
outputs [0] = NULL; // always stays NULL
outputs [1] = NULL;
outputs [2] = NULL;
outputs [3] = NULL;
}
void T6W28_Osc::reset()
{
delay = 0;
last_amp_left = 0;
last_amp_right = 0;
volume_left = 0;
volume_right = 0;
}
// T6W28_Square
blip_inline void T6W28_Square::reset()
{
period = 0;
phase = 0;
T6W28_Osc::reset();
}
void T6W28_Square::run( sms_time_t time, sms_time_t end_time )
{
if ((!volume_left && !volume_right) || period <= 128 )
{
// ignore 16kHz and higher
if ( last_amp_left )
{
synth->offset( time, -last_amp_left, outputs[2] );
last_amp_left = 0;
}
if ( last_amp_right )
{
synth->offset( time, -last_amp_right, outputs[1] );
last_amp_right = 0;
}
time += delay;
if ( !period )
{
time = end_time;
}
else if ( time < end_time )
{
// keep calculating phase
int count = (end_time - time + period - 1) / period;
phase = (phase + count) & 1;
time += count * period;
}
}
else
{
int amp_left = phase ? volume_left : -volume_left;
int amp_right = phase ? volume_right : -volume_right;
{
int delta_left = amp_left - last_amp_left;
int delta_right = amp_right - last_amp_right;
if ( delta_left )
{
last_amp_left = amp_left;
synth->offset( time, delta_left, outputs[2] );
}
if ( delta_right )
{
last_amp_right = amp_right;
synth->offset( time, delta_right, outputs[1] );
}
}
time += delay;
if ( time < end_time )
{
Blip_Buffer* const output_left = this->outputs[2];
Blip_Buffer* const output_right = this->outputs[1];
int delta_left = amp_left * 2;
int delta_right = amp_right * 2;
do
{
delta_left = -delta_left;
delta_right = -delta_right;
synth->offset_inline( time, delta_left, output_left );
synth->offset_inline( time, delta_right, output_right );
time += period;
phase ^= 1;
}
while ( time < end_time );
this->last_amp_left = phase ? volume_left : -volume_left;
this->last_amp_right = phase ? volume_right : -volume_right;
}
}
delay = time - end_time;
}
// T6W28_Noise
static const int noise_periods [3] = { 0x100, 0x200, 0x400 };
blip_inline void T6W28_Noise::reset()
{
period = &noise_periods [0];
shifter = 0x4000;
tap = 13;
period_extra = 0;
T6W28_Osc::reset();
}
void T6W28_Noise::run( sms_time_t time, sms_time_t end_time )
{
int amp_left = volume_left;
int amp_right = volume_right;
if ( shifter & 1 )
{
amp_left = -amp_left;
amp_right = -amp_right;
}
{
int delta_left = amp_left - last_amp_left;
int delta_right = amp_right - last_amp_right;
if ( delta_left )
{
last_amp_left = amp_left;
synth.offset( time, delta_left, outputs[2] );
}
if ( delta_right )
{
last_amp_right = amp_right;
synth.offset( time, delta_right, outputs[1] );
}
}
time += delay;
if ( !volume_left && !volume_right )
time = end_time;
if ( time < end_time )
{
Blip_Buffer* const output_left = this->outputs[2];
Blip_Buffer* const output_right = this->outputs[1];
unsigned l_shifter = this->shifter;
int delta_left = amp_left * 2;
int delta_right = amp_right * 2;
int l_period = *this->period * 2;
if ( !l_period )
l_period = 16;
do
{
int changed = (l_shifter + 1) & 2; // set if prev and next bits differ
l_shifter = (((l_shifter << 14) ^ (l_shifter << tap)) & 0x4000) | (l_shifter >> 1);
if ( changed )
{
delta_left = -delta_left;
synth.offset_inline( time, delta_left, output_left );
delta_right = -delta_right;
synth.offset_inline( time, delta_right, output_right );
}
time += l_period;
}
while ( time < end_time );
this->shifter = l_shifter;
this->last_amp_left = delta_left >> 1;
this->last_amp_right = delta_right >> 1;
}
delay = time - end_time;
}
// T6W28_Apu
T6W28_Apu::T6W28_Apu()
{
for ( int i = 0; i < 3; i++ )
{
squares [i].synth = &square_synth;
oscs [i] = &squares [i];
}
oscs [3] = &noise;
volume( 1.0 );
reset();
}
T6W28_Apu::~T6W28_Apu()
{
}
void T6W28_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
require( (unsigned) index < osc_count );
require( (center && left && right) || (!center && !left && !right) );
T6W28_Osc& osc = *oscs [index];
osc.outputs [1] = right;
osc.outputs [2] = left;
osc.outputs [3] = center;
}
void T6W28_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
for ( int i = 0; i < osc_count; i++ )
osc_output( i, center, left, right );
}
void T6W28_Apu::reset()
{
last_time = 0;
latch_left = 0;
latch_right = 0;
squares [0].reset();
squares [1].reset();
squares [2].reset();
noise.reset();
}
void T6W28_Apu::run_until( sms_time_t end_time )
{
require( end_time >= last_time ); // end_time must not be before previous time
if ( end_time > last_time )
{
// run oscillators
for ( int i = 0; i < osc_count; ++i )
{
T6W28_Osc& osc = *oscs [i];
if ( osc.outputs[1] )
{
if ( i < 3 )
squares [i].run( last_time, end_time );
else
noise.run( last_time, end_time );
}
}
last_time = end_time;
}
}
bool T6W28_Apu::end_frame( sms_time_t end_time )
{
if ( end_time > last_time )
run_until( end_time );
assert( last_time >= end_time );
last_time -= end_time;
return(1);
}
static const unsigned char volumes [16] = {
// volumes [i] = 64 * pow( 1.26, 15 - i ) / pow( 1.26, 15 )
64, 50, 39, 31, 24, 19, 15, 12, 9, 7, 5, 4, 3, 2, 1, 0
};
void T6W28_Apu::write_data_left( sms_time_t time, int data )
{
require( (unsigned) data <= 0xFF );
run_until( time );
if ( data & 0x80 )
latch_left = data;
int index = (latch_left >> 5) & 3;
if ( latch_left & 0x10 )
{
oscs [index]->volume_left = volumes [data & 15];
}
else if ( index < 3 )
{
T6W28_Square& sq = squares [index];
if ( data & 0x80 )
sq.period = (sq.period & 0xFF00) | (data << 4 & 0x00FF);
else
sq.period = (sq.period & 0x00FF) | (data << 8 & 0x3F00);
}
}
void T6W28_Apu::write_data_right( sms_time_t time, int data )
{
require( (unsigned) data <= 0xFF );
run_until( time );
if ( data & 0x80 )
latch_right = data;
int index = (latch_right >> 5) & 3;
//printf("%d\n", index);
if ( latch_right & 0x10 )
{
oscs [index]->volume_right = volumes [data & 15];
}
else if ( index == 2 )
{
if ( data & 0x80 )
noise.period_extra = (noise.period_extra & 0xFF00) | (data << 4 & 0x00FF);
else
noise.period_extra = (noise.period_extra & 0x00FF) | (data << 8 & 0x3F00);
}
else if(index == 3)
{
int select = data & 3;
if ( select < 3 )
noise.period = &noise_periods [select];
else
noise.period = &noise.period_extra;
int const tap_disabled = 16;
noise.tap = (data & 0x04) ? 13 : tap_disabled;
noise.shifter = 0x4000;
}
}
}

View File

@ -0,0 +1,89 @@
// T6W28_Snd_Emu
#ifndef SMS_APU_H
#define SMS_APU_H
namespace MDFN_IEN_NGP
{
typedef long sms_time_t; // clock cycle count
}
#include "T6W28_Oscs.h"
namespace MDFN_IEN_NGP
{
typedef struct
{
int sq_period[3];
int sq_phase[3];
unsigned int noise_period;
unsigned int noise_period_extra;
unsigned int noise_shifter;
unsigned int noise_tap;
int delay[4];
int volume_left[4];
int volume_right[4];
unsigned char latch_left, latch_right;
} T6W28_ApuState;
class T6W28_Apu {
public:
// Outputs can be assigned to a single buffer for mono output, or to three
// buffers for stereo output (using Stereo_Buffer to do the mixing).
// Assign all oscillator outputs to specified buffer(s). If buffer
// is NULL, silences all oscillators.
void output( Blip_Buffer* mono );
void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right );
// Assign single oscillator output to buffer(s). Valid indicies are 0 to 3,
// which refer to Square 1, Square 2, Square 3, and Noise. If buffer is NULL,
// silences oscillator.
enum { osc_count = 4 };
void osc_output( int index, Blip_Buffer* mono );
void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right );
// Reset oscillators and internal state
void reset();
// Write to data port
void write_data_left( sms_time_t, int );
void write_data_right( sms_time_t, int );
// Run all oscillators up to specified time, end current frame, then
// start a new frame at time 0. Returns true if any oscillators added
// sound to one of the left/right buffers, false if they only added
// to the center buffer.
bool end_frame( sms_time_t );
public:
T6W28_Apu();
~T6W28_Apu();
private:
// noncopyable
T6W28_Apu( const T6W28_Apu& );
T6W28_Apu& operator = ( const T6W28_Apu& );
T6W28_Osc* oscs [osc_count];
T6W28_Square squares [3];
//T6W28_Square::Synth square_synth; // used by squares
sms_time_t last_time;
int latch_left, latch_right;
T6W28_Noise noise;
void run_until( sms_time_t );
};
inline void T6W28_Apu::output( Blip_Buffer* b ) { output( b, b, b ); }
inline void T6W28_Apu::osc_output( int i, Blip_Buffer* b ) { osc_output( i, b, b, b ); }
}
#endif

View File

@ -0,0 +1,50 @@
// Private oscillators used by T6W28_Apu
// T6W28_Snd_Emu
#ifndef SMS_OSCS_H
#define SMS_OSCS_H
namespace MDFN_IEN_NGP
{
struct T6W28_Osc
{
int output_select;
int delay;
int last_amp_left;
int last_amp_right;
int volume_left;
int volume_right;
T6W28_Osc();
void reset();
};
struct T6W28_Square : T6W28_Osc
{
int period;
int phase;
void reset();
void run( sms_time_t, sms_time_t );
};
struct T6W28_Noise : T6W28_Osc
{
const int* period;
int period_extra;
unsigned shifter;
unsigned tap;
void reset();
void run( sms_time_t, sms_time_t );
};
}
#endif

View File

@ -0,0 +1,499 @@
/******************************************************************************/
/* Mednafen Virtual Boy Emulation Module */
/******************************************************************************/
/* vsu.cpp:
** Copyright (C) 2010-2016 Mednafen Team
**
** 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 "vsu.h"
#include <stdio.h>
#include <assert.h>
#include <string.h>
static const unsigned int Tap_LUT[8] = { 15 - 1, 11 - 1, 14 - 1, 5 - 1, 9 - 1, 7 - 1, 10 - 1, 12 - 1 };
#define MDFN_UNLIKELY(x) x
VSU::VSU()
{
for(int ch = 0; ch < 6; ch++)
{
for(int lr = 0; lr < 2; lr++)
last_output[ch][lr] = 0;
}
}
VSU::~VSU()
{
}
void VSU::SetSoundRate(double rate)
{
/*
for(int y = 0; y < 2; y++)
{
sbuf[y].set_sample_rate(rate ? rate : 44100, 50);
sbuf[y].clock_rate((long)(VB_MASTER_CLOCK / 4));
sbuf[y].bass_freq(20);
}
*/
}
void VSU::Power(void)
{
SweepControl = 0;
SweepModCounter = 0;
SweepModClockDivider = 1;
for(int ch = 0; ch < 6; ch++)
{
IntlControl[ch] = 0;
LeftLevel[ch] = 0;
RightLevel[ch] = 0;
Frequency[ch] = 0;
EnvControl[ch] = 0;
RAMAddress[ch] = 0;
EffFreq[ch] = 0;
Envelope[ch] = 0;
WavePos[ch] = 0;
FreqCounter[ch] = 1;
IntervalCounter[ch] = 0;
EnvelopeCounter[ch] = 1;
EffectsClockDivider[ch] = 4800;
IntervalClockDivider[ch] = 4;
EnvelopeClockDivider[ch] = 4;
LatcherClockDivider[ch] = 120;
}
ModWavePos = 0;
NoiseLatcherClockDivider = 120;
NoiseLatcher = 0;
lfsr = 0;
memset(WaveData, 0, sizeof(WaveData));
memset(ModData, 0, sizeof(ModData));
last_ts = 0;
}
void VSU::Write(int timestamp, unsigned int A, unsigned char V)
{
if(MDFN_UNLIKELY(A & 0x3))
{
return;
}
//
//
A &= 0x7FF;
//Update(timestamp);
//printf("VSU Write: %d, %08x %02x\n", timestamp, A, V);
if(A < 0x280)
WaveData[A >> 7][(A >> 2) & 0x1F] = V & 0x3F;
else if(A < 0x400)
{
//if(A >= 0x300)
//printf("Modulation mirror write? %08x %02x\n", A, V);
ModData[(A >> 2) & 0x1F] = V;
}
else if(A < 0x600)
{
int ch = (A >> 6) & 0xF;
//if(ch < 6)
//printf("Ch: %d, Reg: %d, Value: %02x\n", ch, (A >> 2) & 0xF, V);
if(ch > 5)
{
if(A == 0x580 && (V & 1))
{
//puts("STOP, HAMMER TIME");
for(int i = 0; i < 6; i++)
IntlControl[i] &= ~0x80;
}
}
else
switch((A >> 2) & 0xF)
{
case 0x0: IntlControl[ch] = V & ~0x40;
if(V & 0x80)
{
EffFreq[ch] = Frequency[ch];
if(ch == 5)
FreqCounter[ch] = 10 * (2048 - EffFreq[ch]);
else
FreqCounter[ch] = 2048 - EffFreq[ch];
IntervalCounter[ch] = (V & 0x1F) + 1;
EnvelopeCounter[ch] = (EnvControl[ch] & 0x7) + 1;
if(ch == 4)
{
SweepModCounter = (SweepControl >> 4) & 7;
SweepModClockDivider = (SweepControl & 0x80) ? 8 : 1;
ModWavePos = 0;
}
WavePos[ch] = 0;
if(ch == 5) // Not sure if this is correct.
lfsr = 1;
//if(!(IntlControl[ch] & 0x80))
// Envelope[ch] = (EnvControl[ch] >> 4) & 0xF;
EffectsClockDivider[ch] = 4800;
IntervalClockDivider[ch] = 4;
EnvelopeClockDivider[ch] = 4;
}
break;
case 0x1: LeftLevel[ch] = (V >> 4) & 0xF;
RightLevel[ch] = (V >> 0) & 0xF;
break;
case 0x2: Frequency[ch] &= 0xFF00;
Frequency[ch] |= V << 0;
EffFreq[ch] &= 0xFF00;
EffFreq[ch] |= V << 0;
break;
case 0x3: Frequency[ch] &= 0x00FF;
Frequency[ch] |= (V & 0x7) << 8;
EffFreq[ch] &= 0x00FF;
EffFreq[ch] |= (V & 0x7) << 8;
break;
case 0x4: EnvControl[ch] &= 0xFF00;
EnvControl[ch] |= V << 0;
Envelope[ch] = (V >> 4) & 0xF;
break;
case 0x5: EnvControl[ch] &= 0x00FF;
if(ch == 4)
EnvControl[ch] |= (V & 0x73) << 8;
else if(ch == 5)
{
EnvControl[ch] |= (V & 0x73) << 8;
lfsr = 1;
}
else
EnvControl[ch] |= (V & 0x03) << 8;
break;
case 0x6: RAMAddress[ch] = V & 0xF;
break;
case 0x7: if(ch == 4)
{
SweepControl = V;
}
break;
}
}
}
inline void VSU::CalcCurrentOutput(int ch, int &left, int &right)
{
if(!(IntlControl[ch] & 0x80))
{
left = right = 0;
return;
}
int WD;
int l_ol, r_ol;
if(ch == 5)
WD = NoiseLatcher; //(NoiseLatcher << 6) - NoiseLatcher;
else
{
if(RAMAddress[ch] > 4)
WD = 0;
else
WD = WaveData[RAMAddress[ch]][WavePos[ch]]; // - 0x20;
}
l_ol = Envelope[ch] * LeftLevel[ch];
if(l_ol)
{
l_ol >>= 3;
l_ol += 1;
}
r_ol = Envelope[ch] * RightLevel[ch];
if(r_ol)
{
r_ol >>= 3;
r_ol += 1;
}
left = WD * l_ol;
right = WD * r_ol;
}
void VSU::Update(int timestamp)
{
//puts("VSU Start");
int left, right;
for(int ch = 0; ch < 6; ch++)
{
int clocks = timestamp - last_ts;
//int running_timestamp = last_ts;
// Output sound here
CalcCurrentOutput(ch, left, right);
/*Synth.offset_inline(running_timestamp, left - last_output[ch][0], &sbuf[0]);
Synth.offset_inline(running_timestamp, right - last_output[ch][1], &sbuf[1]);*/
last_output[ch][0] = left;
last_output[ch][1] = right;
if(!(IntlControl[ch] & 0x80))
continue;
while(clocks > 0)
{
int chunk_clocks = clocks;
if(chunk_clocks > EffectsClockDivider[ch])
chunk_clocks = EffectsClockDivider[ch];
if(ch == 5)
{
if(chunk_clocks > NoiseLatcherClockDivider)
chunk_clocks = NoiseLatcherClockDivider;
}
else
{
if(EffFreq[ch] >= 2040)
{
if(chunk_clocks > LatcherClockDivider[ch])
chunk_clocks = LatcherClockDivider[ch];
}
else
{
if(chunk_clocks > FreqCounter[ch])
chunk_clocks = FreqCounter[ch];
}
}
if(ch == 5 && chunk_clocks > NoiseLatcherClockDivider)
chunk_clocks = NoiseLatcherClockDivider;
FreqCounter[ch] -= chunk_clocks;
while(FreqCounter[ch] <= 0)
{
if(ch == 5)
{
int feedback = ((lfsr >> 7) & 1) ^ ((lfsr >> Tap_LUT[(EnvControl[5] >> 12) & 0x7]) & 1) ^ 1;
lfsr = ((lfsr << 1) & 0x7FFF) | feedback;
FreqCounter[ch] += 10 * (2048 - EffFreq[ch]);
}
else
{
FreqCounter[ch] += 2048 - EffFreq[ch];
WavePos[ch] = (WavePos[ch] + 1) & 0x1F;
}
}
LatcherClockDivider[ch] -= chunk_clocks;
while(LatcherClockDivider[ch] <= 0)
LatcherClockDivider[ch] += 120;
if(ch == 5)
{
NoiseLatcherClockDivider -= chunk_clocks;
if(!NoiseLatcherClockDivider)
{
NoiseLatcherClockDivider = 120;
NoiseLatcher = ((lfsr & 1) << 6) - (lfsr & 1);
}
}
EffectsClockDivider[ch] -= chunk_clocks;
while(EffectsClockDivider[ch] <= 0)
{
EffectsClockDivider[ch] += 4800;
IntervalClockDivider[ch]--;
while(IntervalClockDivider[ch] <= 0)
{
IntervalClockDivider[ch] += 4;
if(IntlControl[ch] & 0x20)
{
IntervalCounter[ch]--;
if(!IntervalCounter[ch])
{
IntlControl[ch] &= ~0x80;
}
}
EnvelopeClockDivider[ch]--;
while(EnvelopeClockDivider[ch] <= 0)
{
EnvelopeClockDivider[ch] += 4;
if(EnvControl[ch] & 0x0100) // Enveloping enabled?
{
EnvelopeCounter[ch]--;
if(!EnvelopeCounter[ch])
{
EnvelopeCounter[ch] = (EnvControl[ch] & 0x7) + 1;
if(EnvControl[ch] & 0x0008) // Grow
{
if(Envelope[ch] < 0xF || (EnvControl[ch] & 0x200))
Envelope[ch] = (Envelope[ch] + 1) & 0xF;
}
else // Decay
{
if(Envelope[ch] > 0 || (EnvControl[ch] & 0x200))
Envelope[ch] = (Envelope[ch] - 1) & 0xF;
}
}
}
} // end while(EnvelopeClockDivider[ch] <= 0)
} // end while(IntervalClockDivider[ch] <= 0)
if(ch == 4)
{
SweepModClockDivider--;
while(SweepModClockDivider <= 0)
{
SweepModClockDivider += (SweepControl & 0x80) ? 8 : 1;
if(((SweepControl >> 4) & 0x7) && (EnvControl[ch] & 0x4000))
{
if(SweepModCounter)
SweepModCounter--;
if(!SweepModCounter)
{
SweepModCounter = (SweepControl >> 4) & 0x7;
if(EnvControl[ch] & 0x1000) // Modulation
{
if(ModWavePos < 32 || (EnvControl[ch] & 0x2000))
{
ModWavePos &= 0x1F;
EffFreq[ch] = (Frequency[ch] + (signed char)ModData[ModWavePos]) & 0x7FF;
ModWavePos++;
}
}
else // Sweep
{
int delta = EffFreq[ch] >> (SweepControl & 0x7);
int NewFreq = EffFreq[ch] + ((SweepControl & 0x8) ? delta : -delta);
//printf("Sweep(%d): Old: %d, New: %d\n", ch, EffFreq[ch], NewFreq);
if(NewFreq < 0)
EffFreq[ch] = 0;
else if(NewFreq > 0x7FF)
{
//EffFreq[ch] = 0x7FF;
IntlControl[ch] &= ~0x80;
}
else
EffFreq[ch] = NewFreq;
}
}
}
} // end while(SweepModClockDivider <= 0)
} // end if(ch == 4)
} // end while(EffectsClockDivider[ch] <= 0)
clocks -= chunk_clocks;
//running_timestamp += chunk_clocks;
// Output sound here too.
CalcCurrentOutput(ch, left, right);
/*
Synth.offset_inline(running_timestamp, left - last_output[ch][0], &sbuf[0]);
Synth.offset_inline(running_timestamp, right - last_output[ch][1], &sbuf[1]);
*/
last_output[ch][0] = left;
last_output[ch][1] = right;
}
}
last_ts = timestamp;
//puts("VSU End");
}
int VSU::EndFrame(int timestamp)
{
int ret = 0;
Update(timestamp);
last_ts = 0;
/*
if(SoundBuf)
{
for(int y = 0; y < 2; y++)
{
sbuf[y].end_frame(timestamp);
ret = sbuf[y].read_samples(SoundBuf + y, SoundBufMaxSize, 1);
}
}
*/
return ret;
}
unsigned char VSU::PeekWave(const unsigned int which, unsigned int Address)
{
assert(which <= 4);
Address &= 0x1F;
return(WaveData[which][Address]);
}
void VSU::PokeWave(const unsigned int which, unsigned int Address, unsigned char value)
{
assert(which <= 4);
Address &= 0x1F;
WaveData[which][Address] = value & 0x3F;
}
unsigned char VSU::PeekModWave(unsigned int Address)
{
Address &= 0x1F;
return(ModData[Address]);
}
void VSU::PokeModWave(unsigned int Address, unsigned char value)
{
Address &= 0x1F;
ModData[Address] = value & 0xFF;
}

View File

@ -0,0 +1,97 @@
/******************************************************************************/
/* Mednafen Virtual Boy Emulation Module */
/******************************************************************************/
/* vsu.h:
** Copyright (C) 2010-2016 Mednafen Team
**
** 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 __VB_VSU_H
#define __VB_VSU_H
class VSU
{
public:
int last_output[6][2];
VSU();
~VSU();
void SetSoundRate(double rate);
void Power(void);
void Write(int timestamp, unsigned int A, unsigned char V);
int EndFrame(int timestamp);
unsigned char PeekWave(const unsigned int which, unsigned int Address);
void PokeWave(const unsigned int which, unsigned int Address, unsigned char value);
unsigned char PeekModWave(unsigned int Address);
void PokeModWave(unsigned int Address, unsigned char value);
private:
void CalcCurrentOutput(int ch, int &left, int &right);
void Update(int timestamp);
unsigned char IntlControl[6];
unsigned char LeftLevel[6];
unsigned char RightLevel[6];
unsigned short Frequency[6];
unsigned short EnvControl[6]; // Channel 5/6 extra functionality tacked on too.
unsigned char RAMAddress[6];
unsigned char SweepControl;
unsigned char WaveData[5][0x20];
unsigned char ModData[0x20];
//
//
//
int EffFreq[6];
int Envelope[6];
int WavePos[6];
int ModWavePos;
int LatcherClockDivider[6];
int FreqCounter[6];
int IntervalCounter[6];
int EnvelopeCounter[6];
int SweepModCounter;
int EffectsClockDivider[6];
int IntervalClockDivider[6];
int EnvelopeClockDivider[6];
int SweepModClockDivider;
int NoiseLatcherClockDivider;
unsigned int NoiseLatcher;
unsigned int lfsr;
int last_ts;
};
#endif

View File

@ -518,7 +518,7 @@ void DivPlatformSoundUnit::setFlags(const DivConfig& flags) {
bool echoOn=flags.getBool("echo",false);
initIlCtrl=3|(echoOn?4:0);
initIlSize=((flags.getInt("echoDelay",0))&63)|(echoOn?0x40:0)|(flags.getBool("swapEcho",false)?0x80:0);
initFil1=flags.getInt("echoFeedback",0);
initFil1=flags.getInt("echoFeedback",0)|(flags.getInt("echoResolution",0)<<4);
initEchoVol=flags.getInt("echoVol",0);
sampleMemSize=flags.getInt("sampleMemSize",0);

647
src/engine/platform/vb.cpp Normal file
View File

@ -0,0 +1,647 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 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 "vb.h"
#include "../engine.h"
#include <math.h>
//#define rWrite(a,v) pendingWrites[a]=v;
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define chWrite(c,a,v) rWrite(0x400+((c)<<6)+((a)<<2),v);
#define CHIP_DIVIDER 16
const char* regCheatSheetVB[]={
"Wave0", "000",
"Wave1", "080",
"Wave2", "100",
"Wave3", "180",
"Wave4", "200",
"ModTable", "280",
"S0INT", "400",
"S0LRV", "404",
"S0FQL", "408",
"S0FQH", "40C",
"S0EV0", "410",
"S0EV1", "414",
"S0RAM", "418",
"S1INT", "440",
"S1LRV", "444",
"S1FQL", "448",
"S1FQH", "44C",
"S1EV0", "450",
"S1EV1", "454",
"S1RAM", "458",
"S2INT", "480",
"S2LRV", "484",
"S2FQL", "488",
"S2FQH", "48C",
"S2EV0", "480",
"S2EV1", "484",
"S2RAM", "488",
"S3INT", "4C0",
"S3LRV", "4C4",
"S3FQL", "4C8",
"S3FQH", "4CC",
"S3EV0", "4C0",
"S3EV1", "4C4",
"S3RAM", "4C8",
"S4INT", "500",
"S4LRV", "504",
"S4FQL", "508",
"S4FQH", "50C",
"S4EV0", "510",
"S4EV1", "514",
"S4RAM", "518",
"S4SWP", "51C",
"S5INT", "540",
"S5LRV", "544",
"S5FQL", "548",
"S5FQH", "54C",
"S5EV0", "550",
"S5EV1", "554",
"S5RAM", "558",
NULL
};
const char** DivPlatformVB::getRegisterSheet() {
return regCheatSheetVB;
}
void DivPlatformVB::acquire(short* bufL, short* bufR, size_t start, size_t len) {
for (size_t h=start; h<start+len; h++) {
// PCM part
/*
for (int i=0; i<6; i++) {
if (chan[i].pcm && chan[i].dacSample!=-1) {
chan[i].dacPeriod+=chan[i].dacRate;
if (chan[i].dacPeriod>rate) {
DivSample* s=parent->getSample(chan[i].dacSample);
if (s->samples<=0) {
chan[i].dacSample=-1;
continue;
}
chWrite(i,0x07,0);
signed char dacData=((signed char)((unsigned char)s->data8[chan[i].dacPos]^0x80))>>3;
chan[i].dacOut=CLAMP(dacData,-16,15);
if (!isMuted[i]) {
chWrite(i,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[i].outVol));
chWrite(i,0x06,chan[i].dacOut&0x1f);
} else {
chWrite(i,0x04,0xc0);
chWrite(i,0x06,0x10);
}
chan[i].dacPos++;
if (s->isLoopable() && chan[i].dacPos>=(unsigned int)s->loopEnd) {
chan[i].dacPos=s->loopStart;
} else if (chan[i].dacPos>=s->samples) {
chan[i].dacSample=-1;
}
chan[i].dacPeriod-=rate;
}
}
}
*/
// VB part
cycles=0;
while (!writes.empty()) {
QueuedWrite w=writes.front();
vb->Write(cycles,w.addr,w.val);
regPool[w.addr]=w.val;
//cycles+=2;
writes.pop();
}
vb->EndFrame(16);
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;
tempL+=vb->last_output[i][0];
tempR+=vb->last_output[i][1];
}
if (tempL<-32768) tempL=-32768;
if (tempL>32767) tempL=32767;
if (tempR<-32768) tempR=-32768;
if (tempR>32767) tempR=32767;
bufL[h]=tempL;
bufR[h]=tempR;
}
}
void DivPlatformVB::updateWave(int ch) {
if (ch>=5) return;
if (chan[ch].pcm) {
chan[ch].deferredWaveUpdate=true;
return;
}
for (int i=0; i<32; i++) {
rWrite((ch<<7)+(i<<2),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
static unsigned char noiseFreq[12]={
4,13,15,18,21,23,25,27,29,31,0,2
};
void DivPlatformVB::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_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15);
if (chan[i].furnaceDac && chan[i].pcm) {
// ignore for now
} else {
chWrite(i,0x04,chan[i].outVol<<4);
}
}
if (chan[i].std.duty.had && i>=4) {
chan[i].noise=chan[i].std.duty.val;
chan[i].freqChanged=true;
int noiseSeek=chan[i].note;
if (noiseSeek<0) noiseSeek=0;
chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0);
}
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
int noiseSeek=parent->calcArp(chan[i].note,chan[i].std.arp.val);
chan[i].baseFreq=NOTE_PERIODIC(noiseSeek);
if (noiseSeek<0) noiseSeek=0;
chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0);
}
chan[i].freqChanged=true;
}
if (chan[i].std.wave.had && !chan[i].pcm) {
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave.val;
chan[i].ws.changeWave1(chan[i].wave);
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
if (chan[i].std.panL.had) {
chan[i].pan&=0x0f;
chan[i].pan|=(chan[i].std.panL.val&15)<<4;
}
if (chan[i].std.panR.had) {
chan[i].pan&=0xf0;
chan[i].pan|=chan[i].std.panR.val&15;
}
if (chan[i].std.panL.had || chan[i].std.panR.had) {
chWrite(i,0x01,isMuted[i]?0:chan[i].pan);
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-32768,32767);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
if (chan[i].furnaceDac && chan[i].pcm) {
if (chan[i].active && chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
chan[i].dacPos=0;
chan[i].dacPeriod=0;
//chWrite(i,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[i].vol));
addWrite(0xffff0000+(i<<8),chan[i].dacSample);
chan[i].keyOn=true;
}
}
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) || chan[i].deferredWaveUpdate) {
updateWave(i);
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].furnaceDac && chan[i].pcm) {
double off=1.0;
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].dacSample);
if (s->centerRate<1) {
off=1.0;
} else {
off=8363.0/(double)s->centerRate;
}
}
chan[i].dacRate=((double)chipClock/2)/MAX(1,off*chan[i].freq);
if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dacRate);
}
if (chan[i].freq<1) chan[i].freq=1;
if (chan[i].freq>2047) chan[i].freq=2047;
chan[i].freq=2048-chan[i].freq;
chWrite(i,0x02,chan[i].freq&0xff);
chWrite(i,0x03,chan[i].freq>>8);
if (chan[i].keyOn) {
}
if (chan[i].keyOff) {
chWrite(i,0x04,0);
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
}
}
int DivPlatformVB::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE);
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:31;
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
chan[c.chan].pcm=true;
} else if (chan[c.chan].furnaceDac) {
chan[c.chan].pcm=false;
}
if (chan[c.chan].pcm) {
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
chan[c.chan].furnaceDac=true;
if (skipRegisterWrites) break;
chan[c.chan].dacSample=ins->amiga.getSample(c.value);
if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
if (dumpWrites) {
//chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol));
addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample);
}
}
chan[c.chan].dacPos=0;
chan[c.chan].dacPeriod=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
//chan[c.chan].keyOn=true;
} else {
chan[c.chan].furnaceDac=false;
if (skipRegisterWrites) break;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
}
chan[c.chan].dacSample=12*sampleBank+chan[c.chan].note%12;
if (chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample);
}
chan[c.chan].dacPos=0;
chan[c.chan].dacPeriod=0;
chan[c.chan].dacRate=parent->getSample(chan[c.chan].dacSample)->rate;
if (dumpWrites) {
//chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol));
addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dacRate);
}
}
break;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (chan[c.chan].wave<0) {
chan[c.chan].wave=0;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
chan[c.chan].ws.init(ins,32,63,chan[c.chan].insChanged);
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
chan[c.chan].pcm=false;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
chan[c.chan].insChanged=true;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
if (chan[c.chan].active && !chan[c.chan].pcm) {
chWrite(c.chan,0x04,chan[c.chan].outVol<<4);
}
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.vol.has) {
return chan[c.chan].vol;
}
return chan[c.chan].outVol;
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_WAVE:
chan[c.chan].wave=c.value;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
chan[c.chan].keyOn=true;
break;
case DIV_CMD_PCE_LFO_MODE:
if (c.value==0) {
lfoMode=0;
} else {
lfoMode=c.value;
}
rWrite(0x08,lfoSpeed);
rWrite(0x09,lfoMode);
break;
case DIV_CMD_PCE_LFO_SPEED:
lfoSpeed=255-c.value;
rWrite(0x08,lfoSpeed);
rWrite(0x09,lfoMode);
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_PERIODIC(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_STD_NOISE_MODE:
chan[c.chan].noise=c.value;
chWrite(c.chan,0x07,chan[c.chan].noise?(0x80|chan[c.chan].note):0);
break;
case DIV_CMD_SAMPLE_MODE:
chan[c.chan].pcm=c.value;
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_PANNING: {
chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4);
chWrite(c.chan,0x01,isMuted[c.chan]?0:chan[c.chan].pan);
break;
}
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE));
}
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 15;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformVB::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
chWrite(ch,0x01,isMuted[ch]?0:chan[ch].pan);
if (!isMuted[ch] && (chan[ch].pcm && chan[ch].dacSample!=-1)) {
//chWrite(ch,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[ch].outVol));
//chWrite(ch,0x06,chan[ch].dacOut&0x1f);
}
}
void DivPlatformVB::forceIns() {
for (int i=0; i<6; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
updateWave(i);
chWrite(i,0x01,isMuted[i]?0:chan[i].pan);
}
}
void* DivPlatformVB::getChanState(int ch) {
return &chan[ch];
}
DivMacroInt* DivPlatformVB::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivDispatchOscBuffer* DivPlatformVB::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformVB::getRegisterPool() {
return regPool;
}
int DivPlatformVB::getRegisterPoolSize() {
return 0x600;
}
int DivPlatformVB::getRegisterPoolDepth() {
return 8;
}
void DivPlatformVB::reset() {
while (!writes.empty()) writes.pop();
memset(regPool,0,0x600);
for (int i=0; i<6; i++) {
chan[i]=DivPlatformVB::Channel();
chan[i].std.setEngine(parent);
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,32,63,false);
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
vb->Power();
lastPan=0xff;
tempL=0;
tempR=0;
cycles=0;
curChan=-1;
sampleBank=0;
lfoMode=0;
lfoSpeed=255;
// set per-channel initial values
for (int i=0; i<6; i++) {
chWrite(i,0x01,isMuted[i]?0:chan[i].pan);
chWrite(i,0x05,0x00);
chWrite(i,0x00,0x80);
chWrite(i,0x06,i);
}
delay=500;
}
bool DivPlatformVB::isStereo() {
return true;
}
bool DivPlatformVB::keyOffAffectsArp(int ch) {
return true;
}
float DivPlatformVB::getPostAmp() {
return 6.0f;
}
void DivPlatformVB::notifyWaveChange(int wave) {
for (int i=0; i<6; i++) {
if (chan[i].wave==wave) {
chan[i].ws.changeWave1(wave);
updateWave(i);
}
}
}
void DivPlatformVB::notifyInsDeletion(void* ins) {
for (int i=0; i<6; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformVB::setFlags(const DivConfig& flags) {
chipClock=5000000.0;
antiClickEnabled=!flags.getBool("noAntiClick",false);
rate=chipClock/16;
for (int i=0; i<6; i++) {
oscBuf[i]->rate=rate;
}
if (vb!=NULL) {
delete vb;
vb=NULL;
}
vb=new VSU;
}
void DivPlatformVB::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformVB::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
int DivPlatformVB::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<6; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
vb=NULL;
setFlags(flags);
reset();
return 6;
}
void DivPlatformVB::quit() {
for (int i=0; i<6; i++) {
delete oscBuf[i];
}
if (vb!=NULL) {
delete vb;
vb=NULL;
}
}
DivPlatformVB::~DivPlatformVB() {
}

123
src/engine/platform/vb.h Normal file
View File

@ -0,0 +1,123 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 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 _PLATFORM_VB_H
#define _PLATFORM_VB_H
#include "../dispatch.h"
#include <queue>
#include "../macroInt.h"
#include "../waveSynth.h"
#include "sound/vsu.h"
class DivPlatformVB: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2, note, antiClickPeriodCount, antiClickWavePos;
int dacPeriod, dacRate, dacOut;
unsigned int dacPos;
int dacSample, ins;
unsigned char pan;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac, deferredWaveUpdate;
signed char vol, outVol, wave;
int macroVolMul;
DivMacroInt std;
DivWaveSynth ws;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
Channel():
freq(0),
baseFreq(0),
pitch(0),
pitch2(0),
note(0),
antiClickPeriodCount(0),
antiClickWavePos(0),
dacPeriod(0),
dacRate(0),
dacOut(0),
dacPos(0),
dacSample(-1),
ins(-1),
pan(255),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
inPorta(false),
noise(false),
pcm(false),
furnaceDac(false),
deferredWaveUpdate(false),
vol(31),
outVol(31),
wave(-1),
macroVolMul(31) {}
};
Channel chan[6];
DivDispatchOscBuffer* oscBuf[6];
bool isMuted[6];
bool antiClickEnabled;
struct QueuedWrite {
unsigned short addr;
unsigned char val;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {}
};
std::queue<QueuedWrite> writes;
unsigned char lastPan;
int cycles, curChan, delay;
int tempL;
int tempR;
unsigned char sampleBank, lfoMode, lfoSpeed;
VSU* vb;
unsigned char regPool[0x600];
void updateWave(int ch);
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
int getRegisterPoolDepth();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool isStereo();
bool keyOffAffectsArp(int ch);
float getPostAmp();
void setFlags(const DivConfig& flags);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
~DivPlatformVB();
};
#endif

View File

@ -476,6 +476,7 @@ void DivPlatformX1_010::tick(bool sysTick) {
}
}
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,chan[i].pcm?off:CHIP_FREQBASE);
if (chan[i].fixedFreq) chan[i].freq=chan[i].fixedFreq;
if (chan[i].pcm) {
if (chan[i].freq<1) chan[i].freq=1;
if (chan[i].freq>255) chan[i].freq=255;
@ -554,11 +555,13 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note);
chan[c.chan].fixedFreq=0;
chan[c.chan].freqChanged=true;
}
} else {
chan[c.chan].macroInit(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
// huh?
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
chWrite(c.chan,0,0); // reset
chWrite(c.chan,1,0);
@ -571,7 +574,8 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
} else {
chan[c.chan].macroInit(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
chan[c.chan].sample=12*sampleBank+c.value%12;
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chWrite(c.chan,0,0); // reset
chWrite(c.chan,1,0);
chWrite(c.chan,2,0);
@ -579,7 +583,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
chWrite(c.chan,5,0);
break;
}
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
DivSample* s=parent->getSample(chan[c.chan].sample);
if (isBanked) {
bankSlot[chan[c.chan].bankSlot]=sampleOffX1[chan[c.chan].sample]>>17;
unsigned int bankedOffs=(chan[c.chan].bankSlot<<17)|(sampleOffX1[chan[c.chan].sample]&0x1ffff);
@ -591,12 +595,14 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
int end=(sampleOffX1[chan[c.chan].sample]+s->length8+0xfff)&~0xfff; // padded
chWrite(c.chan,5,(0x100-(end>>12))&0xff);
}
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<4)/(chipClock/512);
// ????
chan[c.chan].fixedFreq=(((unsigned int)s->rate)<<4)/(chipClock/512);
chan[c.chan].freqChanged=true;
}
} else if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note);
chan[c.chan].fixedFreq=0;
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;

View File

@ -68,7 +68,7 @@ class DivPlatformX1_010: public DivDispatch, public vgsound_emu_mem_intf {
slide(0),
slidefrac(0) {}
};
int freq, baseFreq, pitch, pitch2, note;
int freq, baseFreq, fixedFreq, pitch, pitch2, note;
int wave, sample, ins;
unsigned char pan, autoEnvNum, autoEnvDen;
bool active, insChanged, envChanged, freqChanged, keyOn, keyOff, inPorta, furnacePCM, pcm;
@ -95,7 +95,7 @@ class DivPlatformX1_010: public DivDispatch, public vgsound_emu_mem_intf {
pitch2=0;
}
Channel():
freq(0), baseFreq(0), pitch(0), pitch2(0), note(0),
freq(0), baseFreq(0), fixedFreq(0), pitch(0), pitch2(0), note(0),
wave(-1), sample(-1), ins(-1),
pan(255), autoEnvNum(0), autoEnvDen(0),
active(false), insChanged(true), envChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), furnacePCM(false), pcm(false),

View File

@ -461,7 +461,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
}
if (returnAfterPre) return;
} else {
logV("honoring delay at position %d",whatRow);
//logV("honoring delay at position %d",whatRow);
}
if (chan[i].delayLocked) return;
@ -1157,6 +1157,8 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
break;
}
}
// under no circumstances shall the accumulator become this large
if (tempoAccum>1023) tempoAccum=1023;
}
// process stuff
for (int i=0; i<chans; i++) {
@ -1584,7 +1586,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
int attempts=0;
int runLeftG=size<<MASTER_CLOCK_PREC;
while (++attempts<100) {
while (++attempts<(int)size) {
// 0. check if we've halted
if (halted) break;
// 1. check whether we are done with all buffers
@ -1647,7 +1649,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
}
//logD("attempts: %d",attempts);
if (attempts>=100) {
if (attempts>=(int)size) {
logE("hang detected! stopping! at %d seconds %d micro",totalSeconds,totalTicks);
freelance=false;
playing=false;

View File

@ -315,6 +315,7 @@ struct DivSong {
bool newVolumeScaling;
bool volMacroLinger;
bool brokenOutVol;
bool brokenOutVol2;
bool e1e2StopOnSameNote;
bool brokenPortaArp;
bool snNoLowPeriods;
@ -421,6 +422,7 @@ struct DivSong {
newVolumeScaling(true),
volMacroLinger(true),
brokenOutVol(false),
brokenOutVol2(false),
e1e2StopOnSameNote(false),
brokenPortaArp(false),
snNoLowPeriods(false),

View File

@ -1181,12 +1181,14 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_VBOY]=new DivSysDef(
"Virtual Boy", NULL, 0x9c, 0, 6, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Virtual Boy", NULL, 0x9c, 0, 6, false, true, 0x171, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"a console which failed to sell well due to its headache-inducing features.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "NO"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_NOISE},
{DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY}
{DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY},
{},
waveOnlyEffectHandlerMap
);
sysDefs[DIV_SYSTEM_VRC7]=new DivSysDef(
@ -1652,7 +1654,15 @@ void DivEngine::registerSystems() {
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
{DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232}
{DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232},
{},
{},
{
{0x10, {DIV_CMD_WAVE, "10xy: Set group control (x: sustain; y: part toggle bitmask)"}},
{0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Set noise mode"}},
{0x12, {DIV_CMD_FM_AR, "12xx: Set group attack (0 to 5)"}},
{0x13, {DIV_CMD_FM_DR, "13xx: Set group decay (0 to 11)"}}
}
);
sysDefs[DIV_SYSTEM_YM2612_FRAC]=new DivSysDef(

View File

@ -722,6 +722,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(write.addr&0xff);
w->writeC(write.val&0xff);
break;
case DIV_SYSTEM_VBOY:
w->writeC(0xc7);
w->writeS_BE(baseAddr2S|(write.addr>>2));
w->writeC(write.val&0xff);
break;
case DIV_SYSTEM_OPL:
case DIV_SYSTEM_OPL_DRUMS:
w->writeC(0x0b|baseAddr1);
@ -1271,6 +1276,17 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
howManyChips++;
}
break;
case DIV_SYSTEM_VBOY:
if (!hasVSU) {
hasVSU=disCont[i].dispatch->chipClock;
willExport[i]=true;
} else if (!(hasVSU&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasVSU|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_OPL:
case DIV_SYSTEM_OPL_DRUMS:
if (!hasOPL) {

View File

@ -129,11 +129,13 @@ const char* aboutLine[]={
"MAME SAA1099 by Juergen Buchmueller and Manuel Abadia",
"MAME Namco WSG by Nicola Salmoria and Aaron Giles",
"MAME RF5C68 core by Olivier Galibert and Aaron Giles",
"MAME MSM5232 core by Jarek Burczynski and Hiromitsu Shioya",
"MAME MSM6258 core by Barry Rodewald",
"MAME YMZ280B core by Aaron Giles",
"SAASound by Dave Hooper and Simon Owen",
"SameBoy by Lior Halphon",
"Mednafen PCE and WonderSwan audio cores",
"SNES DSP core by Blargg",
"puNES (NES, MMC5 and FDS) by FHorse",
"NSFPlay (NES and FDS) by Brad Smith and Brezza",
"reSID by Dag Lem",

View File

@ -139,6 +139,10 @@ void FurnaceGUI::drawCompatFlags() {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("if enabled, no checks for the presence of a volume macro will be made.\nthis will cause the last macro value to linger unless a value in the volume column is present.");
}
ImGui::Checkbox("Broken output volume - Episode 2 (PLEASE KEEP ME DISABLED)",&e->song.brokenOutVol2);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("these compatibility flags are getting SO damn ridiculous and out of control.\nas you may have guessed, this one exists due to yet ANOTHER DefleMask-specific behavior.\nplease keep this off at all costs, because I will not support it when ROM export comes.\noh, and don't start an argument out of it. Furnace isn't a DefleMask replacement, and no,\nI am not trying to make it look like one with all these flags.\n\noh, and what about the other flags that don't have to do with DefleMask?\nthose are for .mod import, future FamiTracker import and personal taste!\n\nend of rant");
}
ImGui::Checkbox("Treat SN76489 periods under 8 as 1",&e->song.snNoLowPeriods);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("when enabled, any SN period under 8 will be written as 1 instead.\nthis replicates DefleMask behavior, but reduces available period range.");

View File

@ -199,7 +199,7 @@ void FurnaceGUI::drawInsList(bool asChild) {
int curRow=0;
for (int i=-1; i<(int)e->song.ins.size(); i++) {
ImGui::PushID(i);
String name=ICON_FA_CIRCLE_O " - None -";
String name=ICON_FA_CIRCLE_O;
const char* insType="Bug!";
if (i>=0) {
DivInstrument* ins=e->song.ins[i];
@ -208,183 +208,183 @@ void FurnaceGUI::drawInsList(bool asChild) {
switch (ins->type) {
case DIV_INS_FM:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]);
name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i);
break;
case DIV_INS_STD:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_STD]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i);
break;
case DIV_INS_GB:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_GB]);
name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i);
break;
case DIV_INS_C64:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_C64]);
name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_KEYBOARD_O "##_INS%d",i);
break;
case DIV_INS_AMIGA:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AMIGA]);
name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i);
break;
case DIV_INS_PCE:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PCE]);
name=fmt::sprintf(ICON_FA_ID_BADGE " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_ID_BADGE "##_INS%d",i);
break;
case DIV_INS_AY:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i);
break;
case DIV_INS_AY8930:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY8930]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i);
break;
case DIV_INS_TIA:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_TIA]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i);
break;
case DIV_INS_SAA1099:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SAA1099]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i);
break;
case DIV_INS_VIC:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VIC]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i);
break;
case DIV_INS_PET:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PET]);
name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_SQUARE "##_INS%d",i);
break;
case DIV_INS_VRC6:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i);
break;
case DIV_INS_VRC6_SAW:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6_SAW]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i);
break;
case DIV_INS_OPLL:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPLL]);
name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i);
break;
case DIV_INS_OPL:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL]);
name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i);
break;
case DIV_INS_FDS:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FDS]);
name=fmt::sprintf(ICON_FA_FLOPPY_O " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_FLOPPY_O "##_INS%d",i);
break;
case DIV_INS_VBOY:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VBOY]);
name=fmt::sprintf(ICON_FA_BINOCULARS " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_BINOCULARS "##_INS%d",i);
break;
case DIV_INS_N163:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_N163]);
name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_CALCULATOR "##_INS%d",i);
break;
case DIV_INS_SCC:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SCC]);
name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_CALCULATOR "##_INS%d",i);
break;
case DIV_INS_OPZ:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPZ]);
name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i);
break;
case DIV_INS_POKEY:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEY]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i);
break;
case DIV_INS_BEEPER:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_BEEPER]);
name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_SQUARE "##_INS%d",i);
break;
case DIV_INS_SWAN:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SWAN]);
name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i);
break;
case DIV_INS_MIKEY:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i);
break;
case DIV_INS_VERA:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]);
name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_KEYBOARD_O "##_INS%d",i);
break;
case DIV_INS_X1_010:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i);
break;
case DIV_INS_ES5506:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ES5506]);
name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i);
break;
case DIV_INS_MULTIPCM:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MULTIPCM]);
name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i);
break;
case DIV_INS_SNES:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SNES]);
name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i);
break;
case DIV_INS_SU:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SU]);
name=fmt::sprintf(ICON_FA_MICROCHIP " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_MICROCHIP "##_INS%d",i);
break;
case DIV_INS_NAMCO:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_NAMCO]);
name=fmt::sprintf(ICON_FA_PIE_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_PIE_CHART "##_INS%d",i);
break;
case DIV_INS_OPL_DRUMS:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL_DRUMS]);
name=fmt::sprintf(ICON_FA_COFFEE " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_COFFEE "##_INS%d",i);
break;
case DIV_INS_OPM:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPM]);
name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i);
break;
case DIV_INS_NES:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_NES]);
name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i);
break;
case DIV_INS_MSM6258:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM6258]);
name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i);
break;
case DIV_INS_MSM6295:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM6295]);
name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i);
break;
case DIV_INS_ADPCMA:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ADPCMA]);
name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i);
break;
case DIV_INS_ADPCMB:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ADPCMB]);
name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i);
break;
case DIV_INS_SEGAPCM:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SEGAPCM]);
name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i);
break;
case DIV_INS_QSOUND:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_QSOUND]);
name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i);
break;
case DIV_INS_YMZ280B:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_YMZ280B]);
name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i);
break;
case DIV_INS_RF5C68:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_RF5C68]);
name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i);
break;
case DIV_INS_MSM5232:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM5232]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i);
break;
default:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]);
name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d",i,ins->name,i);
name=fmt::sprintf(ICON_FA_QUESTION "##_INS%d",i);
break;
}
} else {
@ -400,7 +400,6 @@ void FurnaceGUI::drawInsList(bool asChild) {
curIns=i;
wavePreviewInit=true;
}
ImGui::PopStyleColor();
if (wantScrollList && curIns==i) ImGui::SetScrollHereY();
if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) {
nextWindow=GUI_WINDOW_PATTERN;
@ -408,7 +407,9 @@ void FurnaceGUI::drawInsList(bool asChild) {
wavePreviewInit=true;
}
if (ImGui::IsItemHovered() && i>=0) {
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
ImGui::SetTooltip("%s",insType);
ImGui::PopStyleColor();
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
insEditOpen=true;
nextWindow=GUI_WINDOW_INS_EDIT;
@ -417,6 +418,7 @@ void FurnaceGUI::drawInsList(bool asChild) {
if (i>=0) {
if (ImGui::BeginPopupContextItem("InsRightMenu")) {
curIns=i;
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
if (ImGui::MenuItem("replace...")) {
doAction((curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN);
}
@ -429,9 +431,21 @@ void FurnaceGUI::drawInsList(bool asChild) {
if (ImGui::MenuItem("delete")) {
doAction(GUI_ACTION_INS_LIST_DELETE);
}
ImGui::PopStyleColor();
ImGui::EndPopup();
}
}
if (i>=0) {
if (i<(int)e->song.ins.size()) {
DivInstrument* ins=e->song.ins[i];
ImGui::SameLine();
ImGui::Text("%.2X: %s",i,ins->name.c_str());
}
} else {
ImGui::SameLine();
ImGui::Text("- None -");
}
ImGui::PopStyleColor();
if (settings.horizontalDataView) {
if (++curRow>=availableRows) curRow=0;
}

View File

@ -30,6 +30,12 @@ void FurnaceGUI::drawDebug() {
static int bpRow;
static int bpTick;
static bool bpOn;
static double ptcClock;
static double ptcDivider;
static int ptcOctave;
static int ptcMode;
static int ptcBlockBits;
if (nextWindow==GUI_WINDOW_DEBUG) {
debugOpen=true;
ImGui::SetNextWindowFocus();
@ -98,7 +104,7 @@ void FurnaceGUI::drawDebug() {
ImGui::Columns();
ImGui::TreePop();
}
if (ImGui::TreeNode("Playback Status")) {
if (ImGui::TreeNode("Channel Status")) {
ImGui::Text("for best results set latency to minimum or use the Frame Advance button.");
ImGui::Columns(e->getTotalChannelCount());
for (int i=0; i<e->getTotalChannelCount(); i++) {
@ -160,6 +166,11 @@ void FurnaceGUI::drawDebug() {
ImGui::Columns();
ImGui::TreePop();
}
if (ImGui::TreeNode("Playback Status")) {
String pdi=e->getPlaybackDebugInfo();
ImGui::TextWrapped("%s",pdi.c_str());
ImGui::TreePop();
}
if (ImGui::TreeNode("Sample Debug")) {
for (int i=0; i<e->song.sampleLen; i++) {
DivSample* sample=e->getSample(i);
@ -296,6 +307,78 @@ void FurnaceGUI::drawDebug() {
}
ImGui::TreePop();
}
if (ImGui::TreeNode("Pitch Table Calculator")) {
ImGui::InputDouble("Clock",&ptcClock);
ImGui::InputDouble("Divider/FreqBase",&ptcDivider);
ImGui::InputInt("Octave",&ptcOctave);
if (ImGui::RadioButton("Frequency",ptcMode==0)) ptcMode=0;
ImGui::SameLine();
if (ImGui::RadioButton("Period",ptcMode==1)) ptcMode=1;
ImGui::SameLine();
if (ImGui::RadioButton("FreqNum/Block",ptcMode==2)) ptcMode=2;
if (ptcMode==2) {
if (ImGui::InputInt("FreqNum Bits",&ptcBlockBits)) {
if (ptcBlockBits<0) ptcBlockBits=0;
if (ptcBlockBits>13) ptcBlockBits=13;
}
}
if (ImGui::BeginTable("PitchTable",7)) {
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
ImGui::TableNextColumn();
ImGui::Text("Note");
ImGui::TableNextColumn();
ImGui::Text("Pitch");
ImGui::TableNextColumn();
ImGui::Text("Base");
ImGui::TableNextColumn();
ImGui::Text("Hex");
ImGui::TableNextColumn();
ImGui::Text("Final");
ImGui::TableNextColumn();
ImGui::Text("Hex");
ImGui::TableNextColumn();
ImGui::Text("Delta");
int lastFinal=0;
for (int i=0; i<12; i++) {
int note=(12*ptcOctave)+i;
int pitch=0;
int base=e->calcBaseFreq(ptcClock,ptcDivider,note,ptcMode==1);
int final=e->calcFreq(base,pitch,ptcMode==1,0,0,ptcClock,ptcDivider,(ptcMode==2)?ptcBlockBits:0);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%d",note);
ImGui::TableNextColumn();
ImGui::Text("%d",pitch);
ImGui::TableNextColumn();
ImGui::Text("%d",base);
ImGui::TableNextColumn();
ImGui::Text("%x",base);
ImGui::TableNextColumn();
ImGui::Text("%d",final);
ImGui::TableNextColumn();
ImGui::Text("%x",final);
ImGui::TableNextColumn();
ImGui::Text("%d",final-lastFinal);
lastFinal=final;
}
ImGui::EndTable();
}
ImGui::TreePop();
}
if (ImGui::TreeNode("Playground")) {
if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0;
if (ImGui::BeginCombo("Chip",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) {

View File

@ -1439,6 +1439,12 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_ORDERS_REMOVE:
prepareUndo(GUI_UNDO_CHANGE_ORDER);
e->deleteOrder();
if (curOrder>=e->curSubSong->ordersLen) {
curOrder=e->curSubSong->ordersLen-1;
oldOrder=curOrder;
oldOrder1=curOrder;
e->setOrder(curOrder);
}
makeUndo(GUI_UNDO_CHANGE_ORDER);
break;
case GUI_ACTION_ORDERS_MOVE_UP:

View File

@ -565,6 +565,8 @@ void FurnaceGUI::doPaste(PasteMode mode, int arg) {
if (settings.cursorPastePos) {
cursor.y=j;
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
selStart=cursor;
selEnd=cursor;
updateScroll(cursor.y);
}

View File

@ -1993,6 +1993,10 @@ void FurnaceGUI::showError(String what) {
displayError=true;
}
String FurnaceGUI::getLastError() {
return lastError;
}
// what monster did I just create here?
#define B30(tt) (macroDragBit30?((((tt)&0xc0000000)==0x40000000 || ((tt)&0xc0000000)==0x80000000)?0x40000000:0):0)
@ -3291,6 +3295,11 @@ bool FurnaceGUI::loop() {
}
if (recentFile.empty()) {
ImGui::Text("nothing here yet");
} else {
ImGui::Separator();
if (ImGui::MenuItem("clear history")) {
showWarning("Are you sure you want to clear the recent file list?",GUI_WARN_CLEAR_HISTORY);
}
}
ImGui::EndMenu();
}
@ -4630,6 +4639,16 @@ bool FurnaceGUI::loop() {
ImGui::CloseCurrentPopup();
}
break;
case GUI_WARN_CLEAR_HISTORY:
if (ImGui::Button("Yes")) {
recentFile.clear();
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("No")) {
ImGui::CloseCurrentPopup();
}
break;
case GUI_WARN_GENERIC:
if (ImGui::Button("OK")) {
ImGui::CloseCurrentPopup();
@ -5002,7 +5021,7 @@ bool FurnaceGUI::init() {
sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0));
if (sdlWin==NULL) {
logE("could not open window! %s",SDL_GetError());
lastError=fmt::sprintf("could not open window! %s",SDL_GetError());
return false;
}
@ -5057,7 +5076,7 @@ bool FurnaceGUI::init() {
sdlRend=SDL_CreateRenderer(sdlWin,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE);
if (sdlRend==NULL) {
logE("could not init renderer! %s",SDL_GetError());
lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError());
return false;
}
@ -5401,6 +5420,7 @@ FurnaceGUI::FurnaceGUI():
curWindow(GUI_WINDOW_NOTHING),
nextWindow(GUI_WINDOW_NOTHING),
curWindowLast(GUI_WINDOW_NOTHING),
lastPatternWidth(0.0f),
nextDesc(NULL),
latchNote(-1),
latchIns(-2),

View File

@ -355,6 +355,7 @@ enum FurnaceGUIWarnings {
GUI_WARN_CLEAR,
GUI_WARN_SUBSONG_DEL,
GUI_WARN_SYSTEM_DEL,
GUI_WARN_CLEAR_HISTORY,
GUI_WARN_GENERIC
};
@ -1208,6 +1209,7 @@ class FurnaceGUI {
int midiOutClock;
int midiOutMode;
int maxRecentFile;
int centerPattern;
unsigned int maxUndoSteps;
String mainFontPath;
String patFontPath;
@ -1333,6 +1335,7 @@ class FurnaceGUI {
midiOutClock(0),
midiOutMode(1),
maxRecentFile(10),
centerPattern(0),
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),
@ -1372,6 +1375,7 @@ class FurnaceGUI {
float peak[2];
float patChanX[DIV_MAX_CHANS+1];
float patChanSlideY[DIV_MAX_CHANS+1];
float lastPatternWidth;
const int* nextDesc;
String nextDescName;
@ -1801,6 +1805,7 @@ class FurnaceGUI {
public:
void showWarning(String what, FurnaceGUIWarnings type);
void showError(String what);
String getLastError();
const char* noteNameNormal(short note, short octave);
const char* noteName(short note, short octave);
bool decodeNote(const char* what, short& note, short& octave);

View File

@ -936,6 +936,7 @@ const int availableSystems[]={
DIV_SYSTEM_QSOUND,
DIV_SYSTEM_X1_010,
DIV_SYSTEM_SWAN,
DIV_SYSTEM_VBOY,
DIV_SYSTEM_VERA,
DIV_SYSTEM_BUBSYS_WSG,
DIV_SYSTEM_N163,
@ -1009,6 +1010,7 @@ const int chipsWave[]={
DIV_SYSTEM_PCE,
DIV_SYSTEM_X1_010,
DIV_SYSTEM_SWAN,
DIV_SYSTEM_VBOY,
DIV_SYSTEM_BUBSYS_WSG,
DIV_SYSTEM_N163,
DIV_SYSTEM_FDS,

View File

@ -203,6 +203,17 @@ enum FMParams {
#define FM_NAME(x) fmParamNames[settings.fmNames][x]
#define FM_SHORT_NAME(x) fmParamShortNames[settings.fmNames][x]
const char* macroTypeLabels[4]={
ICON_FA_BAR_CHART "##IMacroType",
ICON_FA_AREA_CHART "##IMacroType",
ICON_FA_LINE_CHART "##IMacroType",
ICON_FA_SIGN_OUT "##IMacroType"
};
const char* macroLFOShapes[4]={
"Triangle", "Saw", "Square", "How did you even"
};
const char* fmOperatorBits[5]={
"op1", "op2", "op3", "op4", NULL
};
@ -346,7 +357,7 @@ String macroHoverNote(int id, float val, void* u) {
}
String macroHover(int id, float val, void* u) {
return fmt::sprintf("%d: %d",id,val);
return fmt::sprintf("%d: %d",id,(int)val);
}
String macroHoverLoop(int id, float val, void* u) {
@ -360,6 +371,22 @@ String macroHoverBit30(int id, float val, void* u) {
return "Relative";
}
String macroHoverGain(int id, float val, void* u) {
if (val>=224.0f) {
return fmt::sprintf("%d: +%d (exponential)",id,(int)(val-224));
}
if (val>=192.0f) {
return fmt::sprintf("%d: +%d (linear)",id,(int)(val-192));
}
if (val>=160.0f) {
return fmt::sprintf("%d: -%d (exponential)",id,(int)(val-160));
}
if (val>=128.0f) {
return fmt::sprintf("%d: -%d (linear)",id,(int)(val-128));
}
return fmt::sprintf("%d: %d (direct)",id,(int)val);
}
String macroHoverES5506FilterMode(int id, float val, void* u) {
String mode="???";
switch (((int)val)&3) {
@ -1314,22 +1341,67 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
ImGui::TableNextColumn();
ImGui::Text("%s",i.displayName);
ImGui::SameLine();
if (ImGui::SmallButton(i.macro->open?(ICON_FA_CHEVRON_UP "##IMacroOpen"):(ICON_FA_CHEVRON_DOWN "##IMacroOpen"))) {
i.macro->open=!i.macro->open;
if (ImGui::SmallButton((i.macro->open&1)?(ICON_FA_CHEVRON_UP "##IMacroOpen"):(ICON_FA_CHEVRON_DOWN "##IMacroOpen"))) {
i.macro->open^=1;
}
if (i.macro->open) {
ImGui::SetNextItemWidth(lenAvail);
int macroLen=i.macro->len;
if (ImGui::InputScalar("##IMacroLen",ImGuiDataType_U8,&macroLen,&_ONE,&_THREE)) { MARK_MODIFIED
if (macroLen<0) macroLen=0;
if (macroLen>255) macroLen=255;
i.macro->len=macroLen;
if (i.macro->open&1) {
if ((i.macro->open&6)==0) {
ImGui::SetNextItemWidth(lenAvail);
int macroLen=i.macro->len;
if (ImGui::InputScalar("##IMacroLen",ImGuiDataType_U8,&macroLen,&_ONE,&_THREE)) { MARK_MODIFIED
if (macroLen<0) macroLen=0;
if (macroLen>255) macroLen=255;
i.macro->len=macroLen;
}
}
if (ImGui::Button(ICON_FA_BAR_CHART "##IMacroType")) {
if (ImGui::Button(macroTypeLabels[(i.macro->open>>1)&3])) {
unsigned char prevOpen=i.macro->open;
i.macro->open+=2;
if (i.macro->open>=6) {
i.macro->open-=6;
}
// check whether macro type is now ADSR/LFO or sequence
if (((prevOpen&6)?1:0)!=((i.macro->open&6)?1:0)) {
// swap memory
// this way the macro isn't corrupted if the user decides to go
// back to sequence mode
i.macro->len^=i.macro->lenMemory;
i.macro->lenMemory^=i.macro->len;
i.macro->len^=i.macro->lenMemory;
for (int j=0; j<16; j++) {
i.macro->val[j]^=i.macro->typeMemory[j];
i.macro->typeMemory[j]^=i.macro->val[j];
i.macro->val[j]^=i.macro->typeMemory[j];
}
// if ADSR/LFO, populate min/max
if (i.macro->open&6) {
i.macro->val[0]=i.min;
i.macro->val[1]=i.max;
}
}
PARAMETER;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Coming soon!");
switch (i.macro->open&6) {
case 0:
ImGui::SetTooltip("Macro type: Sequence");
break;
case 2:
ImGui::SetTooltip("Macro type: ADSR");
break;
case 4:
ImGui::SetTooltip("Macro type: LFO");
break;
default:
ImGui::SetTooltip("Macro type: What's going on here?");
break;
}
}
if (i.macro->open&6) {
i.macro->len=16;
}
ImGui::SameLine();
ImGui::Button(ICON_FA_ELLIPSIS_H "##IMacroSet");
@ -1360,204 +1432,371 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
// macro area
ImGui::TableNextColumn();
for (int j=0; j<256; j++) {
bit30Indicator[j]=0;
if (j+macroDragScroll>=i.macro->len) {
asFloat[j]=0;
asInt[j]=0;
} else {
asFloat[j]=deBit30(i.macro->val[j+macroDragScroll]);
asInt[j]=deBit30(i.macro->val[j+macroDragScroll])+i.bitOffset;
if (i.bit30) bit30Indicator[j]=enBit30(i.macro->val[j+macroDragScroll]);
if ((i.macro->open&6)==0) {
for (int j=0; j<256; j++) {
bit30Indicator[j]=0;
if (j+macroDragScroll>=i.macro->len) {
asFloat[j]=0;
asInt[j]=0;
} else {
asFloat[j]=deBit30(i.macro->val[j+macroDragScroll]);
asInt[j]=deBit30(i.macro->val[j+macroDragScroll])+i.bitOffset;
if (i.bit30) bit30Indicator[j]=enBit30(i.macro->val[j+macroDragScroll]);
}
if (j+macroDragScroll>=i.macro->len || (j+macroDragScroll>i.macro->rel && i.macro->loop<i.macro->rel)) {
loopIndicator[j]=0;
} else {
loopIndicator[j]=((i.macro->loop!=255 && (j+macroDragScroll)>=i.macro->loop))|((i.macro->rel!=255 && (j+macroDragScroll)==i.macro->rel)<<1);
}
}
if (j+macroDragScroll>=i.macro->len || (j+macroDragScroll>i.macro->rel && i.macro->loop<i.macro->rel)) {
loopIndicator[j]=0;
} else {
loopIndicator[j]=((i.macro->loop!=255 && (j+macroDragScroll)>=i.macro->loop))|((i.macro->rel!=255 && (j+macroDragScroll)==i.macro->rel)<<1);
}
}
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f));
if (i.macro->vZoom<1) {
if (i.macro->name=="arp") {
i.macro->vZoom=24;
i.macro->vScroll=120-12;
} else if (i.macro->name=="pitch") {
i.macro->vZoom=128;
i.macro->vScroll=2048-64;
} else {
if (i.macro->vZoom<1) {
if (i.macro->name=="arp") {
i.macro->vZoom=24;
i.macro->vScroll=120-12;
} else if (i.macro->name=="pitch") {
i.macro->vZoom=128;
i.macro->vScroll=2048-64;
} else {
i.macro->vZoom=i.max-i.min;
i.macro->vScroll=0;
}
}
if (i.macro->vZoom>(i.max-i.min)) {
i.macro->vZoom=i.max-i.min;
i.macro->vScroll=0;
}
}
if (i.macro->vZoom>(i.max-i.min)) {
i.macro->vZoom=i.max-i.min;
}
memset(doHighlight,0,256*sizeof(bool));
if (e->isRunning()) for (int j=0; j<e->getTotalChannelCount(); j++) {
DivChannelState* chanState=e->getChanState(j);
if (chanState==NULL) continue;
memset(doHighlight,0,256*sizeof(bool));
if (e->isRunning()) for (int j=0; j<e->getTotalChannelCount(); j++) {
DivChannelState* chanState=e->getChanState(j);
if (chanState==NULL) continue;
if (chanState->keyOff) continue;
if (chanState->lastIns!=curIns) continue;
if (chanState->keyOff) continue;
if (chanState->lastIns!=curIns) continue;
DivMacroInt* macroInt=e->getMacroInt(j);
if (macroInt==NULL) continue;
DivMacroInt* macroInt=e->getMacroInt(j);
if (macroInt==NULL) continue;
DivMacroStruct* macroStruct=macroInt->structByName(i.macro->name);
if (macroStruct==NULL) continue;
DivMacroStruct* macroStruct=macroInt->structByName(i.macro->name);
if (macroStruct==NULL) continue;
if (macroStruct->lastPos>i.macro->len) continue;
if (macroStruct->lastPos<macroDragScroll) continue;
if (macroStruct->lastPos>255) continue;
if (!macroStruct->actualHad) continue;
if (macroStruct->lastPos>i.macro->len) continue;
if (macroStruct->lastPos<macroDragScroll) continue;
if (macroStruct->lastPos>255) continue;
if (!macroStruct->actualHad) continue;
doHighlight[macroStruct->lastPos-macroDragScroll]=true;
}
doHighlight[macroStruct->lastPos-macroDragScroll]=true;
}
if (i.isBitfield) {
PlotBitfield("##IMacro",asInt,totalFit,0,i.bitfieldBits,i.max,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),doHighlight);
} else {
PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.hoverFuncUser,i.blockMode,i.macro->open?genericGuide:NULL,doHighlight);
}
if (i.macro->open && (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemClicked(ImGuiMouseButton_Right))) {
macroDragStart=ImGui::GetItemRectMin();
macroDragAreaSize=ImVec2(availableWidth,i.height*dpiScale);
if (i.isBitfield) {
macroDragMin=i.min;
macroDragMax=i.max;
PlotBitfield("##IMacro",asInt,totalFit,0,i.bitfieldBits,i.max,ImVec2(availableWidth,(i.macro->open&1)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),doHighlight);
} else {
macroDragMin=i.min+i.macro->vScroll;
macroDragMax=i.min+i.macro->vScroll+i.macro->vZoom;
PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open&1)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.hoverFuncUser,i.blockMode,(i.macro->open&1)?genericGuide:NULL,doHighlight);
}
macroDragBitOff=i.bitOffset;
macroDragBitMode=i.isBitfield;
macroDragInitialValueSet=false;
macroDragInitialValue=false;
macroDragLen=totalFit;
macroDragActive=true;
macroDragBit30=i.bit30;
macroDragSettingBit30=false;
macroDragTarget=i.macro->val;
macroDragChar=false;
macroDragLineMode=(i.isBitfield)?false:ImGui::IsItemClicked(ImGuiMouseButton_Right);
macroDragLineInitial=ImVec2(0,0);
lastMacroDesc=i;
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
}
if (i.macro->open) {
if (ImGui::IsItemHovered()) {
if (ctrlWheeling) {
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
i.macro->vZoom+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vZoom<1) i.macro->vZoom=1;
if (i.macro->vZoom>(i.max-i.min)) i.macro->vZoom=i.max-i.min;
if ((i.macro->vScroll+i.macro->vZoom)>(i.max-i.min)) {
i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
} else {
macroPointSize+=wheelY;
if (macroPointSize<1) macroPointSize=1;
if (macroPointSize>256) macroPointSize=256;
}
} else if ((ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) && wheelY!=0) {
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
}
// slider
if (!i.isBitfield) {
if (settings.oldMacroVSlider) {
ImGui::SameLine(0.0f);
if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) {
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
if (ImGui::IsItemHovered() && ctrlWheeling) {
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
if ((i.macro->open&1) && (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemClicked(ImGuiMouseButton_Right))) {
macroDragStart=ImGui::GetItemRectMin();
macroDragAreaSize=ImVec2(availableWidth,i.height*dpiScale);
if (i.isBitfield) {
macroDragMin=i.min;
macroDragMax=i.max;
} else {
ImS64 scrollV=(i.max-i.min-i.macro->vZoom)-i.macro->vScroll;
ImS64 availV=i.macro->vZoom;
ImS64 contentsV=(i.max-i.min);
ImGui::SameLine(0.0f);
ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x);
ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos());
scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize;
scrollbarPos.Max.y+=i.height*dpiScale;
ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale));
if (ImGui::IsItemHovered() && ctrlWheeling) {
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
ImGuiID scrollbarID=ImGui::GetID("IMacroVScroll");
ImGui::KeepAliveID(scrollbarID);
if (ImGui::ScrollbarEx(scrollbarPos,scrollbarID,ImGuiAxis_Y,&scrollV,availV,contentsV,0)) {
i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV;
}
macroDragMin=i.min+i.macro->vScroll;
macroDragMax=i.min+i.macro->vScroll+i.macro->vZoom;
}
}
// bit 30 area
if (i.bit30) {
PlotCustom("##IMacroBit30",bit30Indicator,totalFit,macroDragScroll,NULL,0,1,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,&macroHoverBit30);
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
macroDragStart=ImGui::GetItemRectMin();
macroDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale);
macroDragInitialValueSet=false;
macroDragInitialValue=false;
macroDragLen=totalFit;
macroDragActive=true;
macroDragBit30=i.bit30;
macroDragSettingBit30=true;
macroDragTarget=i.macro->val;
macroDragChar=false;
macroDragLineMode=false;
macroDragLineInitial=ImVec2(0,0);
lastMacroDesc=i;
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
}
}
// loop area
PlotCustom("##IMacroLoop",loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,&macroHoverLoop);
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
macroLoopDragStart=ImGui::GetItemRectMin();
macroLoopDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale);
macroLoopDragLen=totalFit;
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
macroLoopDragTarget=&i.macro->rel;
} else {
macroLoopDragTarget=&i.macro->loop;
}
macroLoopDragActive=true;
macroDragBitOff=i.bitOffset;
macroDragBitMode=i.isBitfield;
macroDragInitialValueSet=false;
macroDragInitialValue=false;
macroDragLen=totalFit;
macroDragActive=true;
macroDragBit30=i.bit30;
macroDragSettingBit30=false;
macroDragTarget=i.macro->val;
macroDragChar=false;
macroDragLineMode=(i.isBitfield)?false:ImGui::IsItemClicked(ImGuiMouseButton_Right);
macroDragLineInitial=ImVec2(0,0);
lastMacroDesc=i;
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
i.macro->rel=255;
} else {
i.macro->loop=255;
if ((i.macro->open&1)) {
if (ImGui::IsItemHovered()) {
if (ctrlWheeling) {
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
i.macro->vZoom+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vZoom<1) i.macro->vZoom=1;
if (i.macro->vZoom>(i.max-i.min)) i.macro->vZoom=i.max-i.min;
if ((i.macro->vScroll+i.macro->vZoom)>(i.max-i.min)) {
i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
} else {
macroPointSize+=wheelY;
if (macroPointSize<1) macroPointSize=1;
if (macroPointSize>256) macroPointSize=256;
}
} else if ((ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) && wheelY!=0) {
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
}
// slider
if (!i.isBitfield) {
if (settings.oldMacroVSlider) {
ImGui::SameLine(0.0f);
if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) {
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
if (ImGui::IsItemHovered() && ctrlWheeling) {
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
} else {
ImS64 scrollV=(i.max-i.min-i.macro->vZoom)-i.macro->vScroll;
ImS64 availV=i.macro->vZoom;
ImS64 contentsV=(i.max-i.min);
ImGui::SameLine(0.0f);
ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x);
ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos());
scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize;
scrollbarPos.Max.y+=i.height*dpiScale;
ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale));
if (ImGui::IsItemHovered() && ctrlWheeling) {
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
ImGuiID scrollbarID=ImGui::GetID("IMacroVScroll");
ImGui::KeepAliveID(scrollbarID);
if (ImGui::ScrollbarEx(scrollbarPos,scrollbarID,ImGuiAxis_Y,&scrollV,availV,contentsV,0)) {
i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV;
}
}
}
// bit 30 area
if (i.bit30) {
PlotCustom("##IMacroBit30",bit30Indicator,totalFit,macroDragScroll,NULL,0,1,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,&macroHoverBit30);
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
macroDragStart=ImGui::GetItemRectMin();
macroDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale);
macroDragInitialValueSet=false;
macroDragInitialValue=false;
macroDragLen=totalFit;
macroDragActive=true;
macroDragBit30=i.bit30;
macroDragSettingBit30=true;
macroDragTarget=i.macro->val;
macroDragChar=false;
macroDragLineMode=false;
macroDragLineInitial=ImVec2(0,0);
lastMacroDesc=i;
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
}
}
// loop area
PlotCustom("##IMacroLoop",loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,&macroHoverLoop);
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
macroLoopDragStart=ImGui::GetItemRectMin();
macroLoopDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale);
macroLoopDragLen=totalFit;
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
macroLoopDragTarget=&i.macro->rel;
} else {
macroLoopDragTarget=&i.macro->loop;
}
macroLoopDragActive=true;
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
i.macro->rel=255;
} else {
i.macro->loop=255;
}
}
ImGui::SetNextItemWidth(availableWidth);
String& mmlStr=mmlString[index];
if (ImGui::InputText("##IMacroMML",&mmlStr)) {
decodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.min,(i.isBitfield)?((1<<(i.isBitfield?i.max:0))-1):i.max,i.macro->rel,i.bit30);
}
if (!ImGui::IsItemActive()) {
encodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.macro->rel,false,i.bit30);
}
}
ImGui::SetNextItemWidth(availableWidth);
String& mmlStr=mmlString[index];
if (ImGui::InputText("##IMacroMML",&mmlStr)) {
decodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.min,(i.isBitfield)?((1<<(i.isBitfield?i.max:0))-1):i.max,i.macro->rel,i.bit30);
ImGui::PopStyleVar();
} else {
if (i.macro->open&2) {
if (ImGui::BeginTable("MacroADSR",4)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.3);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.3);
//ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.4);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Bottom");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##MABottom",&i.macro->val[0],1,16)) { PARAMETER
if (i.macro->val[0]<i.min) i.macro->val[0]=i.min;
if (i.macro->val[0]>i.max) i.macro->val[0]=i.max;
}
ImGui::TableNextColumn();
ImGui::Text("Top");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##MATop",&i.macro->val[1],1,16)) { PARAMETER
if (i.macro->val[1]<i.min) i.macro->val[1]=i.min;
if (i.macro->val[1]>i.max) i.macro->val[1]=i.max;
}
/*ImGui::TableNextColumn();
ImGui::Text("the envelope goes here");*/
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Attack");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderInt("##MAAR",&i.macro->val[2],0,255)) { PARAMETER
if (i.macro->val[2]<0) i.macro->val[2]=0;
if (i.macro->val[2]>255) i.macro->val[2]=255;
}
ImGui::TableNextColumn();
ImGui::Text("Sustain");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderInt("##MASL",&i.macro->val[5],0,255)) { PARAMETER
if (i.macro->val[5]<0) i.macro->val[5]=0;
if (i.macro->val[5]>255) i.macro->val[5]=255;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Hold");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderInt("##MAHT",&i.macro->val[3],0,255)) { PARAMETER
if (i.macro->val[3]<0) i.macro->val[3]=0;
if (i.macro->val[3]>255) i.macro->val[3]=255;
}
ImGui::TableNextColumn();
ImGui::Text("SusTime");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderInt("##MAST",&i.macro->val[6],0,255)) { PARAMETER
if (i.macro->val[6]<0) i.macro->val[6]=0;
if (i.macro->val[6]>255) i.macro->val[6]=255;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Decay");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderInt("##MADR",&i.macro->val[4],0,255)) { PARAMETER
if (i.macro->val[4]<0) i.macro->val[4]=0;
if (i.macro->val[4]>255) i.macro->val[4]=255;
}
ImGui::TableNextColumn();
ImGui::Text("SusDecay");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderInt("##MASR",&i.macro->val[7],0,255)) { PARAMETER
if (i.macro->val[7]<0) i.macro->val[7]=0;
if (i.macro->val[7]>255) i.macro->val[7]=255;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::Text("Release");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderInt("##MARR",&i.macro->val[8],0,255)) { PARAMETER
if (i.macro->val[8]<0) i.macro->val[8]=0;
if (i.macro->val[8]>255) i.macro->val[8]=255;
}
ImGui::EndTable();
}
}
if (!ImGui::IsItemActive()) {
encodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.macro->rel,false,i.bit30);
if (i.macro->open&4) {
if (ImGui::BeginTable("MacroLFO",4)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.3);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.3);
//ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.4);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Bottom");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##MABottom",&i.macro->val[0],1,16)) { PARAMETER
if (i.macro->val[0]<i.min) i.macro->val[0]=i.min;
if (i.macro->val[0]>i.max) i.macro->val[0]=i.max;
}
ImGui::TableNextColumn();
ImGui::Text("Top");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##MATop",&i.macro->val[1],1,16)) { PARAMETER
if (i.macro->val[1]<i.min) i.macro->val[1]=i.min;
if (i.macro->val[1]>i.max) i.macro->val[1]=i.max;
}
/*ImGui::TableNextColumn();
ImGui::Text("the envelope goes here");*/
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Speed");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderInt("##MLSpeed",&i.macro->val[11],0,255)) { PARAMETER
if (i.macro->val[11]<0) i.macro->val[11]=0;
if (i.macro->val[11]>255) i.macro->val[11]=255;
}
ImGui::TableNextColumn();
ImGui::Text("Phase");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderInt("##MLPhase",&i.macro->val[13],0,1023)) { PARAMETER
if (i.macro->val[13]<0) i.macro->val[13]=0;
if (i.macro->val[13]>1023) i.macro->val[13]=1023;
}
ImGui::TableNextColumn();
ImGui::Text("Shape");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderInt("##MLShape",&i.macro->val[12],0,2,macroLFOShapes[i.macro->val[12]&3])) { PARAMETER
if (i.macro->val[12]<0) i.macro->val[12]=0;
if (i.macro->val[12]>2) i.macro->val[12]=2;
}
ImGui::EndTable();
}
}
}
ImGui::PopStyleVar();
ImGui::PopID();
index++;
}
@ -1677,7 +1916,47 @@ void FurnaceGUI::drawInsEdit() {
}
if (ImGui::Begin("Instrument Editor",&insEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) {
if (curIns<0 || curIns>=(int)e->song.ins.size()) {
ImGui::SetCursorPosY(ImGui::GetCursorPosY()+(ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()*2.0f)*0.5f);
CENTER_TEXT("no instrument selected");
ImGui::Text("no instrument selected");
if (ImGui::BeginTable("noAssetCenter",3)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.5f);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5f);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
if (e->song.ins.size()>0) {
if (ImGui::BeginCombo("##InsSelect","select one...")) {
String name;
for (size_t i=0; i<e->song.ins.size(); i++) {
name=fmt::sprintf("%.2X: %s##_INSS%d",i,e->song.ins[i]->name,i);
if (ImGui::Selectable(name.c_str(),curIns==(int)i)) {
curIns=i;
wavePreviewInit=true;
}
}
ImGui::EndCombo();
}
ImGui::SameLine();
ImGui::TextUnformatted("or");
ImGui::SameLine();
}
if (ImGui::Button("Open")) {
doAction(GUI_ACTION_INS_LIST_OPEN);
}
ImGui::SameLine();
ImGui::TextUnformatted("or");
ImGui::SameLine();
if (ImGui::Button("Create New")) {
doAction(GUI_ACTION_INS_LIST_ADD);
}
ImGui::TableNextColumn();
ImGui::EndTable();
}
} else {
DivInstrument* ins=e->song.ins[curIns];
if (settings.insEditColorize) {
@ -4342,6 +4621,7 @@ void FurnaceGUI::drawInsEdit() {
ins->type==DIV_INS_FDS ||
(ins->type==DIV_INS_SWAN && !ins->amiga.useSample) ||
(ins->type==DIV_INS_PCE && !ins->amiga.useSample) ||
(ins->type==DIV_INS_VBOY) ||
ins->type==DIV_INS_SCC ||
ins->type==DIV_INS_SNES ||
ins->type==DIV_INS_NAMCO) {
@ -4593,7 +4873,7 @@ void FurnaceGUI::drawInsEdit() {
}
if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC ||
ins->type==DIV_INS_PET || ins->type==DIV_INS_VIC || ins->type==DIV_INS_SEGAPCM ||
ins->type==DIV_INS_FM) {
ins->type==DIV_INS_FM || ins->type==DIV_INS_VBOY) {
dutyMax=0;
}
if (ins->type==DIV_INS_PCE || ins->type==DIV_INS_NAMCO) {
@ -4758,7 +5038,7 @@ void FurnaceGUI::drawInsEdit() {
}
if (ins->type==DIV_INS_SNES) {
ex1Max=5;
ex2Max=5;
ex2Max=255;
}
if (ins->type==DIV_INS_MSM5232) {
ex1Max=5;
@ -4783,7 +5063,9 @@ void FurnaceGUI::drawInsEdit() {
panMax=1;
panSingle=true;
}
if (ins->type==DIV_INS_X1_010 || ins->type==DIV_INS_PCE || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_SAA1099 || ins->type==DIV_INS_NAMCO || ins->type==DIV_INS_RF5C68) {
if (ins->type==DIV_INS_X1_010 || ins->type==DIV_INS_PCE || ins->type==DIV_INS_MIKEY ||
ins->type==DIV_INS_SAA1099 || ins->type==DIV_INS_NAMCO || ins->type==DIV_INS_RF5C68 ||
ins->type==DIV_INS_VBOY) {
panMax=15;
}
if (ins->type==DIV_INS_SEGAPCM) {
@ -4934,7 +5216,7 @@ void FurnaceGUI::drawInsEdit() {
} else if (ins->type==DIV_INS_QSOUND) {
macroList.push_back(FurnaceGUIMacroDesc("Echo Length",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} else if (ins->type==DIV_INS_SNES) {
macroList.push_back(FurnaceGUIMacroDesc("Gain Mode",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_VOLUME],false,NULL,NULL,false,snesGainModes));
macroList.push_back(FurnaceGUIMacroDesc("Gain",&ins->std.ex2Macro,0,ex2Max,256,uiColors[GUI_COLOR_MACRO_VOLUME],false,NULL,macroHoverGain,false));
} else if (ins->type==DIV_INS_MSM5232) {
macroList.push_back(FurnaceGUIMacroDesc("Group Decay",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} else {
@ -4976,9 +5258,6 @@ void FurnaceGUI::drawInsEdit() {
macroList.push_back(FurnaceGUIMacroDesc("Envelope mode",&ins->std.ex8Macro,0,2,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,es5506EnvelopeModes));
macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.algMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,es5506ControlModes));
}
if (ins->type==DIV_INS_SNES) {
macroList.push_back(FurnaceGUIMacroDesc("Gain Rate",&ins->std.ex3Macro,0,127,160,uiColors[GUI_COLOR_MACRO_VOLUME]));
}
if (ins->type==DIV_INS_MSM5232) {
macroList.push_back(FurnaceGUIMacroDesc("Noise",&ins->std.ex3Macro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
}

View File

@ -403,6 +403,12 @@ void FurnaceGUI::drawPattern() {
ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_SELECTION_HOVER]);
ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_SELECTION_ACTIVE]);
if (settings.centerPattern) {
float centerOff=(ImGui::GetContentRegionAvail().x-lastPatternWidth)*0.5;
if (centerOff>0.0f) {
ImGui::SetCursorPosX(ImGui::GetCursorPosX()+centerOff);
}
}
if (ImGui::BeginTable("PatternView",displayChans+2,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY|ImGuiTableFlags_NoPadInnerX|ImGuiTableFlags_NoBordersInFrozenArea)) {
ImGui::TableSetupColumn("pos",ImGuiTableColumnFlags_WidthFixed);
char chanID[2048];
@ -427,6 +433,7 @@ void FurnaceGUI::drawPattern() {
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
float lpwStart=ImGui::GetCursorPosX();
if (ImGui::Selectable((extraChannelButtons==2)?" --##ExtraChannelButtons":" ++##ExtraChannelButtons",false,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) {
if (++extraChannelButtons>2) extraChannelButtons=0;
}
@ -839,6 +846,7 @@ void FurnaceGUI::drawPattern() {
}
}
ImGui::TableNextColumn();
lastPatternWidth=ImGui::GetCursorPosX()-lpwStart+ImGui::GetStyle().ScrollbarSize;
if (e->hasExtValue()) {
ImGui::TextColored(uiColors[GUI_COLOR_EE_VALUE]," %.2X",e->getExtValue());
}
@ -1202,7 +1210,7 @@ void FurnaceGUI::drawPattern() {
}
ImGui::PopStyleVar();
if (patternOpen) {
if (!inhibitMenu && ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::OpenPopup("patternActionMenu");
if (!inhibitMenu && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) ImGui::OpenPopup("patternActionMenu");
if (ImGui::BeginPopup("patternActionMenu",ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) {
editOptions(false);
ImGui::EndPopup();

View File

@ -28,6 +28,9 @@
#include "sampleUtil.h"
#include "util.h"
#define CENTER_TEXT(text) \
ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x));
void FurnaceGUI::drawSampleEdit() {
if (nextWindow==GUI_WINDOW_SAMPLE_EDIT) {
sampleEditOpen=true;
@ -43,7 +46,40 @@ void FurnaceGUI::drawSampleEdit() {
}
if (ImGui::Begin("Sample Editor",&sampleEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) {
if (curSample<0 || curSample>=(int)e->song.sample.size()) {
ImGui::SetCursorPosY(ImGui::GetCursorPosY()+(ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()*2.0f)*0.5f);
CENTER_TEXT("no sample selected");
ImGui::Text("no sample selected");
if (ImGui::BeginTable("noAssetCenter",3)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.5f);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5f);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
if (e->song.sample.size()>0) {
if (ImGui::BeginCombo("##SampleSelect","select one...")) {
actualSampleList();
ImGui::EndCombo();
}
ImGui::SameLine();
ImGui::TextUnformatted("or");
ImGui::SameLine();
}
if (ImGui::Button("Open")) {
doAction(GUI_ACTION_SAMPLE_LIST_OPEN);
}
ImGui::SameLine();
ImGui::TextUnformatted("or");
ImGui::SameLine();
if (ImGui::Button("Create New")) {
doAction(GUI_ACTION_SAMPLE_LIST_ADD);
}
ImGui::TableNextColumn();
ImGui::EndTable();
}
} else {
DivSample* sample=e->song.sample[curSample];
String sampleType="Invalid";
@ -120,6 +156,9 @@ void FurnaceGUI::drawSampleEdit() {
sample->loopEnd=sample->samples;
}
updateSampleTex=true;
if (e->getSampleFormatMask()&(1U<<DIV_SAMPLE_DEPTH_BRR)) {
e->renderSamplesP();
}
}
if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) {
ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!");
@ -151,7 +190,7 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::Text("Loop Start");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,10)) { MARK_MODIFIED
if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,16)) { MARK_MODIFIED
if (sample->loopStart<0) {
sample->loopStart=0;
}
@ -159,6 +198,9 @@ void FurnaceGUI::drawSampleEdit() {
sample->loopStart=sample->loopEnd;
}
updateSampleTex=true;
if (e->getSampleFormatMask()&(1U<<DIV_SAMPLE_DEPTH_BRR)) {
e->renderSamplesP();
}
}
if (ImGui::IsItemActive()) {
keepLoopAlive=true;
@ -170,7 +212,7 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::Text("Loop End");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,10)) { MARK_MODIFIED
if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,16)) { MARK_MODIFIED
if (sample->loopEnd<sample->loopStart) {
sample->loopEnd=sample->loopStart;
}
@ -178,6 +220,9 @@ void FurnaceGUI::drawSampleEdit() {
sample->loopEnd=sample->samples;
}
updateSampleTex=true;
if (e->getSampleFormatMask()&(1U<<DIV_SAMPLE_DEPTH_BRR)) {
e->renderSamplesP();
}
}
if (ImGui::IsItemActive()) {
keepLoopAlive=true;
@ -696,6 +741,9 @@ void FurnaceGUI::drawSampleEdit() {
sample->loopEnd=sample->samples;
}
updateSampleTex=true;
if (e->getSampleFormatMask()&(1U<<DIV_SAMPLE_DEPTH_BRR)) {
e->renderSamplesP();
}
}
if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) {
ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!");
@ -728,13 +776,16 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::Text("Loop Start");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,10)) { MARK_MODIFIED
if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,16)) { MARK_MODIFIED
if (sample->loopStart<0) {
sample->loopStart=0;
}
if (sample->loopStart>sample->loopEnd) {
sample->loopStart=sample->loopEnd;
}
if (e->getSampleFormatMask()&(1U<<DIV_SAMPLE_DEPTH_BRR)) {
e->renderSamplesP();
}
updateSampleTex=true;
}
if (ImGui::IsItemActive()) {
@ -747,13 +798,16 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::Text("Loop End");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,10)) { MARK_MODIFIED
if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,16)) { MARK_MODIFIED
if (sample->loopEnd<sample->loopStart) {
sample->loopEnd=sample->loopStart;
}
if (sample->loopEnd>=(int)sample->samples) {
sample->loopEnd=sample->samples;
}
if (e->getSampleFormatMask()&(1U<<DIV_SAMPLE_DEPTH_BRR)) {
e->renderSamplesP();
}
updateSampleTex=true;
}
if (ImGui::IsItemActive()) {

View File

@ -1443,6 +1443,11 @@ void FurnaceGUI::drawSettings() {
settings.germanNotation=germanNotationB;
}
bool centerPatternB=settings.centerPattern;
if (ImGui::Checkbox("Center pattern view",&centerPatternB)) {
settings.centerPattern=centerPatternB;
}
bool unsignedDetuneB=settings.unsignedDetune;
if (ImGui::Checkbox("Unsigned FM detune values",&unsignedDetuneB)) {
settings.unsignedDetune=unsignedDetuneB;
@ -2363,6 +2368,7 @@ void FurnaceGUI::syncSettings() {
settings.maxRecentFile=e->getConfInt("maxRecentFile",10);
settings.midiOutClock=e->getConfInt("midiOutClock",0);
settings.midiOutMode=e->getConfInt("midiOutMode",1);
settings.centerPattern=e->getConfInt("centerPattern",0);
clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.patFontSize,2,96);
@ -2466,6 +2472,7 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.maxRecentFile,0,30);
clampSetting(settings.midiOutClock,0,1);
clampSetting(settings.midiOutMode,0,2);
clampSetting(settings.centerPattern,0,1);
String initialSys2=e->getConfString("initialSys2","");
if (initialSys2.empty()) {
@ -2630,6 +2637,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("maxRecentFile",settings.maxRecentFile);
e->setConf("midiOutClock",settings.midiOutClock);
e->setConf("midiOutMode",settings.midiOutMode);
e->setConf("centerPattern",settings.centerPattern);
// colors
for (int i=0; i<GUI_COLOR_MAX; i++) {

View File

@ -270,6 +270,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
case DIV_SYSTEM_GB: {
int chipType=flags.getInt("chipType",0);
bool noAntiClick=flags.getBool("noAntiClick",false);
bool enoughAlready=flags.getBool("enoughAlready",false);
if (ImGui::Checkbox("Disable anti-click",&noAntiClick)) {
altered=true;
@ -291,11 +292,15 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
chipType=3;
altered=true;
}
if (ImGui::Checkbox("Pretty please one more compat flag when I use arpeggio and my sound length",&enoughAlready)) {
altered=true;
}
if (altered) {
e->lockSave([&]() {
flags.set("chipType",chipType);
flags.set("noAntiClick",noAntiClick);
flags.set("enoughAlready",enoughAlready);
});
}
break;
@ -1309,6 +1314,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
}
case DIV_SYSTEM_MSM5232: {
int detune=flags.getInt("detune",0);
int vibSpeed=flags.getInt("vibSpeed",0);
float vibDepth=flags.getFloat("vibDepth",0.0f);
bool groupEnv[2];
int groupVol[8];
float capValue[8];
@ -1388,8 +1395,23 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
altered=true;
}
ImGui::Text("Global vibrato:");
if (CWSliderInt("Speed",&vibSpeed,0,256)) {
if (vibSpeed<0) vibSpeed=0;
if (vibSpeed>256) vibSpeed=256;
altered=true;
} rightClickable
if (CWSliderFloat("Depth",&vibDepth,0.0f,256.0f)) {
if (vibDepth<0) vibDepth=0;
if (vibDepth>256) vibDepth=256;
altered=true;
} rightClickable
if (altered) {
flags.set("detune",detune);
flags.set("vibSpeed",vibSpeed);
flags.set("vibDepth",vibDepth);
flags.set("capValue0",capValue[0]);
flags.set("capValue1",capValue[1]);

View File

@ -22,6 +22,7 @@
#include "plot_nolerp.h"
#include "IconsFontAwesome4.h"
#include "misc/cpp/imgui_stdlib.h"
#include <fmt/printf.h>
#include <math.h>
#include <imgui.h>
@ -175,7 +176,40 @@ void FurnaceGUI::drawWaveEdit() {
}
if (ImGui::Begin("Wavetable Editor",&waveEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) {
if (curWave<0 || curWave>=(int)e->song.wave.size()) {
ImGui::SetCursorPosY(ImGui::GetCursorPosY()+(ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()*2.0f)*0.5f);
CENTER_TEXT("no wavetable selected");
ImGui::Text("no wavetable selected");
if (ImGui::BeginTable("noAssetCenter",3)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.5f);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5f);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
if (e->song.wave.size()>0) {
if (ImGui::BeginCombo("##WaveSelect","select one...")) {
actualWaveList();
ImGui::EndCombo();
}
ImGui::SameLine();
ImGui::TextUnformatted("or");
ImGui::SameLine();
}
if (ImGui::Button("Open")) {
doAction(GUI_ACTION_WAVE_LIST_OPEN);
}
ImGui::SameLine();
ImGui::TextUnformatted("or");
ImGui::SameLine();
if (ImGui::Button("Create New")) {
doAction(GUI_ACTION_WAVE_LIST_ADD);
}
ImGui::TableNextColumn();
ImGui::EndTable();
}
} else {
DivWavetable* wave=e->song.wave[curWave];

View File

@ -171,6 +171,7 @@ TAParamResult pVersion(String) {
printf("- MAME SAA1099 emulation core by Juergen Buchmueller and Manuel Abadia (BSD 3-clause)\n");
printf("- MAME Namco WSG by Nicola Salmoria and Aaron Giles (BSD 3-clause)\n");
printf("- MAME RF5C68 core by Olivier Galibert and Aaron Giles (BSD 3-clause)\n");
printf("- MAME MSM5232 core by Jarek Burczynski and Hiromitsu Shioya (GPLv2)\n");
printf("- MAME MSM6258 core by Barry Rodewald (BSD 3-clause)\n");
printf("- MAME YMZ280B core by Aaron Giles (BSD 3-clause)\n");
printf("- QSound core by superctr (BSD 3-clause)\n");
@ -179,6 +180,7 @@ TAParamResult pVersion(String) {
printf("- SAASound by Dave Hooper and Simon Owen (BSD 3-clause)\n");
printf("- SameBoy by Lior Halphon (MIT)\n");
printf("- Mednafen PCE and WonderSwan by Mednafen Team (GPLv2)\n");
printf("- SNES DSP core by Blargg (LGPLv2.1)\n");
printf("- puNES by FHorse (GPLv2)\n");
printf("- NSFPlay by Brad Smith and Brezza (unknown open-source license)\n");
printf("- reSID by Dag Lem (GPLv2)\n");
@ -549,7 +551,7 @@ int main(int argc, char** argv) {
#ifdef HAVE_GUI
g.bindEngine(&e);
if (!g.init()) {
reportError("error while starting GUI!");
reportError(g.getLastError());
return 1;
}