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

This commit is contained in:
cam900 2023-03-27 15:07:45 +09:00
commit 2a881c9f66
84 changed files with 3061 additions and 381 deletions

2
.gitignore vendored
View file

@ -22,3 +22,5 @@ android/app/.cxx/
CMakeSettings.json
CMakePresets.json
extern/imgui_patched/examples/
src/asm/68k/amigatest/*.bin
src/asm/68k/amigatest/player

View file

@ -459,7 +459,7 @@ src/engine/platform/sound/ga20/iremga20.cpp
src/engine/platform/sound/sm8521.c
src/engine/platform/sound/d65010g031.c
src/engine/platform/sound/d65modified.c
src/engine/platform/oplAInterface.cpp
src/engine/platform/ym2608Interface.cpp
@ -469,10 +469,12 @@ src/engine/blip_buf.c
src/engine/brrUtils.c
src/engine/safeReader.cpp
src/engine/safeWriter.cpp
src/engine/cmdStream.cpp
src/engine/config.cpp
src/engine/configEngine.cpp
src/engine/dispatchContainer.cpp
src/engine/engine.cpp
src/engine/export.cpp
src/engine/fileOps.cpp
src/engine/fileOpsIns.cpp
src/engine/filter.cpp
@ -488,6 +490,7 @@ src/engine/waveSynth.cpp
src/engine/vgmOps.cpp
src/engine/zsmOps.cpp
src/engine/zsm.cpp
src/engine/platform/abstract.cpp
src/engine/platform/genesis.cpp
src/engine/platform/genesisext.cpp
@ -550,6 +553,9 @@ src/engine/platform/sm8521.cpp
src/engine/platform/pv1000.cpp
src/engine/platform/pcmdac.cpp
src/engine/platform/dummy.cpp
src/engine/export/abstract.cpp
src/engine/export/amigaValidation.cpp
)
if (USE_SNDFILE)
@ -619,6 +625,7 @@ src/gui/editing.cpp
src/gui/editControls.cpp
src/gui/effectList.cpp
src/gui/findReplace.cpp
src/gui/fmPreview.cpp
src/gui/gradient.cpp
src/gui/grooves.cpp
src/gui/insEdit.cpp

Binary file not shown.

Binary file not shown.

BIN
demos/snes/ManbowSMW.fur Normal file

Binary file not shown.

View file

@ -26,6 +26,7 @@ SOFTWARE.
*/
#include "ImGuiFileDialog.h"
#include "../../src/ta-log.h"
#ifdef __cplusplus
@ -1202,11 +1203,13 @@ namespace IGFD
SetDefaultFileName(".");
else
SetDefaultFileName("");
logV("IGFD: OpenCurrentPath()");
ScanDir(vFileDialogInternal, GetCurrentPath());
}
void IGFD::FileManager::SortFields(const FileDialogInternal& vFileDialogInternal, const SortingFieldEnum& vSortingField, const bool vCanChangeOrder)
{
logV("IGFD: SortFields()");
if (vSortingField != SortingFieldEnum::FIELD_NONE)
{
puHeaderFileName = tableHeaderFileNameString;
@ -1216,10 +1219,17 @@ namespace IGFD
#ifdef USE_THUMBNAILS
puHeaderFileThumbnails = tableHeaderFileThumbnailsString;
#endif // #ifdef USE_THUMBNAILS
} else {
logV("IGFD: sorting by NONE!");
}
if (prFileList.empty()) {
logV("IGFD: with an empty file list?");
}
if (vSortingField == SortingFieldEnum::FIELD_FILENAME)
{
logV("IGFD: sorting by name");
if (vCanChangeOrder && puSortingField == vSortingField) {
//printf("Change the sorting\n");
puSortingDirection[0] = true;//!puSortingDirection[0];
@ -1289,6 +1299,7 @@ namespace IGFD
}
else if (vSortingField == SortingFieldEnum::FIELD_TYPE)
{
logV("IGFD: sorting by type");
if (vCanChangeOrder && puSortingField == vSortingField)
puSortingDirection[1] = !puSortingDirection[1];
@ -1327,6 +1338,7 @@ namespace IGFD
}
else if (vSortingField == SortingFieldEnum::FIELD_SIZE)
{
logV("IGFD: sorting by size");
if (vCanChangeOrder && puSortingField == vSortingField)
puSortingDirection[2] = !puSortingDirection[2];
@ -1367,6 +1379,7 @@ namespace IGFD
}
else if (vSortingField == SortingFieldEnum::FIELD_DATE)
{
logV("IGFD: sorting by date");
if (vCanChangeOrder && puSortingField == vSortingField)
puSortingDirection[3] = !puSortingDirection[3];
@ -1458,6 +1471,8 @@ namespace IGFD
puSortingField = vSortingField;
}
logV("IGFD: applying filtering on file list");
ApplyFilteringOnFileList(vFileDialogInternal);
}
@ -1518,14 +1533,17 @@ namespace IGFD
void IGFD::FileManager::ScanDir(const FileDialogInternal& vFileDialogInternal, const std::string& vPath)
{
std::string path = vPath;
logV("IGFD: ScanDir(%s)",vPath);
if (prCurrentPathDecomposition.empty())
{
logV("IGFD: the current path decomposition is empty. setting.");
SetCurrentDir(path);
}
if (!prCurrentPathDecomposition.empty())
{
logV("IGFD: the current path decomposition is not empty. trying.");
#ifdef WIN32
if (path == puFsRoot)
path += std::string(1u, PATH_SEP);
@ -1553,6 +1571,7 @@ namespace IGFD
#else // dirent
struct dirent** files = nullptr;
int n = scandir(path.c_str(), &files, nullptr, inAlphaSort);
logV("IGFD: %d entries in directory",n);
if (n>0)
{
int i;
@ -1620,11 +1639,18 @@ namespace IGFD
}
free(files);
} else {
logV("IGFD: it's empty");
}
#endif // USE_STD_FILESYSTEM
logV("IGFD: sorting fields...");
SortFields(vFileDialogInternal, puSortingField, false);
} else {
logE("IGFD: current path decomposition is empty!");
}
fileListActuallyEmpty=prFileList.empty();
}
bool IGFD::FileManager::GetDrives()
@ -2436,7 +2462,7 @@ namespace IGFD
void IGFD::FileDialogInternal::ResetForNewDialog()
{
puFileManager.fileListActuallyEmpty=false;
}
/////////////////////////////////////////////////////////////////////////////////////
@ -3737,16 +3763,17 @@ namespace IGFD
fdFilter.SetDefaultFilterIfNotDefined();
// init list of files
if (fdFile.IsFileListEmpty() && !fdFile.puShowDrives)
if (fdFile.IsFileListEmpty() && !fdFile.puShowDrives && !fdFile.fileListActuallyEmpty)
{
IGFD::Utils::ReplaceString(fdFile.puDLGDefaultFileName, fdFile.puDLGpath, ""); // local path
if (!fdFile.puDLGDefaultFileName.empty())
{
fdFile.SetDefaultFileName(fdFile.puDLGDefaultFileName);
fdFilter.SetSelectedFilterWithExt(fdFilter.puDLGdefaultExt);
}
else if (fdFile.puDLGDirectoryMode) // directory mode
} else if (fdFile.puDLGDirectoryMode) { // directory mode
fdFile.SetDefaultFileName(".");
}
logV("IGFD: fdFile.IsFileListEmpty() and !fdFile.puShowDrives");
fdFile.ScanDir(prFileDialogInternal, fdFile.puDLGpath);
}

View file

@ -875,6 +875,7 @@ namespace IGFD
#endif
SortingFieldEnum puSortingField = SortingFieldEnum::FIELD_FILENAME; // detail view sorting column
bool puShowDrives = false; // drives are shown (only on os windows)
bool fileListActuallyEmpty = false;
std::string puDLGpath; // base path set by user when OpenDialog/OpenModal was called
std::string puDLGDefaultFileName; // base default file path name set by user when OpenDialog/OpenModal was called

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,4 @@
all: player
player: player.s sample.bin seq.bin wave.bin
vasmm68k_mot -Fhunkexe -kick1hunks -nosym -o player player.s

View file

@ -0,0 +1,56 @@
# Amiga verification export format
"ROM" export format exclusively for verifying whether the Amiga emulation in Furnace is correct.
do not assume this is the actual ROM export! it is nothing more than a register dump kind of thing...
# process
enable the setting in Furnace to unlock the export option by adding this to furnace.cfg:
```
iCannotWait=1
```
go to file > export Amiga validation data...
put sample.bin, sbook.bin, seq.bin, wave.bin and wbook.bin in this directory.
type `make`. you need vasm (68000 with Mot syntax) in order for it to work.
alternatively, type:
```
vasmm68k_mot -Fhunkexe -kick1hunks -nosym -o player player.s
```
run `player` on Amiga. it should play the exported song.
# notes
may not work correctly if you have slow/fast memory!
sequence and wave data should reside in fast memory but I haven't figured out how to...
# sequence format
## 00-0F: per-channel (00, 10, 20, 30)
- 00 xxxxxx yyyy: set loc/len
- x: loc
- y: len
- 01 xxxxxx yyyy: initialize wavetable (xxxx: pos; yy: length)
- 02 xx: set loc/len from sample book
- 03 xx: initialize wavetable from wave book
- 04 xxxx: initialize wavetable from wave book (short)
- 06 xxxx: set period
- 08 xx: set volume
- 0a xxxx: set data
## F0-FF: global
- f0: do nothing
- f1: next tick
- f2 xx: wait
- f3 xxxx: wait
- f6 xxxx: write to DMACON
- fe xxxx: write to ADKCON
- ff: end of song

View file

@ -0,0 +1,386 @@
; Furnace validation player code
; this is NOT the ROM export you're looking for!
VPOSR = $dff004
VHPOSR = $dff006
COLOR00 = $dff180
chipBase=$dff000
DMACONR = $02
POTGOR = $16
DMACON = $96
ADKCON = $9e
AUDBASE = $a0
AUD0LCH = $a0
AUD0LCL = $a2
AUD0LEN = $a4
AUD0PER = $a6
AUD0VOL = $a8
AUD0DAT = $aa
AUD1VOL = $b8
AUD2VOL = $c8
AUD3VOL = $d8
code_c
init:
move.b #2,$bfe001
lea chipBase,a0
move.w #15,DMACON(a0)
waitCon:
move.w DMACONR(a0),d0
andi.w #15,d0
bne waitCon
move.w #$8200,DMACON(a0)
move.w #$40,AUD0VOL(a0)
move.w #$40,AUD1VOL(a0)
move.w #$40,AUD2VOL(a0)
move.w #$40,AUD3VOL(a0)
lea seqAddr(pc),a0
lea sequence(pc),a1
move.l a1,(a0)
main:
bsr waitVBlank
move.w #$000,d4
move.w d4,COLOR00
lea chipBase,a0
btst.b #2,POTGOR(a0)
bne next
lea state(pc),a0
move.w #1,2(a0)
next:
bsr nextTick
lea state(pc),a0
tst.w 2(a0)
beq main
finish:
lea chipBase,a0
move.w #15,DMACON(a0)
clr.l d0
rts
waitVBlank:
move.l (VPOSR),d0
and.l #$1ff00,d0
cmp.l #$bc00,d0
bne waitVBlank
waitVBlank2:
move.l (VPOSR),d0
and.l #$1ff00,d0
cmp.l #$bd00,d0
bne waitVBlank2
rts
nextTick:
lea state(pc),a4
move.w (a4),d0
subi.w #1,d0
bmi nextTick0
move.w d0,(a4)
rts
nextTick0:
move.l seqAddr(pc),a2
nextTick1:
; get next command
clr.w d0
move.b (a2)+,d0
move.w #$0ff,d4
move.w d4,COLOR00
testSpecial:
cmp.w #$f0,d0
bmi testChannel
testF1:
; f1 - next tick
cmp.b #$f1,d0
bne testF2
; end of tick
move.w #0,(a4)
bra endTick
testF2:
; f2 - wait (char)
cmp.b #$f2,d0
bne testF3
move.b (a2)+,d0
andi.w #$ff,d0
move.w d0,(a4)
bra endTick
testF3:
; f3 - wait (short)
cmp.b #$f3,d0
bne testF6
clr.w d2
move.b (a2)+,d2
lsl.w #8,d2
or.b (a2)+,d2
move.w d2,(a4)
bra endTick
testF6:
; f6 - write DMACON
cmp.b #$f6,d0
bne testFE
move.w #$f00,d4
move.w d4,COLOR00
clr.w d2
move.b (a2)+,d2
lsl.w #8,d2
or.b (a2)+,d2
move.w d2,chipBase+DMACON
; wait for DMACON to be done
move.b (VHPOSR),d0
dmaConWait:
cmp.b (VHPOSR),d0
beq dmaConWait
; wait for DMACON to be done -2
move.b (VHPOSR),d0
dmaConWait1:
cmp.b (VHPOSR),d0
beq dmaConWait1
bra nextTick1
testFE:
; fe - write ADKCON
cmp.b #$fe,d0
bne testFF
clr.w d2
move.b (a2)+,d2
lsl.w #8,d2
or.b (a2)+,d2
move.w d2,chipBase+ADKCON
bra nextTick1
testFF:
; ff - end of song
cmp.b #$ff,d0
bne testOther
theEnd:
lea sequence(pc),a2
testOther:
; something else
bra nextTick1
testChannel:
cmp.b #$40,d0
bge invalidCmd
; process channel
move.b d0,d1
andi.b #15,d0
; check for 0
bne chanNotZero
sampleWrite:
; write loc/len
move.w #$f0f,d4
move.w d4,COLOR00
clr.l d2
move.b (a2)+,d2
lsl.l #8,d2
or.b (a2)+,d2
lsl.l #8,d2
or.b (a2)+,d2
lea sampleData,a0
add.l a0,d2
lea chipBase,a0
move.b d1,d0
andi.l #$ff,d0
addi.l #$a0,d0
adda.l d0,a0
move.l d2,(a0)+ ; location
clr.w d2
move.b (a2)+,d2
lsl.w #8,d2
or.b (a2)+,d2
move.w d2,(a0) ; length
bra nextTick1
chanNotZero:
; check for 8 (VOL)
cmp.b #8,d0
bne chanSampleBook
; write volume
clr.w d2
move.b (a2)+,d2
bra chanWrite
chanSampleBook:
; check for 2 (loc/len from book)
cmp.b #2,d0
bne chanWaveChange
move.w #$f0f,d4
move.w d4,COLOR00
clr.l d3
move.b (a2)+,d3
bsr getSampleBook
; write loc/len
lea sampleData,a0
add.l a0,d2
lea chipBase,a0
move.b d1,d0
andi.l #$f0,d0
addi.l #$a0,d0
adda.l d0,a0
move.l d2,(a0)+ ; location
move.w d3,(a0) ; length
bra nextTick1
chanWaveChange:
; check for 1 (wave change)
cmp.b #1,d0
bne chanWaveBookB
; copy wave
clr.l d2
move.b (a2)+,d2
lsl.l #8,d2
or.b (a2)+,d2
lsl.l #8,d2
or.b (a2)+,d2
add.l #wavetable,d2
move.l d2,a0
lea sampleData,a1
andi.l #$30,d1
lsl.l #4,d1
adda.l d1,a1
clr.l d2
move.b (a2)+,d2
lsl.l #8,d2
or.b (a2)+,d2
bsr copyWave
bra nextTick1
chanWaveBookB:
; check for 3 (wave change from book)
cmp.b #3,d0
bne chanWaveBookW
clr.l d3
move.b (a2)+,d3
bsr getWaveBook
lea sampleData,a1
andi.l #$30,d1
lsl.l #4,d1
adda.l d1,a1
bsr copyWave
bra nextTick1
chanWaveBookW:
; check for 4 (wave change from book short)
cmp.b #4,d0
bne chanOther
clr.l d3
move.b (a2)+,d3
lsl.l #8,d3
or.b (a2)+,d3
bsr getWaveBook
lea sampleData,a1
andi.l #$30,d1
lsl.l #4,d1
adda.l d1,a1
bsr copyWave
bra nextTick1
chanOther:
; get value and write
clr.w d2
move.b (a2)+,d2
lsl.w #8,d2
or.b (a2)+,d2
chanWrite:
move.w #$ff0,d4
move.w d4,COLOR00
lea chipBase,a0
or.b d1,d0
addi.b #AUDBASE,d0
andi.l #$ff,d0
adda.l d0,a0
move.w d2,(a0)
invalidCmd:
bra nextTick1
endTick:
lea seqAddr(pc),a3
move.l a2,(a3)
move.w #$000,d4
move.w d4,COLOR00
rts
; a0: source. a1: destination. d2: length.
copyWave:
; don't copy a zero-length wave
tst.l d2
beq noCopy
copyWaveLoop:
move.w (a0)+,(a1)+
subq.l #2,d2
bne copyWaveLoop
noCopy:
rts
; put wave book entry in a0/d2. d3: index (modified).
getWaveBook:
lea waveBook(pc),a3
lsl.l #2,d3
move.l (a3,d3),d2
move.l d2,d3
rol.l #8,d2
andi.l #$ff,d2
bne getWaveBook2
move.w #$100,d2
getWaveBook2:
andi.l #$ffffff,d3
add.l #wavetable,d3
move.l d3,a0
rts
; get sample book entry in d2/d3. d3: index.
getSampleBook:
lea sampleBook(pc),a3
lsl.l #3,d3
adda.l d3,a3
move.l (a3)+,d2
move.l (a3)+,d3
andi.l #$ffffff,d2
andi.l #$ffff,d3
rts
data_c
cnop 0,4
curColor:
dc.w 0
state:
dc.w 0 ; ticks
dc.w 0 ; quit
cnop 0,4
seqAddr:
dc.l 0
cnop 0,4
sampleBook:
incbin "sbook.bin"
cnop 0,4
waveBook:
incbin "wbook.bin"
cnop 0,4
sequence:
incbin "seq.bin"
cnop 0,4
sampleData:
incbin "sample.bin"
cnop 0,4
wavetable:
incbin "wave.bin"

301
src/engine/cmdStream.cpp Normal file
View file

@ -0,0 +1,301 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "cmdStream.h"
#include "engine.h"
#include "../ta-log.h"
void DivCSPlayer::cleanup() {
delete b;
}
bool DivCSPlayer::tick() {
bool ticked=false;
for (int i=0; i<e->getTotalChannelCount(); i++) {
bool sendVolume=false;
if (chan[i].readPos==0) continue;
ticked=true;
chan[i].waitTicks--;
while (chan[i].waitTicks<=0) {
stream.seek(chan[i].readPos,SEEK_SET);
unsigned char next=stream.readC();
unsigned char command=0;
if (next<0xb3) { // note
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,next-60));
} else if (next>=0xd0 && next<=0xdf) {
command=fastCmds[next&15];
} else if (next>=0xe0 && next<=0xef) { // preset delay
chan[i].waitTicks=fastDelays[next&15];
} else switch (next) {
case 0xb4: // note on null
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL));
break;
case 0xb5: // note off
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i));
break;
case 0xb6: // note off env
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF_ENV,i));
break;
case 0xb7: // env release
e->dispatchCmd(DivCommand(DIV_CMD_ENV_RELEASE,i));
break;
case 0xb8: case 0xbe: case 0xc0: case 0xc2:
case 0xc3: case 0xc4: case 0xc5: case 0xc6:
case 0xc7: case 0xc8: case 0xc9: case 0xca:
command=next-0xb4;
break;
case 0xf7:
command=stream.readC();
break;
case 0xf8:
logE("TODO: CALL");
break;
case 0xf9:
logE("TODO: RET");
break;
case 0xfa:
logE("TODO: JMP");
break;
case 0xfb:
logE("TODO: RATE");
break;
case 0xfc:
chan[i].waitTicks=(unsigned short)stream.readS();
break;
case 0xfd:
chan[i].waitTicks=(unsigned char)stream.readC();
break;
case 0xfe:
chan[i].waitTicks=1;
break;
case 0xff:
chan[i].readPos=0;
break;
}
if (chan[i].readPos==0) break;
if (command) {
int arg0=0;
int arg1=0;
switch (command) {
case DIV_CMD_INSTRUMENT:
case DIV_CMD_HINT_VIBRATO_RANGE:
case DIV_CMD_HINT_VIBRATO_SHAPE:
case DIV_CMD_HINT_PITCH:
case DIV_CMD_HINT_VOLUME:
arg0=(unsigned char)stream.readC();
break;
case DIV_CMD_PANNING:
case DIV_CMD_HINT_VIBRATO:
case DIV_CMD_HINT_ARPEGGIO:
case DIV_CMD_HINT_PORTA:
arg0=(unsigned char)stream.readC();
arg1=(unsigned char)stream.readC();
break;
case DIV_CMD_PRE_PORTA:
arg0=(unsigned char)stream.readC();
arg1=(arg0&0x40)?1:0;
arg0=(arg0&0x80)?1:0;
break;
case DIV_CMD_HINT_VOL_SLIDE:
arg0=(short)stream.readS();
break;
case DIV_CMD_SAMPLE_MODE:
case DIV_CMD_SAMPLE_FREQ:
case DIV_CMD_SAMPLE_BANK:
case DIV_CMD_SAMPLE_POS:
case DIV_CMD_SAMPLE_DIR:
case DIV_CMD_FM_HARD_RESET:
case DIV_CMD_FM_LFO:
case DIV_CMD_FM_LFO_WAVE:
case DIV_CMD_FM_FB:
case DIV_CMD_FM_EXTCH:
case DIV_CMD_FM_AM_DEPTH:
case DIV_CMD_FM_PM_DEPTH:
case DIV_CMD_STD_NOISE_FREQ:
case DIV_CMD_STD_NOISE_MODE:
case DIV_CMD_WAVE:
case DIV_CMD_GB_SWEEP_TIME:
case DIV_CMD_GB_SWEEP_DIR:
case DIV_CMD_PCE_LFO_MODE:
case DIV_CMD_PCE_LFO_SPEED:
case DIV_CMD_NES_DMC:
case DIV_CMD_C64_CUTOFF:
case DIV_CMD_C64_RESONANCE:
case DIV_CMD_C64_FILTER_MODE:
case DIV_CMD_C64_RESET_TIME:
case DIV_CMD_C64_RESET_MASK:
case DIV_CMD_C64_FILTER_RESET:
case DIV_CMD_C64_DUTY_RESET:
case DIV_CMD_C64_EXTENDED:
case DIV_CMD_AY_ENVELOPE_SET:
case DIV_CMD_AY_ENVELOPE_LOW:
case DIV_CMD_AY_ENVELOPE_HIGH:
case DIV_CMD_AY_ENVELOPE_SLIDE:
case DIV_CMD_AY_NOISE_MASK_AND:
case DIV_CMD_AY_NOISE_MASK_OR:
case DIV_CMD_AY_AUTO_ENVELOPE:
case DIV_CMD_FDS_MOD_DEPTH:
case DIV_CMD_FDS_MOD_HIGH:
case DIV_CMD_FDS_MOD_LOW:
case DIV_CMD_FDS_MOD_POS:
case DIV_CMD_FDS_MOD_WAVE:
case DIV_CMD_SAA_ENVELOPE:
case DIV_CMD_AMIGA_FILTER:
case DIV_CMD_AMIGA_AM:
case DIV_CMD_AMIGA_PM:
case DIV_CMD_MACRO_OFF:
case DIV_CMD_MACRO_ON:
arg0=(unsigned char)stream.readC();
break;
case DIV_CMD_FM_TL:
case DIV_CMD_FM_AM:
case DIV_CMD_FM_AR:
case DIV_CMD_FM_DR:
case DIV_CMD_FM_SL:
case DIV_CMD_FM_D2R:
case DIV_CMD_FM_RR:
case DIV_CMD_FM_DT:
case DIV_CMD_FM_DT2:
case DIV_CMD_FM_RS:
case DIV_CMD_FM_KSR:
case DIV_CMD_FM_VIB:
case DIV_CMD_FM_SUS:
case DIV_CMD_FM_WS:
case DIV_CMD_FM_SSG:
case DIV_CMD_FM_REV:
case DIV_CMD_FM_EG_SHIFT:
case DIV_CMD_FM_MULT:
case DIV_CMD_FM_FINE:
case DIV_CMD_AY_IO_WRITE:
case DIV_CMD_AY_AUTO_PWM:
case DIV_CMD_SURROUND_PANNING:
arg0=(unsigned char)stream.readC();
arg1=(unsigned char)stream.readC();
break;
case DIV_CMD_C64_FINE_DUTY:
case DIV_CMD_C64_FINE_CUTOFF:
case DIV_CMD_LYNX_LFSR_LOAD:
arg0=(unsigned short)stream.readS();
break;
case DIV_CMD_FM_FIXFREQ:
arg0=(unsigned short)stream.readS();
arg1=arg0&0x7ff;
arg0>>=12;
break;
case DIV_CMD_NES_SWEEP:
arg0=(unsigned char)stream.readC();
arg1=arg0&0x77;
arg0=(arg0&8)?1:0;
break;
}
switch (command) {
case DIV_CMD_HINT_VOLUME:
chan[i].volume=arg0<<8;
sendVolume=true;
break;
case DIV_CMD_HINT_VOL_SLIDE:
chan[i].volSpeed=arg0;
break;
default: // dispatch it
e->dispatchCmd(DivCommand((DivDispatchCmds)command,i,arg0,arg1));
break;
}
}
chan[i].readPos=stream.tell();
}
if (sendVolume || chan[i].volSpeed!=0) {
chan[i].volume+=chan[i].volSpeed;
if (chan[i].volume<0) {
chan[i].volume=0;
}
if (chan[i].volume>chan[i].volMax) {
chan[i].volume=chan[i].volMax;
}
e->dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
}
}
return ticked;
}
bool DivCSPlayer::init() {
unsigned char magic[4];
stream.seek(0,SEEK_SET);
stream.read(magic,4);
if (memcmp(magic,"FCS",4)!=0) return false;
unsigned int chans=stream.readI();
for (unsigned int i=0; i<chans; i++) {
if (i>=DIV_MAX_CHANS) {
stream.readI();
continue;
}
if ((int)i>=e->getTotalChannelCount()) {
stream.readI();
continue;
}
chan[i].readPos=stream.readI();
}
stream.read(fastDelays,16);
stream.read(fastCmds,16);
// initialize state
for (int i=0; i<e->getTotalChannelCount(); i++) {
chan[i].volMax=(e->getDispatch(e->dispatchOfChan[i])->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,e->dispatchChanOfChan[i]))<<8)|0xff;
chan[i].volume=chan[i].volMax;
}
return true;
}
// DivEngine
bool DivEngine::playStream(unsigned char* f, size_t length) {
BUSY_BEGIN;
cmdStreamInt=new DivCSPlayer(this,f,length);
if (!cmdStreamInt->init()) {
logE("not a command stream!");
lastError="not a command stream";
delete[] f;
delete cmdStreamInt;
cmdStreamInt=NULL;
BUSY_END;
return false;
}
if (!playing) {
reset();
freelance=true;
playing=true;
}
BUSY_END;
return true;
}

59
src/engine/cmdStream.h Normal file
View file

@ -0,0 +1,59 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _CMD_STREAM_H
#define _CMD_STREAM_H
#include "defines.h"
#include "safeReader.h"
class DivEngine;
struct DivCSChannelState {
unsigned int readPos;
int waitTicks;
int volume, volMax, volSpeed;
DivCSChannelState():
readPos(0),
waitTicks(0),
volume(0x7f00),
volMax(0),
volSpeed(0) {}
};
class DivCSPlayer {
DivEngine* e;
unsigned char* b;
SafeReader stream;
DivCSChannelState chan[DIV_MAX_CHANS];
unsigned char fastDelays[16];
unsigned char fastCmds[16];
public:
void cleanup();
bool tick();
bool init();
DivCSPlayer(DivEngine* en, unsigned char* buf, size_t len):
e(en),
b(buf),
stream(buf,len) {}
};
#endif

View file

@ -280,19 +280,31 @@ struct DivRegWrite {
* - 0xffffffff: reset
*/
unsigned int addr;
unsigned short val;
DivRegWrite(unsigned int a, unsigned short v):
unsigned int val;
DivRegWrite(unsigned int a, unsigned int v):
addr(a), val(v) {}
};
struct DivDelayedWrite {
int time;
DivRegWrite write;
DivDelayedWrite(int t, unsigned int a, unsigned short v):
DivDelayedWrite(int t, unsigned int a, unsigned int v):
time(t),
write(a,v) {}
};
struct DivSamplePos {
int sample, pos, freq;
DivSamplePos(int s, int p, int f):
sample(s),
pos(p),
freq(f) {}
DivSamplePos():
sample(-1),
pos(0),
freq(0) {}
};
struct DivDispatchOscBuffer {
bool follow;
unsigned int rate;
@ -371,18 +383,29 @@ class DivDispatch {
/**
* get the state of a channel.
* @param chan the channel.
* @return a pointer, or NULL.
*/
virtual void* getChanState(int chan);
/**
* get the DivMacroInt of a chanmel.
* get the DivMacroInt of a channel.
* @param chan the channel.
* @return a pointer, or NULL.
*/
virtual DivMacroInt* getChanMacroInt(int chan);
/**
* get currently playing sample (and its position).
* @param chan the channel.
* @return a DivSamplePos. if sample is -1 then nothing is playing or the
* channel doesn't play samples.
*/
virtual DivSamplePos getSamplePos(int chan);
/**
* get an oscilloscope buffer for a channel.
* @param chan the channel.
* @return a pointer to a DivDispatchOscBuffer, or NULL if not supported.
*/
virtual DivDispatchOscBuffer* getOscBuffer(int chan);

View file

@ -1588,7 +1588,7 @@ void DivEngine::checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries) {
// get unsorted directory
DivAssetDir* unsortedDir=NULL;
for (DivAssetDir& i: dir) {
if (i.name=="Unsorted") {
if (i.name.empty()) {
unsortedDir=&i;
break;
}
@ -1596,7 +1596,7 @@ void DivEngine::checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries) {
// create unsorted directory if it doesn't exist
if (unsortedDir==NULL) {
dir.push_back(DivAssetDir("Unsorted"));
dir.push_back(DivAssetDir(""));
unsortedDir=&(*dir.rbegin());
}
@ -2119,6 +2119,11 @@ DivMacroInt* DivEngine::getMacroInt(int chan) {
return disCont[dispatchOfChan[chan]].dispatch->getChanMacroInt(dispatchChanOfChan[chan]);
}
DivSamplePos DivEngine::getSamplePos(int chan) {
if (chan<0 || chan>=chans) return DivSamplePos();
return disCont[dispatchOfChan[chan]].dispatch->getSamplePos(dispatchChanOfChan[chan]);
}
DivDispatchOscBuffer* DivEngine::getOscBuffer(int chan) {
if (chan<0 || chan>=chans) return NULL;
return disCont[dispatchOfChan[chan]].dispatch->getOscBuffer(dispatchChanOfChan[chan]);
@ -2498,6 +2503,10 @@ void DivEngine::recalcChans() {
if (isInsTypePossible[i]) possibleInsTypes.push_back((DivInstrumentType)i);
}
checkAssetDir(song.insDir,song.ins.size());
checkAssetDir(song.waveDir,song.wave.size());
checkAssetDir(song.sampleDir,song.sample.size());
hasLoadedSomething=true;
}
@ -2576,6 +2585,10 @@ int DivEngine::divToFileRate(int drate) {
return 4;
}
void DivEngine::testFunction() {
logI("it works!");
}
int DivEngine::getEffectiveSampleRate(int rate) {
if (rate<1) return 0;
switch (song.system[0]) {
@ -4358,6 +4371,7 @@ bool DivEngine::initAudioBackend() {
lowLatency=getConfInt("lowLatency",0);
metroVol=(float)(getConfInt("metroVol",100))/100.0f;
midiOutClock=getConfInt("midiOutClock",0);
midiOutProgramChange = getConfInt("midiOutProgramChange",0);
midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE);
if (metroVol<0.0f) metroVol=0.0f;
if (metroVol>2.0f) metroVol=2.0f;

View file

@ -23,8 +23,10 @@
#include "instrument.h"
#include "song.h"
#include "dispatch.h"
#include "export.h"
#include "dataErrors.h"
#include "safeWriter.h"
#include "cmdStream.h"
#include "../audio/taAudio.h"
#include "blip_buf.h"
#include <atomic>
@ -47,11 +49,17 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define EXTERN_BUSY_BEGIN e->softLocked=false; e->isBusy.lock();
#define EXTERN_BUSY_BEGIN_SOFT e->softLocked=true; e->isBusy.lock();
#define EXTERN_BUSY_END e->isBusy.unlock(); e->softLocked=false;
#define DIV_VERSION "dev145"
#define DIV_ENGINE_VERSION 145
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
#define DIV_VERSION_S3M 0xff03
#define DIV_VERSION_FTM 0xff04
// "Namco C163"
#define DIV_C163_DEFAULT_NAME "Namco 163"
@ -359,6 +367,7 @@ class DivEngine {
bool systemsRegistered;
bool hasLoadedSomething;
bool midiOutClock;
bool midiOutProgramChange;
int midiOutMode;
int softLockCount;
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats, curSpeed;
@ -397,6 +406,8 @@ class DivEngine {
static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS];
static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS];
DivCSPlayer* cmdStreamInt;
struct SamplePreview {
double rate;
int sample;
@ -441,7 +452,6 @@ class DivEngine {
// MIDI stuff
std::function<int(const TAMidiMessage&)> midiCallback=[](const TAMidiMessage&) -> int {return -2;};
int dispatchCmd(DivCommand c);
void processRow(int i, bool afterDelay);
void nextOrder();
void nextRow();
@ -454,9 +464,12 @@ class DivEngine {
void reset();
void playSub(bool preserveDrift, int goalRow=0);
void testFunction();
bool loadDMF(unsigned char* file, size_t len);
bool loadFur(unsigned char* file, size_t len);
bool loadMod(unsigned char* file, size_t len);
bool loadS3M(unsigned char* file, size_t len);
bool loadFTM(unsigned char* file, size_t len);
bool loadFC(unsigned char* file, size_t len);
@ -493,6 +506,10 @@ class DivEngine {
// check whether an asset directory is complete
void checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries);
// add every export method here
friend class DivROMExport;
friend class DivExportAmigaValidation;
public:
DivSong song;
DivOrders* curOrders;
@ -522,6 +539,8 @@ class DivEngine {
void createNewFromDefaults();
// load a file.
bool load(unsigned char* f, size_t length);
// play a binary command stream.
bool playStream(unsigned char* f, size_t length);
// save as .dmf.
SafeWriter* saveDMF(unsigned char version);
// save as .fur.
@ -529,7 +548,7 @@ class DivEngine {
SafeWriter* saveFur(bool notPrimary=false);
// build a ROM file (TODO).
// specify system to build ROM for.
SafeWriter* buildROM(int sys);
std::vector<DivROMExportOutput> buildROM(DivROMExportOptions sys);
// dump to VGM.
// set trailingTicks to:
// - 0 to add one tick of trailing
@ -552,6 +571,9 @@ class DivEngine {
// notify wavetable change
void notifyWaveChange(int wave);
// dispatch a command
int dispatchCmd(DivCommand c);
// get system IDs
static DivSystem systemFromFileFur(unsigned char val);
static unsigned char systemToFileFur(DivSystem val);
@ -901,6 +923,9 @@ class DivEngine {
// get macro interpreter
DivMacroInt* getMacroInt(int chan);
// get sample position
DivSamplePos getSamplePos(int chan);
// get osc buffer
DivDispatchOscBuffer* getOscBuffer(int chan);
@ -1094,6 +1119,7 @@ class DivEngine {
systemsRegistered(false),
hasLoadedSomething(false),
midiOutClock(false),
midiOutProgramChange(false),
midiOutMode(DIV_MIDI_MODE_NOTE),
softLockCount(0),
subticks(0),
@ -1133,6 +1159,7 @@ class DivEngine {
audioEngine(DIV_AUDIO_NULL),
exportMode(DIV_EXPORT_MODE_ONE),
exportFadeOut(0.0),
cmdStreamInt(NULL),
midiBaseChan(0),
midiPoly(true),
midiAgeCounter(0),

37
src/engine/export.cpp Normal file
View file

@ -0,0 +1,37 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "engine.h"
#include "export/amigaValidation.h"
std::vector<DivROMExportOutput> DivEngine::buildROM(DivROMExportOptions sys) {
DivROMExport* exporter=NULL;
switch (sys) {
case DIV_ROM_AMIGA_VALIDATION:
exporter=new DivExportAmigaValidation;
break;
default:
exporter=new DivROMExport;
break;
}
std::vector<DivROMExportOutput> ret=exporter->go(this);
delete exporter;
return ret;
}

75
src/engine/export.h Normal file
View file

@ -0,0 +1,75 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _EXPORT_H
#define _EXPORT_H
#include "song.h"
#include <initializer_list>
#include <vector>
class DivEngine;
enum DivROMExportOptions {
DIV_ROM_ABSTRACT=0,
DIV_ROM_AMIGA_VALIDATION,
DIV_ROM_MAX
};
struct DivROMExportOutput {
String name;
SafeWriter* data;
DivROMExportOutput(String n, SafeWriter* d):
name(n),
data(d) {}
DivROMExportOutput():
name(""),
data(NULL) {}
};
class DivROMExport {
public:
virtual std::vector<DivROMExportOutput> go(DivEngine* e);
virtual ~DivROMExport() {}
};
struct DivROMExportDef {
const char* name;
const char* author;
const char* description;
DivSystem requisites[32];
int requisitesLen;
bool multiOutput;
DivROMExportDef(const char* n, const char* a, const char* d, std::initializer_list<DivSystem> req, bool multiOut):
name(n),
author(a),
description(d),
multiOutput(multiOut) {
requisitesLen=0;
memset(requisites,0,32*sizeof(DivSystem));
for (DivSystem i: req) {
requisites[requisitesLen++]=i;
}
}
};
#endif

View file

@ -0,0 +1,26 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "../export.h"
#include "../../ta-log.h"
std::vector<DivROMExportOutput> DivROMExport::go(DivEngine* e) {
logW("what's this? the null ROM export?");
return std::vector<DivROMExportOutput>();
}

View file

@ -0,0 +1,276 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "amigaValidation.h"
#include "../engine.h"
#include "../platform/amiga.h"
struct WaveEntry {
unsigned int pos;
short width;
signed char data[256];
WaveEntry():
pos(0),
width(32) {
memset(data,0,256);
}
};
struct SampleBookEntry {
unsigned int loc;
unsigned short len;
SampleBookEntry():
loc(0),
len(0) {}
};
std::vector<DivROMExportOutput> DivExportAmigaValidation::go(DivEngine* e) {
std::vector<DivROMExportOutput> ret;
std::vector<WaveEntry> waves;
std::vector<SampleBookEntry> sampleBook;
unsigned int wavesDataPtr=0;
WaveEntry curWaveState[4];
unsigned int sampleBookLoc=0;
DivPlatformAmiga* amiga=(DivPlatformAmiga*)e->getDispatch(0);
e->stop();
e->repeatPattern=false;
e->setOrder(0);
EXTERN_BUSY_BEGIN_SOFT;
// determine loop point
int loopOrder=0;
int loopRow=0;
int loopEnd=0;
e->walkSong(loopOrder,loopRow,loopEnd);
e->curOrder=0;
e->freelance=false;
e->playing=false;
e->extValuePresent=false;
e->remainingLoops=-1;
// play the song ourselves
bool done=false;
// sample.bin
SafeWriter* sample=new SafeWriter;
sample->init();
for (int i=0; i<256; i++) {
sample->writeI(0);
}
sample->write(&((const unsigned char*)amiga->getSampleMem(0))[0x400],amiga->getSampleMemUsage(0)-0x400);
if (sample->tell()&1) sample->writeC(0);
// seq.bin
SafeWriter* seq=new SafeWriter;
seq->init();
amiga->toggleRegisterDump(true);
// write song data
e->playSub(false);
size_t songTick=0;
size_t lastTick=0;
//bool writeLoop=false;
int loopPos=-1;
for (int i=0; i<e->chans; i++) {
e->chan[i].wentThroughNote=false;
e->chan[i].goneThroughNote=false;
}
while (!done) {
if (loopPos==-1) {
if (loopOrder==e->curOrder && loopRow==e->curRow && e->ticks==1) {
//writeLoop=true;
}
}
if (e->nextTick(false,true)) {
done=true;
amiga->getRegisterWrites().clear();
if (lastTick!=songTick) {
int delta=songTick-lastTick;
if (delta==1) {
seq->writeC(0xf1);
} else if (delta<256) {
seq->writeC(0xf2);
seq->writeC(delta-1);
} else if (delta<32768) {
seq->writeC(0xf3);
seq->writeS_BE(delta-1);
}
lastTick=songTick;
}
break;
}
// check wavetable changes
for (int i=0; i<4; i++) {
if (amiga->chan[i].useWave) {
if ((amiga->chan[i].audLen*2)!=curWaveState[i].width || memcmp(curWaveState[i].data,&(((signed char*)amiga->getSampleMem())[i<<8]),amiga->chan[i].audLen*2)!=0) {
curWaveState[i].width=amiga->chan[i].audLen*2;
memcpy(curWaveState[i].data,&(((signed char*)amiga->getSampleMem())[i<<8]),amiga->chan[i].audLen*2);
int waveNum=-1;
for (size_t j=0; j<waves.size(); j++) {
if (waves[j].width!=curWaveState[i].width) continue;
if (memcmp(waves[j].data,curWaveState[i].data,curWaveState[i].width)==0) {
waveNum=j;
break;
}
}
if (waveNum==-1) {
// write new wavetable
waveNum=(int)waves.size();
curWaveState[i].pos=wavesDataPtr;
waves.push_back(curWaveState[i]);
wavesDataPtr+=curWaveState[i].width;
}
if (waveNum<256) {
seq->writeC((i<<4)|3);
seq->writeC(waveNum);
} else if (waveNum<65536) {
seq->writeC((i<<4)|4);
seq->writeS_BE(waveNum);
} else{
seq->writeC((i<<4)|1);
seq->writeC(waves[waveNum].pos>>16);
seq->writeC(waves[waveNum].pos>>8);
seq->writeC(waves[waveNum].pos);
seq->writeS_BE(waves[waveNum].width);
}
}
}
}
// get register writes
std::vector<DivRegWrite>& writes=amiga->getRegisterWrites();
for (DivRegWrite& j: writes) {
if (lastTick!=songTick) {
int delta=songTick-lastTick;
if (delta==1) {
seq->writeC(0xf1);
} else if (delta<256) {
seq->writeC(0xf2);
seq->writeC(delta-1);
} else if (delta<32768) {
seq->writeC(0xf3);
seq->writeS_BE(delta-1);
}
lastTick=songTick;
}
if (j.addr>=0x200) { // direct loc/len change
if (j.addr&4) { // len
int sampleBookIndex=-1;
for (size_t i=0; i<sampleBook.size(); i++) {
if (sampleBook[i].loc==sampleBookLoc && sampleBook[i].len==(j.val&0xffff)) {
sampleBookIndex=i;
break;
}
}
if (sampleBookIndex==-1) {
if (sampleBook.size()<256) {
SampleBookEntry e;
e.loc=sampleBookLoc;
e.len=j.val&0xffff;
sampleBookIndex=sampleBook.size();
sampleBook.push_back(e);
}
}
if (sampleBookIndex==-1) {
seq->writeC((j.addr&3)<<4);
seq->writeC(sampleBookLoc>>16);
seq->writeC(sampleBookLoc>>8);
seq->writeC(sampleBookLoc);
seq->writeS_BE(j.val);
} else {
seq->writeC(((j.addr&3)<<4)|2);
seq->writeC(sampleBookIndex);
}
} else { // loc
sampleBookLoc=j.val;
}
} else if (j.addr<0xa0) {
// don't write INTENA
if ((j.addr&15)!=10) {
seq->writeC(0xf0|(j.addr&15));
seq->writeS_BE(j.val);
}
} else if ((j.addr&15)!=0 && (j.addr&15)!=2 && (j.addr&15)!=4) {
seq->writeC(j.addr-0xa0);
if ((j.addr&15)==8) {
seq->writeC(j.val);
} else {
seq->writeS_BE(j.val);
}
}
}
writes.clear();
songTick++;
}
// end of song
seq->writeC(0xff);
amiga->toggleRegisterDump(false);
e->remainingLoops=-1;
e->playing=false;
e->freelance=false;
e->extValuePresent=false;
EXTERN_BUSY_END;
// wave.bin
SafeWriter* wave=new SafeWriter;
wave->init();
for (WaveEntry& i: waves) {
wave->write(i.data,i.width);
}
// sbook.bin
SafeWriter* sbook=new SafeWriter;
sbook->init();
for (SampleBookEntry& i: sampleBook) {
// 8 bytes per entry
sbook->writeI_BE(i.loc);
sbook->writeI_BE(i.len);
}
// wbook.bin
SafeWriter* wbook=new SafeWriter;
wbook->init();
for (WaveEntry& i: waves) {
wbook->writeC(i.width);
wbook->writeC(i.pos>>16);
wbook->writeC(i.pos>>8);
wbook->writeC(i.pos);
}
// finish
ret.push_back(DivROMExportOutput("sbook.bin",sbook));
ret.push_back(DivROMExportOutput("wbook.bin",wbook));
ret.push_back(DivROMExportOutput("sample.bin",sample));
ret.push_back(DivROMExportOutput("wave.bin",wave));
ret.push_back(DivROMExportOutput("seq.bin",seq));
return ret;
}

View file

@ -0,0 +1,26 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "../export.h"
class DivExportAmigaValidation: public DivROMExport {
public:
std::vector<DivROMExportOutput> go(DivEngine* e);
~DivExportAmigaValidation() {}
};

View file

@ -30,6 +30,7 @@
#define DIV_FTM_MAGIC "FamiTracker Module"
#define DIV_FC13_MAGIC "SMOD"
#define DIV_FC14_MAGIC "FC14"
#define DIV_S3M_MAGIC "SCRM"
struct InflateBlock {
unsigned char* buf;
@ -3226,6 +3227,246 @@ void generateFCPresetWave(int index, DivWavetable* wave) {
}
}
bool DivEngine::loadS3M(unsigned char* file, size_t len) {
struct InvalidHeaderException {};
bool success=false;
char magic[4]={0,0,0,0};
SafeReader reader=SafeReader(file,len);
warnings="";
unsigned char chanSettings[32];
unsigned char ord[256];
unsigned short insPtr[256];
unsigned short patPtr[256];
unsigned char chanPan[16];
unsigned char defVol[256];
try {
DivSong ds;
ds.version=DIV_VERSION_S3M;
ds.linearPitch=0;
ds.pitchMacroIsLinear=false;
ds.noSlidesOnFirstTick=true;
ds.rowResetsArpPos=true;
ds.ignoreJumpAtEnd=false;
// load here
if (!reader.seek(0x2c,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete[] file;
return false;
}
reader.read(magic,4);
if (memcmp(magic,DIV_S3M_MAGIC,4)!=0) {
logW("the magic isn't complete");
throw EndOfFileException(&reader,reader.tell());
}
if (!reader.seek(0,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete[] file;
return false;
}
ds.name=reader.readString(28);
reader.readC(); // 0x1a
if (reader.readC()!=16) {
logW("type is wrong!");
}
reader.readS(); // x
unsigned short ordersLen=reader.readS();
ds.insLen=reader.readS();
if (ds.insLen<0 || ds.insLen>256) {
logE("invalid instrument count!");
lastError="invalid instrument count!";
delete[] file;
return false;
}
unsigned short patCount=reader.readS();
unsigned short flags=reader.readS();
unsigned short version=reader.readS();
bool signedSamples=(reader.readS()==1);
if ((flags&64) || version==0x1300) {
ds.noSlidesOnFirstTick=false;
}
reader.readI(); // "SCRM"
unsigned char globalVol=reader.readC();
ds.subsong[0]->speeds.val[0]=(unsigned char)reader.readC();
ds.subsong[0]->hz=((double)reader.readC())/2.5;
ds.subsong[0]->customTempo=true;
unsigned char masterVol=reader.readC();
logV("masterVol: %d",masterVol);
logV("signedSamples: %d",signedSamples);
logV("globalVol: %d",globalVol);
reader.readC(); // UC
bool defaultPan=(((unsigned char)reader.readC())==252);
reader.readS(); // reserved
reader.readI();
reader.readI(); // the last 2 bytes is Special. we don't read that.
reader.read(chanSettings,32);
logD("reading orders...");
for (int i=0; i<ordersLen; i++) {
ord[i]=reader.readC();
logV("- %.2x",ord[i]);
}
// should be even
if (ordersLen&1) reader.readC();
logD("reading ins pointers...");
for (int i=0; i<ds.insLen; i++) {
insPtr[i]=reader.readS();
logV("- %.2x",insPtr[i]);
}
logD("reading pat pointers...");
for (int i=0; i<patCount; i++) {
patPtr[i]=reader.readS();
logV("- %.2x",patPtr[i]);
}
if (defaultPan) {
reader.read(chanPan,16);
} else {
memset(chanPan,0,16);
}
// determine chips to use
ds.systemLen=0;
bool hasPCM=false;
bool hasFM=false;
for (int i=0; i<32; i++) {
if (!(chanSettings[i]&128)) continue;
if ((chanSettings[i]&127)>=32) continue;
if ((chanSettings[i]&127)>=16) {
hasFM=true;
} else {
hasPCM=true;
}
if (hasFM && hasPCM) break;
}
ds.systemName="PC";
if (hasPCM) {
ds.system[ds.systemLen]=DIV_SYSTEM_ES5506;
ds.systemVol[ds.systemLen]=1.0f;
ds.systemPan[ds.systemLen]=0;
ds.systemLen++;
}
if (hasFM) {
ds.system[ds.systemLen]=DIV_SYSTEM_OPL2;
ds.systemVol[ds.systemLen]=1.0f;
ds.systemPan[ds.systemLen]=0;
ds.systemLen++;
}
// load instruments/samples
for (int i=0; i<ds.insLen; i++) {
DivInstrument* ins=new DivInstrument;
if (!reader.seek(0x4c+insPtr[i]*16,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete ins;
delete[] file;
return false;
}
reader.read(magic,4);
if (memcmp(magic,"SCRS",4)==0) {
ins->type=DIV_INS_ES5506;
} else if (memcmp(magic,"SCRI",4)==0) {
ins->type=DIV_INS_OPL;
} else {
ins->type=DIV_INS_ES5506;
ds.ins.push_back(ins);
continue;
}
if (!reader.seek(insPtr[i]*16,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete ins;
delete[] file;
return false;
}
String dosName=reader.readString(13);
if (ins->type==DIV_INS_ES5506) {
unsigned int memSeg=0;
memSeg=(unsigned char)reader.readC();
memSeg|=((unsigned short)reader.readS())<<8;
logV("memSeg: %d",memSeg);
unsigned int length=reader.readI();
DivSample* s=new DivSample;
s->depth=DIV_SAMPLE_DEPTH_8BIT;
s->init(length);
s->loopStart=reader.readI();
s->loopEnd=reader.readI();
defVol[i]=reader.readC();
logV("defVol: %d",defVol[i]);
reader.readC(); // x
} else {
}
ds.ins.push_back(ins);
}
if (active) quitDispatch();
BUSY_BEGIN_SOFT;
saveLock.lock();
song.unload();
song=ds;
changeSong(0);
recalcChans();
saveLock.unlock();
BUSY_END;
if (active) {
initDispatch();
BUSY_BEGIN;
renderSamples();
reset();
BUSY_END;
}
success=true;
} catch (EndOfFileException& e) {
//logE("premature end of file!");
lastError="incomplete file";
} catch (InvalidHeaderException& e) {
//logE("invalid header!");
lastError="invalid header!";
}
return success;
}
bool DivEngine::loadFC(unsigned char* file, size_t len) {
struct InvalidHeaderException {};
bool success=false;
@ -3820,12 +4061,58 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
#define CHECK_BLOCK_VERSION(x) \
if (blockVersion>x) { \
logE("incompatible block version %d for %s!",blockVersion,blockName); \
lastError="incompatible block version"; \
delete[] file; \
return false; \
logW("incompatible block version %d for %s!",blockVersion,blockName); \
}
const int ftEffectMap[]={
-1, // none
0x0f,
0x0b,
0x0d,
0xff,
-1, // volume? not supported in Furnace yet
0x03,
0x03, // unused?
0x13,
0x14,
0x00,
0x04,
0x07,
0xe5,
0xed,
0x11,
0x01, // porta up
0x02, // porta down
0x12,
0x90, // sample offset - not supported yet
0xe1,
0xe2,
0x0a,
0xec,
0x0c,
-1, // delayed volume - not supported yet
0x11, // FDS
0x12,
0x13,
0x20, // DPCM pitch
0x22, // 5B
0x24,
0x23,
0x21,
-1, // VRC7 "custom patch port" - not supported?
-1, // VRC7 "custom patch write"
-1, // release - not supported yet
0x09, // select groove
-1, // transpose - not supported
0x10, // Namco 163
-1, // FDS vol env - not supported
-1, // FDS auto FM - not supported yet
-1, // phase reset - not supported
-1, // harmonic - not supported
};
constexpr int ftEffectMapSize=sizeof(ftEffectMap)/sizeof(int);
bool DivEngine::loadFTM(unsigned char* file, size_t len) {
SafeReader reader=SafeReader(file,len);
warnings="";
@ -3837,6 +4124,9 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
unsigned int n163Chans=0;
bool hasSequence[256][8];
unsigned char sequenceIndex[256][8];
unsigned int hilightA=4;
unsigned int hilightB=16;
double customHz=60;
memset(hasSequence,0,256*8*sizeof(bool));
memset(sequenceIndex,0,256*8);
@ -3857,6 +4147,14 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
return false;
}
for (DivSubSong* i: ds.subsong) {
i->clearData();
delete i;
}
ds.subsong.clear();
ds.linearPitch=0;
while (true) {
blockName=reader.readString(3);
if (blockName=="END") {
@ -3874,7 +4172,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize);
if (blockName=="PARAMS") {
CHECK_BLOCK_VERSION(6);
// versions 7-9 don't change anything?
CHECK_BLOCK_VERSION(9);
unsigned int oldSpeedTempo=0;
if (blockVersion<=1) {
oldSpeedTempo=reader.readI();
@ -3884,15 +4183,32 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
}
tchans=reader.readI();
unsigned int pal=reader.readI();
unsigned int customHz=reader.readI();
if (blockVersion>=7) {
// advanced Hz control
int controlType=reader.readI();
switch (controlType) {
case 1:
customHz=1000000.0/(double)reader.readI();
break;
default:
reader.readI();
break;
}
} else {
customHz=reader.readI();
}
unsigned int newVibrato=0;
bool sweepReset=false;
unsigned int speedSplitPoint=0;
if (blockVersion>=3) {
newVibrato=reader.readI();
}
if (blockVersion>=4) {
ds.subsong[0]->hilightA=reader.readI();
ds.subsong[0]->hilightB=reader.readI();
if (blockVersion>=9) {
sweepReset=reader.readI();
}
if (blockVersion>=4 && blockVersion<7) {
hilightA=reader.readI();
hilightB=reader.readI();
}
if (expansions&8) if (blockVersion>=5) { // N163 channels
n163Chans=reader.readI();
@ -3901,20 +4217,24 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
speedSplitPoint=reader.readI();
}
if (blockVersion>=8) {
int fineTuneCents=reader.readC()*100;
fineTuneCents+=reader.readC();
ds.tuning=440.0*pow(2.0,(double)fineTuneCents/1200.0);
}
logV("old speed/tempo: %d",oldSpeedTempo);
logV("expansions: %x",expansions);
logV("channels: %d",tchans);
logV("PAL: %d",pal);
logV("custom Hz: %d",customHz);
logV("custom Hz: %f",customHz);
logV("new vibrato: %d",newVibrato);
logV("N163 channels: %d",n163Chans);
logV("highlight 1: %d",ds.subsong[0]->hilightA);
logV("highlight 2: %d",ds.subsong[0]->hilightB);
logV("highlight 1: %d",hilightA);
logV("highlight 2: %d",hilightB);
logV("split point: %d",speedSplitPoint);
if (customHz!=0) {
ds.subsong[0]->hz=customHz;
}
logV("sweep reset: %d",sweepReset);
// initialize channels
int systemID=0;
@ -3959,28 +4279,46 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
CHECK_BLOCK_VERSION(1);
ds.name=reader.readString(32);
ds.author=reader.readString(32);
ds.copyright=reader.readString(32);
ds.category=reader.readString(32);
ds.systemName="NES";
} else if (blockName=="HEADER") {
CHECK_BLOCK_VERSION(3);
CHECK_BLOCK_VERSION(4);
unsigned char totalSongs=reader.readC();
logV("%d songs:",totalSongs+1);
for (int i=0; i<=totalSongs; i++) {
String subSongName=reader.readString();
ds.subsong.push_back(new DivSubSong);
ds.subsong[i]->name=subSongName;
ds.subsong[i]->hilightA=hilightA;
ds.subsong[i]->hilightB=hilightB;
if (customHz!=0) {
ds.subsong[i]->hz=customHz;
}
logV("- %s",subSongName);
}
for (unsigned int i=0; i<tchans; i++) {
// TODO: obey channel ID
unsigned char chID=reader.readC();
logV("for channel ID %d",chID);
for (int j=0; j<=totalSongs; j++) {
unsigned char effectCols=reader.readC();
if (j==0) {
ds.subsong[0]->pat[i].effectCols=effectCols+1;
}
ds.subsong[j]->pat[i].effectCols=effectCols+1;
logV("- song %d has %d effect columns",j,effectCols);
}
}
if (blockVersion>=4) {
for (int i=0; i<=totalSongs; i++) {
ds.subsong[i]->hilightA=(unsigned char)reader.readC();
ds.subsong[i]->hilightB=(unsigned char)reader.readC();
}
}
} else if (blockName=="INSTRUMENTS") {
CHECK_BLOCK_VERSION(6);
reader.seek(blockSize,SEEK_CUR);
/*
ds.insLen=reader.readI();
if (ds.insLen<0 || ds.insLen>256) {
logE("too many instruments/out of range!");
@ -4140,21 +4478,131 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
ins->name=reader.readString((unsigned int)reader.readI());
logV("- %d: %s",insIndex,ins->name);
}
*/
} else if (blockName=="SEQUENCES") {
CHECK_BLOCK_VERSION(6);
reader.seek(blockSize,SEEK_CUR);
} else if (blockName=="FRAMES") {
CHECK_BLOCK_VERSION(3);
for (size_t i=0; i<ds.subsong.size(); i++) {
DivSubSong* s=ds.subsong[i];
s->ordersLen=reader.readI();
if (blockVersion>=3) {
s->speeds.val[0]=reader.readI();
}
if (blockVersion>=2) {
s->virtualTempoN=reader.readI();
s->patLen=reader.readI();
}
int why=tchans;
if (blockVersion==1) {
why=reader.readI();
}
logV("reading %d and %d orders",tchans,s->ordersLen);
for (int j=0; j<s->ordersLen; j++) {
for (int k=0; k<why; k++) {
unsigned char o=reader.readC();
logV("%.2x",o);
s->orders.ord[k][j]=o;
}
}
}
} else if (blockName=="PATTERNS") {
CHECK_BLOCK_VERSION(5);
CHECK_BLOCK_VERSION(6);
size_t blockEnd=reader.tell()+blockSize;
if (blockVersion==1) {
int patLenOld=reader.readI();
for (DivSubSong* i: ds.subsong) {
i->patLen=patLenOld;
}
}
// so it appears .ftm doesn't keep track of how many patterns are stored in the file....
while (reader.tell()<blockEnd) {
int subs=0;
if (blockVersion>=2) subs=reader.readI();
int ch=reader.readI();
int patNum=reader.readI();
int numRows=reader.readI();
DivPattern* pat=ds.subsong[subs]->pat[ch].getPattern(patNum,true);
for (int i=0; i<numRows; i++) {
unsigned int row=0;
if (blockVersion>=2 && blockVersion<6) { // row index
row=reader.readI();
} else {
row=reader.readC();
}
unsigned char nextNote=reader.readC();
unsigned char nextOctave=reader.readC();
if (nextNote==0x0d) {
pat->data[row][0]=100;
} else if (nextNote==0x0e) {
pat->data[row][0]=101;
} else if (nextNote==0x01) {
pat->data[row][0]=12;
pat->data[row][1]=nextOctave-1;
} else if (nextNote==0) {
pat->data[row][0]=0;
} else if (nextNote<0x0d) {
pat->data[row][0]=nextNote-1;
pat->data[row][1]=nextOctave;
}
unsigned char nextIns=reader.readC();
if (nextIns<0x40) {
pat->data[row][2]=nextIns;
} else {
pat->data[row][2]=-1;
}
unsigned char nextVol=reader.readC();
if (nextVol<0x10) {
pat->data[row][3]=nextVol;
} else {
pat->data[row][3]=-1;
}
int effectCols=ds.subsong[subs]->pat[ch].effectCols;
if (blockVersion>=6) effectCols=4;
for (int j=0; j<effectCols; j++) {
unsigned char nextEffect=reader.readC();
unsigned char nextEffectVal=0;
if (nextEffect!=0 || blockVersion<6) nextEffectVal=reader.readC();
if (nextEffect==0 && nextEffectVal==0) {
pat->data[row][4+(j*2)]=-1;
pat->data[row][5+(j*2)]=-1;
} else {
if (nextEffect<ftEffectMapSize) {
pat->data[row][4+(j*2)]=ftEffectMap[nextEffect];
} else {
pat->data[row][4+(j*2)]=-1;
}
pat->data[row][5+(j*2)]=nextEffectVal;
}
}
}
}
} else if (blockName=="DPCM SAMPLES") {
CHECK_BLOCK_VERSION(1);
reader.seek(blockSize,SEEK_CUR);
} else if (blockName=="SEQUENCES_VRC6") {
// where are the 5B and FDS sequences?
CHECK_BLOCK_VERSION(6);
reader.seek(blockSize,SEEK_CUR);
} else if (blockName=="SEQUENCES_N163") {
CHECK_BLOCK_VERSION(1);
reader.seek(blockSize,SEEK_CUR);
} else if (blockName=="COMMENTS") {
CHECK_BLOCK_VERSION(1);
reader.seek(blockSize,SEEK_CUR);
} else {
logE("block %s is unknown!",blockName);
lastError="unknown block "+blockName;
@ -4169,6 +4617,27 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
return false;
}
}
addWarning("FamiTracker import is experimental!");
ds.version=DIV_VERSION_FTM;
if (active) quitDispatch();
BUSY_BEGIN_SOFT;
saveLock.lock();
song.unload();
song=ds;
changeSong(0);
recalcChans();
saveLock.unlock();
BUSY_END;
if (active) {
initDispatch();
BUSY_BEGIN;
renderSamples();
reset();
BUSY_END;
}
} catch (EndOfFileException& e) {
logE("premature end of file!");
lastError="incomplete file";

View file

@ -20,6 +20,7 @@
#include "macroInt.h"
#include "instrument.h"
#include "engine.h"
#include "../ta-log.h"
#define ADSR_LOW source.val[0]
#define ADSR_HIGH source.val[1]
@ -52,6 +53,7 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic
}
if (masked) {
had=false;
has=false;
return;
}
if (delay>0) {
@ -246,8 +248,10 @@ void DivMacroInt::setEngine(DivEngine* eng) {
}
#define ADD_MACRO(m,s) \
if (!m.masked) { \
macroList[macroListLen]=&m; \
macroSource[macroListLen++]=&s;
macroSource[macroListLen++]=&s; \
}
void DivMacroInt::init(DivInstrument* which) {
ins=which;

View file

@ -37,6 +37,10 @@ DivMacroInt* DivDispatch::getChanMacroInt(int chan) {
return NULL;
}
DivSamplePos DivDispatch::getSamplePos(int chan) {
return DivSamplePos();
}
DivDispatchOscBuffer* DivDispatch::getOscBuffer(int chan) {
return NULL;
}

View file

@ -213,6 +213,10 @@ void DivPlatformAmiga::rWrite(unsigned short addr, unsigned short val) {
//logV("%.3x = %.4x",addr,val);
regPool[addr>>1]=val;
if (!skipRegisterWrites && dumpWrites) {
addWrite(addr,val);
}
switch (addr&0x1fe) {
case 0x96: { // DMACON
if (val&32768) {
@ -400,6 +404,29 @@ void DivPlatformAmiga::tick(bool sysTick) {
chan[i].keyOn=true;
}
}
}
unsigned short dmaOff=0;
unsigned short dmaOn=0;
for (int i=0; i<4; i++) {
if (chan[i].keyOn || chan[i].keyOff) {
chWrite(i,6,1);
dmaOff|=1<<i;
}
}
if (dmaOff) rWrite(0x96,dmaOff);
for (int i=0; i<4; i++) {
double off=1.0;
if (!chan[i].useWave && chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=8363.0/(double)s->centerRate;
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
@ -409,13 +436,16 @@ void DivPlatformAmiga::tick(bool sysTick) {
chWrite(i,6,chan[i].freq);
if (chan[i].keyOn) {
rWrite(0x96,1<<i);
if (chan[i].useWave) {
rWrite(0x9a,(128<<i));
chWrite(i,0,0);
chWrite(i,2,i<<8);
chWrite(i,4,chan[i].audLen);
rWrite(0x96,0x8000|(1<<i));
if (dumpWrites) {
addWrite(0x200+i,i<<8);
addWrite(0x204+i,chan[i].audLen);
}
dmaOn|=1<<i;
} else {
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].sample);
@ -432,13 +462,21 @@ void DivPlatformAmiga::tick(bool sysTick) {
chWrite(i,0,0);
chWrite(i,2,0x400);
chWrite(i,4,1);
if (dumpWrites) {
addWrite(0x200+i,0x400);
addWrite(0x204+i,1);
}
} else {
chWrite(i,0,start>>16);
chWrite(i,2,start);
chWrite(i,4,len);
if (dumpWrites) {
addWrite(0x200+i,start);
addWrite(0x204+i,len);
}
}
rWrite(0x96,0x8000|(1<<i));
dmaOn|=1<<i;
if (s->isLoopable()) {
int loopPos=(sampleOff[chan[i].sample]+s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT))&(~1);
int loopEnd=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)-s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT))>>1;
@ -455,12 +493,12 @@ void DivPlatformAmiga::tick(bool sysTick) {
chWrite(i,0,0);
chWrite(i,2,0x400);
chWrite(i,4,1);
if (dumpWrites) {
addWrite(0x200+i,0x400);
addWrite(0x204+i,1);
}
}
}
if (chan[i].keyOff) {
rWrite(0x96,1<<i);
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
@ -468,6 +506,15 @@ void DivPlatformAmiga::tick(bool sysTick) {
}
}
if (dmaOn) rWrite(0x96,0x8000|dmaOn);
for (int i=0; i<4; i++) {
if ((dmaOn&(1<<i)) && !chan[i].useWave && dumpWrites) {
addWrite(0x200+i,(chan[i].irLocH<<16)|chan[i].irLocL);
addWrite(0x204+i,chan[i].irLen);
}
}
for (int i=0; i<4; i++) {
if (chan[i].writeVol) {
chan[i].writeVol=false;
@ -718,6 +765,18 @@ DivMacroInt* DivPlatformAmiga::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivSamplePos DivPlatformAmiga::getSamplePos(int ch) {
if (ch>=4) return DivSamplePos();
if (chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) return DivSamplePos();
int audPer=amiga.audPer[ch];
if (audPer<1) audPer=1;
return DivSamplePos(
chan[ch].sample,
amiga.dmaLoc[ch]-sampleOff[chan[ch].sample],
chipClock/audPer
);
}
void DivPlatformAmiga::notifyInsChange(int ins) {
for (int i=0; i<4; i++) {
if (chan[i].ins==ins) {

View file

@ -116,6 +116,7 @@ class DivPlatformAmiga: public DivDispatch {
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
friend class DivExportAmigaValidation;
void irq(int ch);
void rWrite(unsigned short addr, unsigned short val);
@ -136,6 +137,7 @@ class DivPlatformAmiga: public DivDispatch {
int getOutputCount();
bool keyOffAffectsArp(int ch);
DivMacroInt* getChanMacroInt(int ch);
DivSamplePos getSamplePos(int ch);
void setFlags(const DivConfig& flags);
void notifyInsChange(int ins);
void notifyWaveChange(int wave);

View file

@ -700,6 +700,15 @@ DivMacroInt* DivPlatformAY8910::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivSamplePos DivPlatformAY8910::getSamplePos(int ch) {
if (ch>=3) return DivSamplePos();
return DivSamplePos(
chan[ch].dac.sample,
chan[ch].dac.pos,
chan[ch].dac.rate
);
}
DivDispatchOscBuffer* DivPlatformAY8910::getOscBuffer(int ch) {
return oscBuf[ch];
}

View file

@ -143,6 +143,7 @@ class DivPlatformAY8910: public DivDispatch {
int getOutputCount();
bool keyOffAffectsArp(int ch);
DivMacroInt* getChanMacroInt(int ch);
DivSamplePos getSamplePos(int ch);
bool getDCOffRequired();
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);

View file

@ -696,6 +696,15 @@ DivMacroInt* DivPlatformAY8930::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivSamplePos DivPlatformAY8930::getSamplePos(int ch) {
if (ch>=3) return DivSamplePos();
return DivSamplePos(
chan[ch].dac.sample,
chan[ch].dac.pos,
chan[ch].dac.rate
);
}
DivDispatchOscBuffer* DivPlatformAY8930::getOscBuffer(int ch) {
return oscBuf[ch];
}

View file

@ -145,6 +145,7 @@ class DivPlatformAY8930: public DivDispatch {
int getOutputCount();
bool keyOffAffectsArp(int ch);
DivMacroInt* getChanMacroInt(int ch);
DivSamplePos getSamplePos(int ch);
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);

View file

@ -336,6 +336,18 @@ DivMacroInt* DivPlatformGA20::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivSamplePos DivPlatformGA20::getSamplePos(int ch) {
if (ch>=4) return DivSamplePos();
if (chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) return DivSamplePos();
if (!ga20.is_playing(ch)) return DivSamplePos();
unsigned char f=chan[ch].freq;
return DivSamplePos(
chan[ch].sample,
ga20.get_position(ch)-sampleOffGA20[chan[ch].sample],
chipClock/(4*(0x100-(int)f))
);
}
DivDispatchOscBuffer* DivPlatformGA20::getOscBuffer(int ch) {
return oscBuf[ch];
}

View file

@ -78,6 +78,7 @@ class DivPlatformGA20: public DivDispatch, public iremga20_intf {
virtual int dispatch(DivCommand c) override;
virtual void* getChanState(int chan) override;
virtual DivMacroInt* getChanMacroInt(int ch) override;
virtual DivSamplePos getSamplePos(int ch) override;
virtual DivDispatchOscBuffer* getOscBuffer(int chan) override;
virtual unsigned char* getRegisterPool() override;
virtual int getRegisterPoolSize() override;

View file

@ -172,7 +172,14 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) {
flushFirst=false;
}
OPN2_Clock(&fm,o); os[0]+=o[0]; os[1]+=o[1];
OPN2_Clock(&fm,o);
if (chipType==2) {
os[0]+=CLAMP(o[0],-8192,8191);
os[1]+=CLAMP(o[1],-8192,8191);
} else {
os[0]+=o[0];
os[1]+=o[1];
}
//OPN2_Write(&fm,0,0);
if (i==5) {
if (fm.dacen) {
@ -1202,6 +1209,17 @@ DivMacroInt* DivPlatformGenesis::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivSamplePos DivPlatformGenesis::getSamplePos(int ch) {
if (!chan[5].dacMode) return DivSamplePos();
if (ch<5) return DivSamplePos();
if (ch>5 && !softPCM) return DivSamplePos();
return DivSamplePos(
chan[ch].dacSample,
chan[ch].dacPos,
chan[ch].dacRate
);
}
DivDispatchOscBuffer* DivPlatformGenesis::getOscBuffer(int ch) {
return oscBuf[ch];
}

View file

@ -105,6 +105,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
DivSamplePos getSamplePos(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();

View file

@ -415,6 +415,16 @@ DivMacroInt* DivPlatformLynx::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivSamplePos DivPlatformLynx::getSamplePos(int ch) {
if (ch>=4) return DivSamplePos();
if (!chan[ch].pcm) return DivSamplePos();
return DivSamplePos(
chan[ch].sample,
chan[ch].samplePos,
chan[ch].sampleFreq
);
}
DivDispatchOscBuffer* DivPlatformLynx::getOscBuffer(int ch) {
return oscBuf[ch];
}

View file

@ -72,6 +72,7 @@ class DivPlatformLynx: public DivDispatch {
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
DivSamplePos getSamplePos(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();

View file

@ -19,6 +19,7 @@
#include "n163.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <math.h>
#define rRead(a,v) n163.addr_w(a); n163.data_r(v);
@ -166,6 +167,7 @@ void DivPlatformN163::updateWave(int ch, int wave, int pos, int len) {
void DivPlatformN163::updateWaveCh(int ch) {
if (ch<=chanMax) {
logV("updateWave with pos %d and len %d",chan[ch].wavePos,chan[ch].waveLen);
updateWave(ch,-1,chan[ch].wavePos,chan[ch].waveLen);
if (chan[ch].active && !isMuted[ch]) {
chan[ch].volumeChanged=true;
@ -337,15 +339,15 @@ int DivPlatformN163::dispatch(DivCommand c) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_N163);
if (chan[c.chan].insChanged) {
chan[c.chan].wave=ins->n163.wave;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
chan[c.chan].wavePos=ins->n163.wavePos;
chan[c.chan].waveLen=ins->n163.waveLen;
chan[c.chan].waveMode=ins->n163.waveMode;
chan[c.chan].ws.init(NULL,chan[c.chan].waveLen,15,false);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
chan[c.chan].waveChanged=true;
if (chan[c.chan].waveMode&0x3 || ins->ws.enabled) {
chan[c.chan].waveUpdated=true;
}
chan[c.chan].insChanged=false;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
@ -360,6 +362,7 @@ int DivPlatformN163::dispatch(DivCommand c) {
}
chan[c.chan].macroInit(ins);
chan[c.chan].ws.init(ins,chan[c.chan].waveLen,15,chan[c.chan].insChanged);
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:

View file

@ -183,6 +183,7 @@ void DivPlatformNamcoWSG::acquire(short** buf, size_t len) {
}
void DivPlatformNamcoWSG::updateWave(int ch) {
if (romMode) return;
if (devType==30) {
for (int i=0; i<32; i++) {
((namco_cus30_device*)namco)->namcos1_cus30_w(i+ch*32,chan[ch].ws.output[i]);
@ -291,9 +292,9 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
rWrite(0x1d,(chan[2].freq>>12)&15);
rWrite(0x1e,(chan[2].freq>>16)&15);
rWrite(0x05,0);
rWrite(0x0a,1);
rWrite(0x0f,2);
rWrite(0x05,romMode?(chan[0].wave&7):0);
rWrite(0x0a,romMode?(chan[1].wave&7):1);
rWrite(0x0f,romMode?(chan[2].wave&7):2);
break;
case 15:
for (int i=0; i<8; i++) {
@ -304,7 +305,7 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
}
rWrite((i<<3)+0x04,chan[i].freq&0xff);
rWrite((i<<3)+0x05,(chan[i].freq>>8)&0xff);
rWrite((i<<3)+0x06,((chan[i].freq>>16)&15)|(i<<4));
rWrite((i<<3)+0x06,((chan[i].freq>>16)&15)|((romMode?(chan[i].wave&7):i)<<4));
}
break;
case 30:
@ -496,10 +497,11 @@ void DivPlatformNamcoWSG::reset() {
if (dumpWrites) {
addWrite(0xffffffff,0);
}
// TODO: wave memory
namco->set_voices(chans);
namco->set_stereo((devType==2 || devType==30));
namco->device_start(NULL);
updateROMWaves();
}
int DivPlatformNamcoWSG::getOutputCount() {
@ -510,6 +512,27 @@ bool DivPlatformNamcoWSG::keyOffAffectsArp(int ch) {
return true;
}
void DivPlatformNamcoWSG::updateROMWaves() {
if (romMode) {
// copy wavetables
for (int i=0; i<8; i++) {
int data=0;
DivWavetable* w=parent->getWave(i);
for (int j=0; j<32; j++) {
if (w->max<1 || w->len<1) {
data=0;
} else {
data=w->data[j*w->len/32]*15/w->max;
if (data<0) data=0;
if (data>15) data=15;
}
namco->update_namco_waveform(i*32+j,data);
}
}
}
}
void DivPlatformNamcoWSG::notifyWaveChange(int wave) {
for (int i=0; i<chans; i++) {
if (chan[i].wave==wave) {
@ -517,6 +540,7 @@ void DivPlatformNamcoWSG::notifyWaveChange(int wave) {
updateWave(i);
}
}
updateROMWaves();
}
void DivPlatformNamcoWSG::notifyInsDeletion(void* ins) {
@ -552,6 +576,8 @@ void DivPlatformNamcoWSG::setFlags(const DivConfig& flags) {
oscBuf[i]->rate=rate;
}
newNoise=flags.getBool("newNoise",true);
romMode=flags.getBool("romMode",true);
if (devType==30) romMode=false;
}
void DivPlatformNamcoWSG::poke(unsigned int addr, unsigned short val) {

View file

@ -50,8 +50,10 @@ class DivPlatformNamcoWSG: public DivDispatch {
namco_audio_device* namco;
int devType, chans;
bool newNoise;
bool romMode;
unsigned char regPool[512];
void updateWave(int ch);
void updateROMWaves();
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
public:

View file

@ -505,6 +505,16 @@ DivMacroInt* DivPlatformPCE::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivSamplePos DivPlatformPCE::getSamplePos(int ch) {
if (ch>=6) return DivSamplePos();
if (!chan[ch].pcm) return DivSamplePos();
return DivSamplePos(
chan[ch].dacSample,
chan[ch].dacPos,
chan[ch].dacRate
);
}
DivDispatchOscBuffer* DivPlatformPCE::getOscBuffer(int ch) {
return oscBuf[ch];
}

View file

@ -81,6 +81,7 @@ class DivPlatformPCE: public DivDispatch {
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
DivSamplePos getSamplePos(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();

View file

@ -30,7 +30,7 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
const int depthScale=(15-outDepth);
int output=0;
for (size_t h=0; h<len; h++) {
if (!chan[0].active || isMuted) {
if (!chan[0].active) {
buf[0][h]=0;
buf[1][h]=0;
oscBuf->data[oscBuf->needle++]=0;
@ -171,7 +171,11 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
}
}
}
if (isMuted) {
output=0;
} else {
output=output*chan[0].vol*chan[0].envVol/16384;
}
oscBuf->data[oscBuf->needle++]=output;
if (outStereo) {
buf[0][h]=((output*chan[0].panL)>>(depthScale+8))<<depthScale;
@ -437,6 +441,15 @@ DivMacroInt* DivPlatformPCMDAC::getChanMacroInt(int ch) {
return &chan[0].std;
}
DivSamplePos DivPlatformPCMDAC::getSamplePos(int ch) {
if (ch>=1) return DivSamplePos();
return DivSamplePos(
chan[ch].sample,
chan[ch].audPos,
chan[ch].freq
);
}
void DivPlatformPCMDAC::notifyInsChange(int ins) {
if (chan[0].ins==ins) {
chan[0].insChanged=true;

View file

@ -79,6 +79,7 @@ class DivPlatformPCMDAC: public DivDispatch {
void muteChannel(int ch, bool mute);
int getOutputCount();
DivMacroInt* getChanMacroInt(int ch);
DivSamplePos getSamplePos(int ch);
void setFlags(const DivConfig& flags);
void notifyInsChange(int ins);
void notifyWaveChange(int wave);

View file

@ -40,7 +40,7 @@ void DivPlatformPV1000::acquire(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
short samp;
samp=d65010g031_sound_tick(&d65010g031,1);
buf[0][h]=samp<<12;
buf[0][h]=samp;
for (int i=0; i<3; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(d65010g031.square[i].out<<12);
}
@ -263,6 +263,10 @@ void DivPlatformPV1000::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
bool DivPlatformPV1000::getDCOffRequired() {
return true;
}
int DivPlatformPV1000::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
dumpWrites=false;

View file

@ -21,7 +21,7 @@
#define _PV1000_H
#include "../dispatch.h"
#include "sound/d65010g031.h"
#include "sound/d65modified.h"
#include <queue>
class DivPlatformPV1000: public DivDispatch {
@ -55,6 +55,7 @@ class DivPlatformPV1000: public DivDispatch {
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
bool getDCOffRequired();
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
~DivPlatformPV1000();

View file

@ -307,6 +307,7 @@ void DivPlatformRF5C68::forceIns() {
chan[i].insChanged=true;
chan[i].freqChanged=true;
chan[i].sample=-1;
chWrite(i,1,isMuted[i]?0:chan[i].panning);
}
}

View file

@ -382,6 +382,17 @@ DivMacroInt* DivPlatformSegaPCM::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivSamplePos DivPlatformSegaPCM::getSamplePos(int ch) {
if (ch>=16) return DivSamplePos();
if (chan[ch].pcm.sample<0 || chan[ch].pcm.sample>=parent->song.sampleLen) return DivSamplePos();
if (!pcm.is_playing(ch)) return DivSamplePos();
return DivSamplePos(
chan[ch].pcm.sample,
pcm.get_addr(ch)-sampleOffSegaPCM[chan[ch].pcm.sample],
122*(chan[ch].pcm.freq+1)
);
}
DivDispatchOscBuffer* DivPlatformSegaPCM::getOscBuffer(int ch) {
return oscBuf[ch];
}

View file

@ -87,6 +87,7 @@ class DivPlatformSegaPCM: public DivDispatch {
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
DivSamplePos getSamplePos(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();

View file

@ -681,6 +681,20 @@ DivMacroInt* DivPlatformSNES::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivSamplePos DivPlatformSNES::getSamplePos(int ch) {
if (ch>=8) return DivSamplePos();
if (!chan[ch].active) return DivSamplePos();
if (chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) return DivSamplePos();
const SPC_DSP::voice_t* v=dsp.get_voice(ch);
// TODO: fix?
if (sampleMem[v->brr_addr&0xffff]==0) return DivSamplePos();
return DivSamplePos(
chan[ch].sample,
((v->brr_addr-sampleOff[chan[ch].sample])*16/9)+v->brr_offset,
(chan[ch].freq*125)/16
);
}
DivDispatchOscBuffer* DivPlatformSNES::getOscBuffer(int ch) {
return oscBuf[ch];
}

View file

@ -97,6 +97,7 @@ class DivPlatformSNES: public DivDispatch {
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
DivSamplePos getSamplePos(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();

View file

@ -34,9 +34,31 @@ freely, subject to the following restrictions:
TODO:
- needs hardware test
ALTERED VERSION!!!
ALTERED VERSION!!!
ALTERED VERSION!!!
ALTERED VERSION!!!
ALTERED VERSION!!!
ALTERED VERSION!!!
ALTERED VERSION!!!
ALTERED VERSION!!!
ALTERED VERSION!!!
ALTERED VERSION!!!
ALTERED VERSION!!!
ALTERED VERSION!!!
ALTERED VERSION!!!
ALTERED VERSION!!!
THIS IS **NOT** NOT NOT NOT!!!! THE ORIGINAL SOFTWARE
IT ISN'T
THE MODIFICATIONS THAT WERE MADE ARE:
1. FIX VOLUMES - APPARENTLY THE SQUARES HAVE DIFFERENT VOLUMES (thanks forple)
*/
#include "d65010g031.h"
#include "d65modified.h"
#include <stdlib.h>
static int d65010g031_max(int a, int b) { return (a > b) ? a : b; }
@ -57,12 +79,20 @@ int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle)
return 0;
}
// this is the bit I altered
// THIS IS **NOT** THE ORIGINAL SOFTWARE! I am plainly marking it as such!
const int d65Volumes[3]={
3840,
5120,
8192
};
int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle)
{
int out = 0;
for (int i = 0; i < 3; i++)
{
out += d65010g031_square_tick(&d65010g031->square[i], cycle);
out += d65010g031_square_tick(&d65010g031->square[i], cycle)?d65Volumes[i]:-d65Volumes[i];
}
return out;
}

View file

@ -39,6 +39,12 @@ public:
u8 read(u32 offset);
inline void set_mute(const int ch, const bool mute) { m_channel[ch & 3].mute = mute; }
inline unsigned int get_position(const int ch) {
return m_channel[ch&3].pos;
}
inline bool is_playing(const int ch) {
return m_channel[ch&3].play;
}
// device-level overrides
void device_reset();
@ -46,6 +52,7 @@ public:
// sound stream update overrides
void sound_stream_update(short** outputs, int len);
private:
struct channel_def
{

View file

@ -134,6 +134,18 @@ uint8_t* segapcm_device::get_ram() {
return m_ram;
}
unsigned int segapcm_device::get_addr(int ch) {
uint8_t *regs = &m_ram[8*ch];
int offset = (regs[0x86] & m_bankmask) << m_bankshift;
uint32_t addr = (regs[0x85] << 8) | (regs[0x84]) | offset;
return addr;
}
bool segapcm_device::is_playing(int ch) {
uint8_t *regs = &m_ram[8*ch];
return !(regs[0x86]&1);
}
void segapcm_device::mute(int ch, bool doMute) {
m_muted[ch&15]=doMute;
}

View file

@ -34,6 +34,8 @@ public:
void write(unsigned int offset, uint8_t data);
uint8_t read(unsigned int offset);
uint8_t* get_ram();
unsigned int get_addr(int ch);
bool is_playing(int ch);
void mute(int ch, bool doMute);
// device-level overrides

View file

@ -123,6 +123,9 @@ public:
uint8_t t_envx_out;
sample_t out[2]; // Furnace addition, for per-channel oscilloscope
};
// Furnace addition, gets a voice
const voice_t* get_voice(int n);
private:
enum { brr_block_size = 9 };
@ -298,6 +301,10 @@ inline void SPC_DSP::get_voice_outputs( sample_t* outs )
}
}
inline const SPC_DSP::voice_t* SPC_DSP::get_voice(int n) {
return &m.voices[n];
}
#if !SPC_NO_COPY_STATE_FUNCS
class SPC_State_Copier {

View file

@ -19,6 +19,7 @@
#include "vic20.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <math.h>
#define rWrite(a,v) {regPool[(a)]=(v)&0xff; vic_sound_machine_store(vic,a,(v)&0xff);}
@ -79,9 +80,7 @@ void DivPlatformVIC20::calcAndWriteOutVol(int ch, int env) {
}
void DivPlatformVIC20::writeOutVol(int ch) {
if (!isMuted[ch] && chan[ch].active) {
rWrite(14,chan[ch].outVol);
}
}
void DivPlatformVIC20::tick(bool sysTick) {
@ -99,6 +98,20 @@ void DivPlatformVIC20::tick(bool sysTick) {
}
chan[i].freqChanged=true;
}
if (chan[i].std.duty.had) {
if (chan[i].onOff!=(bool)chan[i].std.duty.val) {
chan[i].onOff=(bool)chan[i].std.duty.val;
if (chan[i].active) {
if (chan[i].onOff) {
chan[i].keyOn=true;
chan[i].keyOff=false;
} else {
chan[i].keyOn=false;
chan[i].keyOff=true;
}
}
}
}
if (chan[i].std.wave.had) {
if (chan[i].wave!=chan[i].std.wave.val) {
chan[i].wave=chan[i].std.wave.val&0x0f;
@ -156,6 +169,7 @@ int DivPlatformVIC20::dispatch(DivCommand c) {
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].onOff=true;
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroInit(ins);

View file

@ -27,10 +27,12 @@
class DivPlatformVIC20: public DivDispatch {
struct Channel: public SharedChannel<int> {
int wave, waveWriteCycle;
bool onOff;
Channel():
SharedChannel<int>(15),
wave(0),
waveWriteCycle(-1) {}
waveWriteCycle(-1),
onOff(true) {}
};
Channel chan[4];
DivDispatchOscBuffer* oscBuf[4];

View file

@ -448,6 +448,16 @@ DivMacroInt* DivPlatformVRC6::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivSamplePos DivPlatformVRC6::getSamplePos(int ch) {
if (ch>=2) return DivSamplePos();
if (!chan[ch].pcm) return DivSamplePos();
return DivSamplePos(
chan[ch].dacSample,
chan[ch].dacPos,
chan[ch].dacRate
);
}
DivDispatchOscBuffer* DivPlatformVRC6::getOscBuffer(int ch) {
return oscBuf[ch];
}

View file

@ -65,6 +65,7 @@ class DivPlatformVRC6: public DivDispatch, public vrcvi_intf {
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
DivSamplePos getSamplePos(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();

View file

@ -278,7 +278,7 @@ int DivEngine::dispatchCmd(DivCommand c) {
cmdStream.push_back(c);
}
if (output) if (!skipping && output->midiOut!=NULL) {
if (output) if (!skipping && output->midiOut!=NULL && !isChannelMuted(c.chan)) {
if (output->midiOut->isDeviceOpen()) {
if (midiOutMode==DIV_MIDI_MODE_NOTE) {
int scaledVol=(chan[c.chan].volume*127)/MAX(1,chan[c.chan].volMax);
@ -305,7 +305,7 @@ int DivEngine::dispatchCmd(DivCommand c) {
chan[c.chan].curMidiNote=-1;
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].lastIns!=c.value) {
if (chan[c.chan].lastIns!=c.value && midiOutProgramChange) {
output->midiOut->send(TAMidiMessage(0xc0|(c.chan&15),c.value,0));
}
break;
@ -1385,6 +1385,15 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
}
}
if (subticks==tickMult && cmdStreamInt) {
if (!cmdStreamInt->tick()) {
cmdStreamInt->cleanup();
delete cmdStreamInt;
cmdStreamInt=NULL;
}
}
firstTick=false;
if (shallStop) {

View file

@ -236,10 +236,14 @@ String SafeReader::readString(size_t stlen) {
#endif
size_t curPos=0;
if (isEOF()) throw EndOfFileException(this, len);
bool zero=false;
while (!isEOF() && curPos<stlen) {
unsigned char c=readC();
if (c!=0) ret.push_back(c);
if (c==0) {
zero=true;
}
if (!zero) ret.push_back(c);
curPos++;
}
return ret;

View file

@ -144,6 +144,16 @@ int SafeWriter::writeI(int val) {
return write(&val,4);
}
int SafeWriter::writeI_BE(int val) {
unsigned char bytes[4] {
(unsigned char)((val>>24)&0xff),
(unsigned char)((val>>16)&0xff),
(unsigned char)((val>>8)&0xff),
(unsigned char)(val&0xff)
};
return write(bytes,4);
}
int SafeWriter::writeL(int64_t val) {
return write(&val,8);
}

View file

@ -789,7 +789,7 @@ void DivEngine::registerSystems() {
{"S1", "S2", "S3"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
{DIV_INS_AY, DIV_INS_AY, DIV_INS_AY},
{},
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
{},
ayPostEffectHandlerMap
);
@ -870,7 +870,7 @@ void DivEngine::registerSystems() {
{"S1", "S2", "S3"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
{DIV_INS_AY8930, DIV_INS_AY8930, DIV_INS_AY8930},
{},
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
{},
ay8930PostEffectHandlerMap
);

View file

@ -583,8 +583,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
logD("writing stream command %x:%x with stream ID %d",write.addr,write.val,streamID);
switch (write.addr&0xff) {
case 0: // play sample
if (write.val<song.sampleLen) {
if (playingSample[streamID]!=write.val) {
if (write.val<(unsigned int)song.sampleLen) {
if (playingSample[streamID]!=(int)write.val) {
pendingFreq[streamID]=write.val;
} else {
DivSample* sample=song.sample[write.val];

View file

@ -20,6 +20,7 @@
#include "waveSynth.h"
#include "engine.h"
#include "instrument.h"
#include "../ta-log.h"
bool DivWaveSynth::activeChanged() {
if (activeChangedB) {
@ -211,6 +212,7 @@ void DivWaveSynth::setWidth(int val) {
void DivWaveSynth::changeWave1(int num) {
DivWavetable* w1=e->getWave(num);
logV("changeWave1 (%d)",width);
if (width<1) return;
for (int i=0; i<width; i++) {
if (w1->max<1 || w1->len<1) {

View file

@ -455,12 +455,14 @@ void FurnaceGUI::drawInsList(bool asChild) {
if (ImGui::Selectable(name.c_str(),(i==-1)?(curIns<0 || curIns>=e->song.insLen):(curIns==i))) {
curIns=i;
wavePreviewInit=true;
updateFMPreview=true;
}
if (wantScrollList && curIns==i) ImGui::SetScrollHereY();
if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) {
nextWindow=GUI_WINDOW_PATTERN;
curIns=i;
wavePreviewInit=true;
updateFMPreview=true;
}
if (ImGui::IsItemHovered() && i>=0 && !mobileUI) {
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
@ -474,6 +476,7 @@ void FurnaceGUI::drawInsList(bool asChild) {
if (i>=0) {
if (ImGui::BeginPopupContextItem("InsRightMenu")) {
curIns=i;
updateFMPreview=true;
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);

View file

@ -58,6 +58,8 @@ void FurnaceGUI::drawDebug() {
ImGui::SameLine();
if (ImGui::Button("Pattern Advance")) e->haltWhen(DIV_HALT_PATTERN);
if (ImGui::Button("Play Command Stream")) openFileDialog(GUI_FILE_CMDSTREAM_OPEN);
if (ImGui::Button("Panic")) e->syncReset();
ImGui::SameLine();
if (ImGui::Button("Abort")) {
@ -519,6 +521,14 @@ void FurnaceGUI::drawDebug() {
ImGui::InputFloat("maxRr",&maxRr);
ImGui::TreePop();
}
if (ImGui::TreeNode("FM Preview")) {
float asFloat[FM_PREVIEW_SIZE];
for (int i=0; i<FM_PREVIEW_SIZE; i++) {
asFloat[i]=(float)fmPreview[i]/8192.0f;
}
ImGui::PlotLines("##DebugFMPreview",asFloat,FM_PREVIEW_SIZE,0,"Preview",-1.0,1.0,ImVec2(300.0f*dpiScale,150.0f*dpiScale));
ImGui::TreePop();
}
if (ImGui::TreeNode("User Interface")) {
if (ImGui::Button("Inspect")) {
inspectorOpen=!inspectorOpen;

View file

@ -127,6 +127,7 @@ void FurnaceGUI::doAction(int what) {
}
wavePreviewInit=true;
wantScrollList=true;
updateFMPreview=true;
break;
case GUI_ACTION_INS_DOWN:
if (++curIns>=(int)e->song.ins.size()) {
@ -134,6 +135,7 @@ void FurnaceGUI::doAction(int what) {
}
wavePreviewInit=true;
wantScrollList=true;
updateFMPreview=true;
break;
case GUI_ACTION_STEP_UP:
if (++editStep>64) editStep=64;
@ -593,6 +595,7 @@ void FurnaceGUI::doAction(int what) {
wantScrollList=true;
MARK_MODIFIED;
wavePreviewInit=true;
updateFMPreview=true;
}
break;
case GUI_ACTION_INS_LIST_DUPLICATE:
@ -606,6 +609,7 @@ void FurnaceGUI::doAction(int what) {
wantScrollList=true;
MARK_MODIFIED;
wavePreviewInit=true;
updateFMPreview=true;
}
}
break;
@ -653,11 +657,13 @@ void FurnaceGUI::doAction(int what) {
if (--curIns<0) curIns=0;
wantScrollList=true;
wavePreviewInit=true;
updateFMPreview=true;
break;
case GUI_ACTION_INS_LIST_DOWN:
if (++curIns>=(int)e->song.ins.size()) curIns=((int)e->song.ins.size())-1;
wantScrollList=true;
wavePreviewInit=true;
updateFMPreview=true;
break;
case GUI_ACTION_WAVE_LIST_ADD:
@ -1366,6 +1372,7 @@ void FurnaceGUI::doAction(int what) {
nextWindow=GUI_WINDOW_INS_EDIT;
MARK_MODIFIED;
wavePreviewInit=true;
updateFMPreview=true;
}
break;
}

View file

@ -19,7 +19,9 @@
#define _USE_MATH_DEFINES
#include "gui.h"
#include "../fileutils.h"
#include "IconsFontAwesome4.h"
#include "misc/cpp/imgui_stdlib.h"
#include <fmt/printf.h>
// 0: all directions
@ -514,11 +516,31 @@ void FurnaceGUI::drawMobileControls() {
openFileDialog(GUI_FILE_EXPORT_VGM);
}
ImGui::Button("CmdStream");
if (ImGui::Button("CmdStream")) {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY);
}
ImGui::SameLine();
if (ImGui::Button("CmdStream Text")) {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM);
}
ImGui::Separator();
if (ImGui::BeginTabBar("MobileSong")) {
if (ImGui::BeginTabItem("Song Info")) {
drawSongInfo(true);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Subsongs")) {
drawSubSongs(true);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Speed")) {
drawSpeed(true);
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
break;
}
case GUI_SCENE_CHANNELS:
@ -575,6 +597,40 @@ void FurnaceGUI::drawMobileControls() {
if (ImGui::Button("Switch to Desktop Mode")) {
toggleMobileUI(!mobileUI);
}
int numAmiga=0;
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++;
}
if (numAmiga) {
ImGui::Text(
"this is NOT ROM export! only use for making sure the\n"
"Furnace Amiga emulator is working properly by\n"
"comparing it with real Amiga output."
);
ImGui::Text("Directory");
ImGui::SameLine();
ImGui::InputText("##AVDPath",&workingDirROMExport);
if (ImGui::Button("Bake Data")) {
std::vector<DivROMExportOutput> out=e->buildROM(DIV_ROM_AMIGA_VALIDATION);
if (workingDirROMExport.size()>0) {
if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR;
}
for (DivROMExportOutput& i: out) {
String path=workingDirROMExport+i.name;
FILE* outFile=ps_fopen(path.c_str(),"wb");
if (outFile!=NULL) {
fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile);
fclose(outFile);
}
i.data->finish();
delete i.data;
}
showError(fmt::sprintf("Done! Baked %d files.",(int)out.size()));
}
}
break;
}
}

View file

@ -1005,6 +1005,13 @@ void FurnaceGUI::doUndo() {
break;
}
if (curOrder>=e->curSubSong->ordersLen) {
curOrder=e->curSubSong->ordersLen-1;
oldOrder=curOrder;
oldOrder1=curOrder;
e->setOrder(curOrder);
}
undoHist.pop_back();
}
@ -1058,5 +1065,12 @@ void FurnaceGUI::doRedo() {
break;
}
if (curOrder>=e->curSubSong->ordersLen) {
curOrder=e->curSubSong->ordersLen-1;
oldOrder=curOrder;
oldOrder1=curOrder;
e->setOrder(curOrder);
}
redoHist.pop_back();
}

86
src/gui/fmPreview.cpp Normal file
View file

@ -0,0 +1,86 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _USE_MATH_DEFINES
#include "gui.h"
#include "../../extern/opn/ym3438.h"
#define FM_WRITE(addr,val) \
OPN2_Write((ym3438_t*)fmPreviewOPN,0,(addr)); \
do { \
OPN2_Clock((ym3438_t*)fmPreviewOPN,out); \
} while (((ym3438_t*)fmPreviewOPN)->write_busy); \
OPN2_Write((ym3438_t*)fmPreviewOPN,1,(val)); \
do { \
OPN2_Clock((ym3438_t*)fmPreviewOPN,out); \
} while (((ym3438_t*)fmPreviewOPN)->write_busy); \
const unsigned char dtTableFMP[8]={
7,6,5,0,1,2,3,4
};
void FurnaceGUI::renderFMPreview(const DivInstrumentFM& params, int pos) {
if (fmPreviewOPN==NULL) {
fmPreviewOPN=new ym3438_t;
}
short out[2];
int aOut=0;
bool mult0=false;
if (pos==0) {
OPN2_Reset((ym3438_t*)fmPreviewOPN);
OPN2_SetChipType((ym3438_t*)fmPreviewOPN,ym3438_mode_opn);
// set params
for (int i=0; i<4; i++) {
if ((params.op[i].mult&15)==0) {
mult0=true;
break;
}
}
for (int i=0; i<4; i++) {
const DivInstrumentFM::Operator& op=params.op[i];
unsigned short baseAddr=i*4;
FM_WRITE(baseAddr+0x40,op.tl);
FM_WRITE(baseAddr+0x30,(op.mult&15)|(dtTableFMP[op.dt&7]<<4));
FM_WRITE(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
FM_WRITE(baseAddr+0x60,(op.dr&31)|(op.am<<7));
FM_WRITE(baseAddr+0x70,op.d2r&31);
FM_WRITE(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
FM_WRITE(baseAddr+0x90,op.ssgEnv&15);
}
FM_WRITE(0xb0,(params.alg&7)|((params.fb&7)<<3));
FM_WRITE(0xb4,0xc0|(params.fms&7)|((params.ams&3)<<4));
FM_WRITE(0xa4,mult0?0x1c:0x14); // frequency
FM_WRITE(0xa0,0);
FM_WRITE(0x28,0xf0); // key on
}
// render
for (int i=0; i<FM_PREVIEW_SIZE; i++) {
aOut=0;
for (int j=0; j<24; j++) {
OPN2_Clock((ym3438_t*)fmPreviewOPN,out);
}
aOut+=((ym3438_t*)fmPreviewOPN)->ch_out[0];
if (aOut<-32768) aOut=-32768;
if (aOut>32767) aOut=32767;
fmPreview[i]=aOut;
}
}

View file

@ -1175,6 +1175,7 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) {
if (settings.absorbInsInput) {
curIns=pat->data[cursor.y][target];
wavePreviewInit=true;
updateFMPreview=true;
}
makeUndo(GUI_UNDO_PATTERN_EDIT);
if (direct) {
@ -1736,9 +1737,18 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir();
hasOpened=fileDialog->openSave(
"Export Command Stream",
{"text file", "*.txt",
"binary file", "*.bin"},
"text file{.txt},binary file{.bin}",
{"text file", "*.txt"},
"text file{.txt}",
workingDirROMExport,
dpiScale
);
break;
case GUI_FILE_EXPORT_CMDSTREAM_BINARY:
if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir();
hasOpened=fileDialog->openSave(
"Export Command Stream",
{"binary file", "*.bin"},
"binary file{.bin}",
workingDirROMExport,
dpiScale
);
@ -1839,6 +1849,17 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
dpiScale
);
break;
case GUI_FILE_CMDSTREAM_OPEN:
if (!dirExists(workingDirROM)) workingDirROM=getHomeDir();
hasOpened=fileDialog->openLoad(
"Play Command Stream",
{"command stream", "*.bin",
"all files", "*"},
"command stream{.bin},.*",
workingDirROM,
dpiScale
);
break;
case GUI_FILE_TEST_OPEN:
if (!dirExists(workingDirTest)) workingDirTest=getHomeDir();
hasOpened=fileDialog->openLoad(
@ -2093,6 +2114,64 @@ void FurnaceGUI::pushRecentFile(String path) {
}
}
int FurnaceGUI::loadStream(String path) {
if (!path.empty()) {
logI("loading stream...");
FILE* f=ps_fopen(path.c_str(),"rb");
if (f==NULL) {
perror("error");
lastError=strerror(errno);
return 1;
}
if (fseek(f,0,SEEK_END)<0) {
perror("size error");
lastError=fmt::sprintf("on seek: %s",strerror(errno));
fclose(f);
return 1;
}
ssize_t len=ftell(f);
if (len==(SIZE_MAX>>1)) {
perror("could not get file length");
lastError=fmt::sprintf("on pre tell: %s",strerror(errno));
fclose(f);
return 1;
}
if (len<1) {
if (len==0) {
logE("that file is empty!");
lastError="file is empty";
} else {
perror("tell error");
lastError=fmt::sprintf("on tell: %s",strerror(errno));
}
fclose(f);
return 1;
}
if (fseek(f,0,SEEK_SET)<0) {
perror("size error");
lastError=fmt::sprintf("on get size: %s",strerror(errno));
fclose(f);
return 1;
}
unsigned char* file=new unsigned char[len];
if (fread(file,1,(size_t)len,f)!=(size_t)len) {
perror("read error");
lastError=fmt::sprintf("on read: %s",strerror(errno));
fclose(f);
delete[] file;
return 1;
}
fclose(f);
if (!e->playStream(file,(size_t)len)) {
lastError=e->getLastError();
logE("could not open file!");
return 1;
}
}
return 0;
}
void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut);
displayExporting=true;
@ -3404,6 +3483,7 @@ bool FurnaceGUI::loop() {
curIns=msg.data[0];
if (curIns>=(int)e->song.ins.size()) curIns=e->song.ins.size()-1;
wavePreviewInit=true;
updateFMPreview=true;
}
break;
case TA_MIDI_CONTROL:
@ -3682,6 +3762,41 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
}
int numAmiga=0;
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++;
}
if (numAmiga && settings.iCannotWait) {
if (ImGui::BeginMenu("export Amiga validation data...")) {
ImGui::Text(
"this is NOT ROM export! only use for making sure the\n"
"Furnace Amiga emulator is working properly by\n"
"comparing it with real Amiga output."
);
ImGui::Text("Directory");
ImGui::SameLine();
ImGui::InputText("##AVDPath",&workingDirROMExport);
if (ImGui::Button("Bake Data")) {
std::vector<DivROMExportOutput> out=e->buildROM(DIV_ROM_AMIGA_VALIDATION);
if (workingDirROMExport.size()>0) {
if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR;
}
for (DivROMExportOutput& i: out) {
String path=workingDirROMExport+i.name;
FILE* outFile=ps_fopen(path.c_str(),"wb");
if (outFile!=NULL) {
fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile);
fclose(outFile);
}
i.data->finish();
delete i.data;
}
showError(fmt::sprintf("Done! Baked %d files.",(int)out.size()));
ImGui::CloseCurrentPopup();
}
ImGui::EndMenu();
}
}
if (ImGui::BeginMenu("export command stream...")) {
ImGui::Text(
"this option exports a text or binary file which\n"
@ -3690,7 +3805,10 @@ bool FurnaceGUI::loop() {
"technical/development use only!"
);
if (ImGui::Button("export")) {
if (ImGui::Button("export (binary)")) {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY);
}
if (ImGui::Button("export (text)")) {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM);
}
ImGui::EndMenu();
@ -4123,6 +4241,7 @@ bool FurnaceGUI::loop() {
} else {
curIns=prevIns;
wavePreviewInit=true;
updateFMPreview=true;
}
prevIns=-3;
}
@ -4167,6 +4286,7 @@ bool FurnaceGUI::loop() {
break;
case GUI_FILE_EXPORT_ROM:
case GUI_FILE_EXPORT_CMDSTREAM:
case GUI_FILE_EXPORT_CMDSTREAM_BINARY:
workingDirROMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_LOAD_MAIN_FONT:
@ -4190,6 +4310,9 @@ bool FurnaceGUI::loop() {
case GUI_FILE_MU5_ROM_OPEN:
workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_CMDSTREAM_OPEN:
workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_TEST_OPEN:
case GUI_FILE_TEST_OPEN_MULTI:
case GUI_FILE_TEST_SAVE:
@ -4254,9 +4377,10 @@ bool FurnaceGUI::loop() {
checkExtension(".zsm");
}
if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM) {
// we can't tell whether the user chose .txt or .bin in the system file picker
const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="text file")?".txt":".bin";
checkExtensionDual(".txt",".bin",fallbackExt);
checkExtension(".txt");
}
if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM_BINARY) {
checkExtension(".bin");
}
if (curFileDialog==GUI_FILE_EXPORT_COLORS) {
checkExtension(".cfgc");
@ -4598,15 +4722,9 @@ bool FurnaceGUI::loop() {
case GUI_FILE_EXPORT_ROM:
showError("Coming soon!");
break;
case GUI_FILE_EXPORT_CMDSTREAM: {
String lowerCase=fileName;
for (char& i: lowerCase) {
if (i>='A' && i<='Z') i+='a'-'A';
}
bool isBinary=true;
if ((lowerCase.size()<4 || lowerCase.rfind(".bin")!=lowerCase.size()-4)) {
isBinary=false;
}
case GUI_FILE_EXPORT_CMDSTREAM:
case GUI_FILE_EXPORT_CMDSTREAM_BINARY: {
bool isBinary=(curFileDialog==GUI_FILE_EXPORT_CMDSTREAM_BINARY);
SafeWriter* w=e->saveCommand(isBinary);
if (w!=NULL) {
@ -4660,6 +4778,11 @@ bool FurnaceGUI::loop() {
case GUI_FILE_MU5_ROM_OPEN:
settings.mu5Path=copyOfName;
break;
case GUI_FILE_CMDSTREAM_OPEN:
if (loadStream(copyOfName)>0) {
showError(fmt::sprintf("Error while loading file! (%s)",lastError));
}
break;
case GUI_FILE_TEST_OPEN:
showWarning(fmt::sprintf("You opened: %s",copyOfName),GUI_WARN_GENERIC);
break;
@ -5120,6 +5243,7 @@ bool FurnaceGUI::loop() {
if (i!=DIV_INS_AMIGA) e->song.ins[curIns]->amiga.useSample=true;
nextWindow=GUI_WINDOW_INS_EDIT;
wavePreviewInit=true;
updateFMPreview=true;
}
MARK_MODIFIED;
}
@ -5435,6 +5559,7 @@ bool FurnaceGUI::init() {
waveSigned=e->getConfBool("waveSigned",false);
waveGenVisible=e->getConfBool("waveGenVisible",false);
waveEditStyle=e->getConfInt("waveEditStyle",0);
extraChannelButtons=e->getConfInt("extraChannelButtons",0);
lockLayout=e->getConfBool("lockLayout",false);
#ifdef IS_MOBILE
fullScreen=true;
@ -5849,6 +5974,7 @@ void FurnaceGUI::commitState() {
e->setConf("waveSigned",waveSigned);
e->setConf("waveGenVisible",waveGenVisible);
e->setConf("waveEditStyle",waveEditStyle);
e->setConf("extraChannelButtons",extraChannelButtons);
e->setConf("lockLayout",lockLayout);
e->setConf("fullScreen",fullScreen);
e->setConf("mobileUI",mobileUI);
@ -5979,6 +6105,10 @@ FurnaceGUI::FurnaceGUI():
mobileEditButtonPos(0.7f,0.7f),
mobileEditButtonSize(60.0f,60.0f),
curSysSection(NULL),
updateFMPreview(true),
fmPreviewOn(false),
fmPreviewPaused(false),
fmPreviewOPN(NULL),
pendingRawSampleDepth(8),
pendingRawSampleChannels(1),
pendingRawSampleUnsigned(false),

View file

@ -68,6 +68,8 @@
#define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str()
#define FM_PREVIEW_SIZE 512
// TODO:
// - add colors for FM envelope and waveform
// - maybe add "alternate" color for FM modulators/carriers (a bit difficult)
@ -368,6 +370,7 @@ enum FurnaceGUIFileDialogs {
GUI_FILE_EXPORT_VGM,
GUI_FILE_EXPORT_ZSM,
GUI_FILE_EXPORT_CMDSTREAM,
GUI_FILE_EXPORT_CMDSTREAM_BINARY,
GUI_FILE_EXPORT_ROM,
GUI_FILE_LOAD_MAIN_FONT,
GUI_FILE_LOAD_PAT_FONT,
@ -380,6 +383,7 @@ enum FurnaceGUIFileDialogs {
GUI_FILE_YRW801_ROM_OPEN,
GUI_FILE_TG100_ROM_OPEN,
GUI_FILE_MU5_ROM_OPEN,
GUI_FILE_CMDSTREAM_OPEN,
GUI_FILE_TEST_OPEN,
GUI_FILE_TEST_OPEN_MULTI,
@ -1192,6 +1196,9 @@ class FurnaceGUI {
ImVec2 mobileEditButtonPos, mobileEditButtonSize;
const int* curSysSection;
DivInstrumentFM opllPreview;
short fmPreview[FM_PREVIEW_SIZE];
bool updateFMPreview, fmPreviewOn, fmPreviewPaused;
void* fmPreviewOPN;
String pendingRawSample;
int pendingRawSampleDepth, pendingRawSampleChannels;
@ -1357,6 +1364,7 @@ class FurnaceGUI {
int channelFont;
int channelTextCenter;
int midiOutClock;
int midiOutProgramChange;
int midiOutMode;
int maxRecentFile;
int centerPattern;
@ -1369,6 +1377,8 @@ class FurnaceGUI {
int oneDigitEffects;
int disableFadeIn;
int alwaysPlayIntro;
int iCannotWait;
int orderButtonPos;
unsigned int maxUndoSteps;
String mainFontPath;
String patFontPath;
@ -1497,6 +1507,7 @@ class FurnaceGUI {
channelFont(1),
channelTextCenter(1),
midiOutClock(0),
midiOutProgramChange(0),
midiOutMode(1),
maxRecentFile(10),
centerPattern(0),
@ -1509,6 +1520,8 @@ class FurnaceGUI {
oneDigitEffects(0),
disableFadeIn(0),
alwaysPlayIntro(0),
iCannotWait(0),
orderButtonPos(2),
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),
@ -1885,6 +1898,8 @@ class FurnaceGUI {
void drawGBEnv(unsigned char vol, unsigned char len, unsigned char sLen, bool dir, const ImVec2& size);
bool drawSysConf(int chan, DivSystem type, DivConfig& flags, bool modifyOnChange);
void kvsConfig(DivInstrument* ins);
void drawFMPreview(const ImVec2& size);
void renderFMPreview(const DivInstrumentFM& params, int pos=0);
// these ones offer ctrl-wheel fine value changes.
bool CWSliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format=NULL, ImGuiSliderFlags flags=0);
@ -1919,6 +1934,8 @@ class FurnaceGUI {
void drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float availableWidth, int index);
void drawMacros(std::vector<FurnaceGUIMacroDesc>& macros, FurnaceGUIMacroEditState& state);
void drawOrderButtons();
void actualWaveList();
void actualSampleList();
@ -1966,7 +1983,7 @@ class FurnaceGUI {
void drawNewSong();
void drawLog();
void drawEffectList();
void drawSubSongs();
void drawSubSongs(bool asChild=false);
void drawFindReplace();
void drawSpoiler();
void drawClock();
@ -2058,6 +2075,7 @@ class FurnaceGUI {
void openFileDialog(FurnaceGUIFileDialogs type);
int save(String path, int dmfVersion);
int load(String path);
int loadStream(String path);
void pushRecentFile(String path);
void exportAudio(String path, DivAudioExportModes mode);

View file

@ -271,9 +271,9 @@ const char* x1_010EnvBits[8]={
"enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL
};
const char* n163UpdateBits[8]={
/*const char* n163UpdateBits[8]={
"now", "every waveform changed", NULL
};
};*/
const char* suControlBits[5]={
"ring mod", "low pass", "high pass", "band pass", NULL
@ -1259,9 +1259,10 @@ void FurnaceGUI::drawGBEnv(unsigned char vol, unsigned char len, unsigned char s
#define P(x) if (x) { \
MARK_MODIFIED; \
e->notifyInsChange(curIns); \
updateFMPreview=true; \
}
#define PARAMETER MARK_MODIFIED; e->notifyInsChange(curIns);
#define PARAMETER MARK_MODIFIED; e->notifyInsChange(curIns); updateFMPreview=true;
String genericGuide(float value) {
return fmt::sprintf("%d",(int)value);
@ -1279,9 +1280,25 @@ inline bool enBit30(const int val) {
void FurnaceGUI::kvsConfig(DivInstrument* ins) {
if (ins->type==DIV_INS_FM && fmPreviewOn) {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("(click to configure TL scaling)");
ImGui::SetTooltip("left click to restart\nmiddle click to pause\nright click to see algorithm");
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
updateFMPreview=true;
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) {
fmPreviewPaused=!fmPreviewPaused;
}
} else {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("left click to configure TL scaling\nright click to see FM preview");
}
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right) && ins->type==DIV_INS_FM) {
fmPreviewOn=!fmPreviewOn;
}
if (!fmPreviewOn || ins->type!=DIV_INS_FM) {
int opCount=4;
if (ins->type==DIV_INS_OPLL) opCount=2;
if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2;
@ -1315,6 +1332,15 @@ void FurnaceGUI::kvsConfig(DivInstrument* ins) {
}
ImGui::EndPopup();
}
}
}
void FurnaceGUI::drawFMPreview(const ImVec2& size) {
float asFloat[FM_PREVIEW_SIZE];
for (int i=0; i<FM_PREVIEW_SIZE; i++) {
asFloat[i]=(float)fmPreview[i]/8192.0f;
}
ImGui::PlotLines("##DebugFMPreview",asFloat,FM_PREVIEW_SIZE,0,NULL,-1.0,1.0,size);
}
void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float availableWidth, int index) {
@ -2125,6 +2151,7 @@ void FurnaceGUI::drawInsEdit() {
if (ImGui::Selectable(name.c_str(),curIns==(int)i)) {
curIns=i;
wavePreviewInit=true;
updateFMPreview=true;
}
}
ImGui::EndCombo();
@ -2148,6 +2175,10 @@ void FurnaceGUI::drawInsEdit() {
}
} else {
DivInstrument* ins=e->song.ins[curIns];
if (updateFMPreview) {
renderFMPreview(ins->fm);
updateFMPreview=false;
}
if (settings.insEditColorize) {
pushAccentColors(uiColors[GUI_COLOR_INSTR_STD+ins->type],uiColors[GUI_COLOR_INSTR_STD+ins->type],uiColors[GUI_COLOR_INSTR_STD+ins->type],ImVec4(0.0f,0.0f,0.0f,0.0f));
}
@ -2167,6 +2198,7 @@ void FurnaceGUI::drawInsEdit() {
curIns=i;
ins=e->song.ins[curIns];
wavePreviewInit=true;
updateFMPreview=true;
}
}
ImGui::EndCombo();
@ -2306,7 +2338,15 @@ void FurnaceGUI::drawInsEdit() {
P(CWSliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); rightClickable
P(CWSliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); rightClickable
ImGui::TableNextColumn();
if (ins->type==DIV_INS_FM && fmPreviewOn) {
drawFMPreview(ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
if (!fmPreviewPaused) {
renderFMPreview(ins->fm,1);
WAKE_UP;
}
} else {
drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
}
kvsConfig(ins);
break;
case DIV_INS_OPZ:
@ -4262,9 +4302,18 @@ void FurnaceGUI::drawInsEdit() {
}
popToggleColors();
P(ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff));
P(ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs));
P(ImGui::Checkbox("Absolute Duty Macro",&ins->c64.dutyIsAbs));
if (ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff)) {
ins->std.volMacro.vZoom=-1;
PARAMETER;
}
if (ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs)) {
ins->std.volMacro.vZoom=-1;
PARAMETER;
}
if (ImGui::Checkbox("Absolute Duty Macro",&ins->c64.dutyIsAbs)) {
ins->std.dutyMacro.vZoom=-1;
PARAMETER;
}
P(ImGui::Checkbox("Don't test/gate before new note",&ins->c64.noTest));
ImGui::EndTabItem();
}
@ -5116,7 +5165,7 @@ void FurnaceGUI::drawInsEdit() {
dutyMax=ins->amiga.useSample?0:255;
}
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_PET || ins->type==DIV_INS_SEGAPCM ||
ins->type==DIV_INS_FM || ins->type==DIV_INS_K007232 || ins->type==DIV_INS_GA20 ||
ins->type==DIV_INS_SM8521 || ins->type==DIV_INS_PV1000) {
dutyMax=0;
@ -5133,6 +5182,10 @@ void FurnaceGUI::drawInsEdit() {
dutyLabel="Noise";
dutyMax=1;
}
if (ins->type==DIV_INS_VIC) {
dutyLabel="On/Off";
dutyMax=1;
}
if (ins->type==DIV_INS_SWAN) {
dutyLabel="Noise";
dutyMax=ins->amiga.useSample?0:8;
@ -5149,10 +5202,10 @@ void FurnaceGUI::drawInsEdit() {
dutyLabel="Duty";
dutyMax=63;
}
if (ins->type==DIV_INS_N163) {
/*if (ins->type==DIV_INS_N163) {
dutyLabel="Waveform pos.";
dutyMax=255;
}
}*/
if (ins->type==DIV_INS_VRC6) {
dutyLabel="Duty";
dutyMax=ins->amiga.useSample?0:7;
@ -5432,8 +5485,8 @@ void FurnaceGUI::drawInsEdit() {
macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,saaEnvBits));
} else if (ins->type==DIV_INS_X1_010 && !ins->amiga.useSample) {
macroList.push_back(FurnaceGUIMacroDesc("Envelope Mode",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,x1_010EnvBits));
} else if (ins->type==DIV_INS_N163) {
macroList.push_back(FurnaceGUIMacroDesc("Wave Length",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
/*} else if (ins->type==DIV_INS_N163) {
macroList.push_back(FurnaceGUIMacroDesc("Wave Length",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));*/
} else if (ins->type==DIV_INS_FDS) {
macroList.push_back(FurnaceGUIMacroDesc("Mod Depth",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} else if (ins->type==DIV_INS_SU) {
@ -5455,8 +5508,8 @@ void FurnaceGUI::drawInsEdit() {
if (ex2Max>0) {
if (ins->type==DIV_INS_C64) {
macroList.push_back(FurnaceGUIMacroDesc("Resonance",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER]));
} else if (ins->type==DIV_INS_N163) {
macroList.push_back(FurnaceGUIMacroDesc("Wave Update",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,n163UpdateBits));
/*} else if (ins->type==DIV_INS_N163) {
macroList.push_back(FurnaceGUIMacroDesc("Wave Update",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,n163UpdateBits));*/
} else if (ins->type==DIV_INS_FDS) {
macroList.push_back(FurnaceGUIMacroDesc("Mod Speed",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} else if (ins->type==DIV_INS_SU) {

View file

@ -91,6 +91,122 @@ void FurnaceGUI::drawMobileOrderSel() {
ImGui::End();
}
#define NEXT_BUTTON \
if (++buttonColumn>=buttonColumns) { \
buttonColumn=0; \
} else { \
ImGui::SameLine(); \
}
void FurnaceGUI::drawOrderButtons() {
int buttonColumns=(settings.orderButtonPos==0)?8:1;
int buttonColumn=0;
while (buttonColumns<8 && ((8/buttonColumns)*ImGui::GetFrameHeightWithSpacing())>ImGui::GetContentRegionAvail().y) {
buttonColumns++;
}
if (ImGui::Button(ICON_FA_PLUS)) { handleUnimportant
// add order row (new)
doAction(GUI_ACTION_ORDERS_ADD);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Add new order");
}
NEXT_BUTTON;
if (ImGui::Button(ICON_FA_MINUS)) { handleUnimportant
// remove this order row
doAction(GUI_ACTION_ORDERS_REMOVE);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Remove order");
}
NEXT_BUTTON;
if (ImGui::Button(ICON_FA_FILES_O)) { handleUnimportant
// duplicate order row
doAction(GUI_ACTION_ORDERS_DUPLICATE);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
doAction(GUI_ACTION_ORDERS_DEEP_CLONE);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Duplicate order (right-click to deep clone)");
}
NEXT_BUTTON;
if (ImGui::Button(ICON_FA_ANGLE_UP)) { handleUnimportant
// move order row up
doAction(GUI_ACTION_ORDERS_MOVE_UP);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Move order up");
}
NEXT_BUTTON;
if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { handleUnimportant
// move order row down
doAction(GUI_ACTION_ORDERS_MOVE_DOWN);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Move order down");
}
NEXT_BUTTON;
if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { handleUnimportant
// duplicate order row at end
doAction(GUI_ACTION_ORDERS_DUPLICATE_END);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
doAction(GUI_ACTION_ORDERS_DEEP_CLONE_END);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Duplicate order at end of song (right-click to deep clone)");
}
NEXT_BUTTON;
if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { handleUnimportant
// whether to change one or all orders in a row
changeAllOrders=!changeAllOrders;
}
if (ImGui::IsItemHovered()) {
if (changeAllOrders) {
ImGui::SetTooltip("Order change mode: entire row");
} else {
ImGui::SetTooltip("Order change mode: one");
}
}
NEXT_BUTTON;
const char* orderEditModeLabel="?##OrderEditMode";
if (orderEditMode==3) {
orderEditModeLabel=ICON_FA_ARROWS_V "##OrderEditMode";
} else if (orderEditMode==2) {
orderEditModeLabel=ICON_FA_ARROWS_H "##OrderEditMode";
} else if (orderEditMode==1) {
orderEditModeLabel=ICON_FA_I_CURSOR "##OrderEditMode";
} else {
orderEditModeLabel=ICON_FA_MOUSE_POINTER "##OrderEditMode";
}
if (ImGui::Button(orderEditModeLabel)) { handleUnimportant
orderEditMode++;
if (orderEditMode>3) orderEditMode=0;
curNibble=false;
}
if (ImGui::IsItemHovered()) {
if (orderEditMode==3) {
ImGui::SetTooltip("Order edit mode: Select and type (scroll vertically)");
} else if (orderEditMode==2) {
ImGui::SetTooltip("Order edit mode: Select and type (scroll horizontally)");
} else if (orderEditMode==1) {
ImGui::SetTooltip("Order edit mode: Select and type (don't scroll)");
} else {
ImGui::SetTooltip("Order edit mode: Click to change");
}
}
}
void FurnaceGUI::drawOrders() {
static char selID[4096];
if (nextWindow==GUI_WINDOW_ORDERS) {
@ -107,12 +223,34 @@ void FurnaceGUI::drawOrders() {
} else {
//ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH));
}
if (ImGui::Begin("Orders",&ordersOpen,globalWinFlags)) {
float regionX=ImGui::GetContentRegionAvail().x;
if (ImGui::Begin("Orders",&ordersOpen,globalWinFlags|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) {
if (ImGui::BeginTable("OrdColumn",(settings.orderButtonPos==0)?1:2,ImGuiTableFlags_BordersInnerV)) {
if (settings.orderButtonPos==2) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed);
} else if (settings.orderButtonPos==1) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch);
}
ImVec2 prevSpacing=ImGui::GetStyle().ItemSpacing;
if (settings.orderButtonPos!=0) {
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(1.0f*dpiScale,1.0f*dpiScale));
ImGui::Columns(2,NULL,false);
ImGui::SetColumnWidth(-1,regionX-24.0f*dpiScale);
}
ImGui::TableNextRow();
if (settings.orderButtonPos<2) {
ImGui::TableNextColumn();
drawOrderButtons();
}
if (settings.orderButtonPos==0) {
ImGui::TableNextRow();
}
ImGui::TableNextColumn();
int displayChans=0;
for (int i=0; i<e->getTotalChannelCount(); i++) {
if (e->curSubSong->chanShow[i]) displayChans++;
@ -256,94 +394,19 @@ void FurnaceGUI::drawOrders() {
ImGui::PopFont();
ImGui::EndTable();
}
ImGui::NextColumn();
if (ImGui::Button(ICON_FA_PLUS)) { handleUnimportant
// add order row (new)
doAction(GUI_ACTION_ORDERS_ADD);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Add new order");
}
if (ImGui::Button(ICON_FA_MINUS)) { handleUnimportant
// remove this order row
doAction(GUI_ACTION_ORDERS_REMOVE);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Remove order");
}
if (ImGui::Button(ICON_FA_FILES_O)) { handleUnimportant
// duplicate order row
doAction(GUI_ACTION_ORDERS_DUPLICATE);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
doAction(GUI_ACTION_ORDERS_DEEP_CLONE);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Duplicate order (right-click to deep clone)");
}
if (ImGui::Button(ICON_FA_ANGLE_UP)) { handleUnimportant
// move order row up
doAction(GUI_ACTION_ORDERS_MOVE_UP);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Move order up");
}
if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { handleUnimportant
// move order row down
doAction(GUI_ACTION_ORDERS_MOVE_DOWN);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Move order down");
}
if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { handleUnimportant
// duplicate order row at end
doAction(GUI_ACTION_ORDERS_DUPLICATE_END);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
doAction(GUI_ACTION_ORDERS_DEEP_CLONE_END);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Duplicate order at end of song (right-click to deep clone)");
}
if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { handleUnimportant
// whether to change one or all orders in a row
changeAllOrders=!changeAllOrders;
}
if (ImGui::IsItemHovered()) {
if (changeAllOrders) {
ImGui::SetTooltip("Order change mode: entire row");
} else {
ImGui::SetTooltip("Order change mode: one");
}
}
const char* orderEditModeLabel="?##OrderEditMode";
if (orderEditMode==3) {
orderEditModeLabel=ICON_FA_ARROWS_V "##OrderEditMode";
} else if (orderEditMode==2) {
orderEditModeLabel=ICON_FA_ARROWS_H "##OrderEditMode";
} else if (orderEditMode==1) {
orderEditModeLabel=ICON_FA_I_CURSOR "##OrderEditMode";
} else {
orderEditModeLabel=ICON_FA_MOUSE_POINTER "##OrderEditMode";
}
if (ImGui::Button(orderEditModeLabel)) { handleUnimportant
orderEditMode++;
if (orderEditMode>3) orderEditMode=0;
curNibble=false;
}
if (ImGui::IsItemHovered()) {
if (orderEditMode==3) {
ImGui::SetTooltip("Order edit mode: Select and type (scroll vertically)");
} else if (orderEditMode==2) {
ImGui::SetTooltip("Order edit mode: Select and type (scroll horizontally)");
} else if (orderEditMode==1) {
ImGui::SetTooltip("Order edit mode: Select and type (don't scroll)");
} else {
ImGui::SetTooltip("Order edit mode: Click to change");
}
if (settings.orderButtonPos==2) {
ImGui::TableNextColumn();
drawOrderButtons();
}
if (settings.orderButtonPos!=0) {
ImGui::PopStyleVar();
}
ImGui::EndTable();
}
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_ORDERS;
oldOrder1=e->getOrder();
ImGui::End();

View file

@ -1367,6 +1367,7 @@ void FurnaceGUI::drawSampleEdit() {
}
}
dl->PushClipRect(rectMin,rectMax);
if (e->isPreviewingSample()) {
if (!statusBar2.empty()) {
statusBar2+=" | ";
@ -1380,7 +1381,6 @@ void FurnaceGUI::drawSampleEdit() {
end^=start;
start^=end;
}
ImDrawList* dl=ImGui::GetWindowDrawList();
ImVec2 p1=rectMin;
p1.x+=(e->getSamplePreviewPos()-samplePos)/sampleZoom;
ImVec4 posColor=uiColors[GUI_COLOR_SAMPLE_NEEDLE];
@ -1390,8 +1390,8 @@ void FurnaceGUI::drawSampleEdit() {
posTrail2.w=0.0f;
float trailDistance=(e->getSamplePreviewRate()/100.0f)/sampleZoom;
if (p1.x<rectMin.x) p1.x=rectMin.x;
if (p1.x>rectMax.x) p1.x=rectMax.x;
//if (p1.x<rectMin.x) p1.x=rectMin.x;
//if (p1.x>rectMax.x) p1.x=rectMax.x;
ImVec2 p2=p1;
p2.y=rectMax.y;
@ -1407,6 +1407,46 @@ void FurnaceGUI::drawSampleEdit() {
dl->AddLine(p1,p2,ImGui::GetColorU32(posColor));
}
if (e->isRunning()) {
for (int i=0; i<e->getTotalChannelCount(); i++) {
DivSamplePos chanPos=e->getSamplePos(i);
if (chanPos.sample!=curSample) continue;
int start=sampleSelStart;
int end=sampleSelEnd;
if (start>end) {
start^=end;
end^=start;
start^=end;
}
ImVec2 p1=rectMin;
p1.x+=(chanPos.pos-samplePos)/sampleZoom;
ImVec4 posColor=uiColors[GUI_COLOR_SAMPLE_NEEDLE_PLAYING];
ImVec4 posTrail1=posColor;
ImVec4 posTrail2=posColor;
posTrail1.w*=0.5f;
posTrail2.w=0.0f;
float trailDistance=((float)chanPos.freq/100.0f)/sampleZoom;
//if (p1.x<rectMin.x) p1.x=rectMin.x;
//if (p1.x>rectMax.x) p1.x=rectMax.x;
ImVec2 p2=p1;
p2.y=rectMax.y;
dl->AddRectFilledMultiColor(
ImVec2(p1.x-trailDistance,p1.y),
p2,
ImGui::GetColorU32(posTrail2),
ImGui::GetColorU32(posTrail1),
ImGui::GetColorU32(posTrail1),
ImGui::GetColorU32(posTrail2)
);
dl->AddLine(p1,p2,ImGui::GetColorU32(posColor));
}
}
dl->PopClipRect();
if (drawSelection) {
int start=sampleSelStart;
int end=sampleSelEnd;
@ -1415,7 +1455,6 @@ void FurnaceGUI::drawSampleEdit() {
end^=start;
start^=end;
}
ImDrawList* dl=ImGui::GetWindowDrawList();
ImVec2 p1=rectMin;
p1.x+=(start-samplePos)/sampleZoom;

View file

@ -1149,6 +1149,11 @@ void FurnaceGUI::drawSettings() {
settings.midiOutClock=midiOutClockB;
}
bool midiOutProgramChangeB=settings.midiOutProgramChange;
if (ImGui::Checkbox("Send Program Change",&midiOutProgramChangeB)) {
settings.midiOutProgramChange=midiOutProgramChangeB;
}
ImGui::TreePop();
}
}
@ -1447,6 +1452,17 @@ void FurnaceGUI::drawSettings() {
settings.controlLayout=3;
}
ImGui::Text("Position of buttons in Orders:");
if (ImGui::RadioButton("Top##obp0",settings.orderButtonPos==0)) {
settings.orderButtonPos=0;
}
if (ImGui::RadioButton("Left##obp1",settings.orderButtonPos==1)) {
settings.orderButtonPos=1;
}
if (ImGui::RadioButton("Right##obp2",settings.orderButtonPos==2)) {
settings.orderButtonPos=2;
}
ImGui::Text("FM parameter editor layout:");
if (ImGui::RadioButton("Modern##fml0",settings.fmLayout==0)) {
settings.fmLayout=0;
@ -2416,6 +2432,7 @@ void FurnaceGUI::drawSettings() {
// "Debug" - toggles mobile UI
// "Nice Amiga cover of the song!" - enables hidden systems (YMU759/SoundUnit/Dummy)
// "42 63" - enables all instrument types
// "????" - enables stuff
if (ImGui::BeginTabItem("Cheat Codes")) {
ImVec2 settingsViewSize=ImGui::GetContentRegionAvail();
settingsViewSize.y-=ImGui::GetFrameHeight()+ImGui::GetStyle().WindowPadding.y;
@ -2607,6 +2624,7 @@ void FurnaceGUI::syncSettings() {
settings.channelTextCenter=e->getConfInt("channelTextCenter",1);
settings.maxRecentFile=e->getConfInt("maxRecentFile",10);
settings.midiOutClock=e->getConfInt("midiOutClock",0);
settings.midiOutProgramChange=e->getConfInt("midiOutProgramChange",0);
settings.midiOutMode=e->getConfInt("midiOutMode",1);
settings.centerPattern=e->getConfInt("centerPattern",0);
settings.ordersCursor=e->getConfInt("ordersCursor",1);
@ -2619,6 +2637,8 @@ void FurnaceGUI::syncSettings() {
settings.disableFadeIn=e->getConfInt("disableFadeIn",0);
settings.alwaysPlayIntro=e->getConfInt("alwaysPlayIntro",0);
settings.cursorFollowsOrder=e->getConfInt("cursorFollowsOrder",1);
settings.iCannotWait=e->getConfInt("iCannotWait",0);
settings.orderButtonPos=e->getConfInt("orderButtonPos",2);
clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.patFontSize,2,96);
@ -2724,6 +2744,7 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.channelTextCenter,0,1);
clampSetting(settings.maxRecentFile,0,30);
clampSetting(settings.midiOutClock,0,1);
clampSetting(settings.midiOutProgramChange,0,1);
clampSetting(settings.midiOutMode,0,2);
clampSetting(settings.centerPattern,0,1);
clampSetting(settings.ordersCursor,0,1);
@ -2734,6 +2755,8 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.disableFadeIn,0,1);
clampSetting(settings.alwaysPlayIntro,0,3);
clampSetting(settings.cursorFollowsOrder,0,1);
clampSetting(settings.iCannotWait,0,1);
clampSetting(settings.orderButtonPos,0,2);
if (settings.exportLoops<0.0) settings.exportLoops=0.0;
if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0;
@ -2932,6 +2955,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("channelTextCenter",settings.channelTextCenter);
e->setConf("maxRecentFile",settings.maxRecentFile);
e->setConf("midiOutClock",settings.midiOutClock);
e->setConf("midiOutProgramChange",settings.midiOutProgramChange);
e->setConf("midiOutMode",settings.midiOutMode);
e->setConf("centerPattern",settings.centerPattern);
e->setConf("ordersCursor",settings.ordersCursor);
@ -2943,7 +2967,9 @@ void FurnaceGUI::commitSettings() {
e->setConf("oneDigitEffects",settings.oneDigitEffects);
e->setConf("disableFadeIn",settings.disableFadeIn);
e->setConf("alwaysPlayIntro",settings.alwaysPlayIntro);
e->setConf("cursorFollowsOrder", settings.cursorFollowsOrder);
e->setConf("cursorFollowsOrder",settings.cursorFollowsOrder);
e->setConf("iCannotWait",settings.iCannotWait);
e->setConf("orderButtonPos",settings.orderButtonPos);
// colors
for (int i=0; i<GUI_COLOR_MAX; i++) {

View file

@ -4,15 +4,18 @@
#include "misc/cpp/imgui_stdlib.h"
#include "intConst.h"
void FurnaceGUI::drawSubSongs() {
void FurnaceGUI::drawSubSongs(bool asChild) {
if (nextWindow==GUI_WINDOW_SUBSONGS) {
subSongsOpen=true;
ImGui::SetNextWindowFocus();
nextWindow=GUI_WINDOW_NOTHING;
}
if (!subSongsOpen) return;
if (!subSongsOpen && !asChild) return;
if (!asChild) {
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH));
if (ImGui::Begin("Subsongs",&subSongsOpen,globalWinFlags)) {
}
bool began=asChild?ImGui::BeginChild("Subsongs"):ImGui::Begin("Subsongs",&subSongsOpen,globalWinFlags);
if (began) {
char id[1024];
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing()*2.0f-ImGui::GetStyle().ItemSpacing.x);
if (e->curSubSong->name.empty()) {
@ -107,12 +110,16 @@ void FurnaceGUI::drawSubSongs() {
MARK_MODIFIED;
}
if (ImGui::GetContentRegionAvail().y>(10.0f*dpiScale)) {
if (!asChild && ImGui::GetContentRegionAvail().y>(10.0f*dpiScale)) {
if (ImGui::InputTextMultiline("##SubSongNotes",&e->curSubSong->notes,ImGui::GetContentRegionAvail(),ImGuiInputTextFlags_UndoRedo)) {
MARK_MODIFIED;
}
}
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS;
if (!asChild && ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS;
if (asChild) {
ImGui::EndChild();
} else {
ImGui::End();
}
}

View file

@ -621,6 +621,9 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
chipType=3;
altered=true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("note: AY-3-8914 is not supported by the VGM format!");
}
}
ImGui::BeginDisabled(type==DIV_SYSTEM_AY8910 && chipType==2);
if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) {
@ -1738,6 +1741,27 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
}
break;
}
case DIV_SYSTEM_NAMCO:
case DIV_SYSTEM_NAMCO_15XX: {
bool romMode=flags.getBool("romMode",false);
ImGui::Text("Waveform storage mode:");
if (ImGui::RadioButton("RAM",!romMode)) {
romMode=false;
altered=true;
}
if (ImGui::RadioButton("ROM (up to 8 waves)",romMode)) {
romMode=true;
altered=true;
}
if (altered) {
e->lockSave([&]() {
flags.set("romMode",romMode);
});
}
break;
}
case DIV_SYSTEM_NAMCO_CUS30: {
bool newNoise=flags.getBool("newNoise",true);
@ -1772,11 +1796,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
case DIV_SYSTEM_VBOY:
case DIV_SYSTEM_GA20:
case DIV_SYSTEM_PV1000:
case DIV_SYSTEM_NAMCO:
case DIV_SYSTEM_NAMCO_15XX:
ImGui::Text("nothing to configure");
break;
case DIV_SYSTEM_VERA:
break;
case DIV_SYSTEM_YMU759:
supportsCustomRate=false;
ImGui::Text("nothing to configure");