mirror of
https://github.com/tildearrow/furnace.git
synced 2024-12-04 02:07:27 +00:00
Merge branch 'master' of https://github.com/tildearrow/furnace into x1_010_bank
This commit is contained in:
commit
2a881c9f66
84 changed files with 3061 additions and 381 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
BIN
demos/misc/TerminalZone_SM8521.fur
Normal file
BIN
demos/misc/TerminalZone_SM8521.fur
Normal file
Binary file not shown.
BIN
demos/misc/empty_PV-1000.fur
Normal file
BIN
demos/misc/empty_PV-1000.fur
Normal file
Binary file not shown.
BIN
demos/snes/ManbowSMW.fur
Normal file
BIN
demos/snes/ManbowSMW.fur
Normal file
Binary file not shown.
41
extern/igfd/ImGuiFileDialog.cpp
vendored
41
extern/igfd/ImGuiFileDialog.cpp
vendored
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
1
extern/igfd/ImGuiFileDialog.h
vendored
1
extern/igfd/ImGuiFileDialog.h
vendored
|
@ -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
|
||||
|
|
BIN
instruments/FM/bass/Finger Bass.fui
Normal file
BIN
instruments/FM/bass/Finger Bass.fui
Normal file
Binary file not shown.
BIN
instruments/FM/bass/Slap Bass.fui
Normal file
BIN
instruments/FM/bass/Slap Bass.fui
Normal file
Binary file not shown.
BIN
instruments/FM/drums/4-7 Snare.fui
Normal file
BIN
instruments/FM/drums/4-7 Snare.fui
Normal file
Binary file not shown.
BIN
instruments/FM/drums/Single Clap.fui
Normal file
BIN
instruments/FM/drums/Single Clap.fui
Normal file
Binary file not shown.
BIN
instruments/FM/strings/Slow Strings.fui
Normal file
BIN
instruments/FM/strings/Slow Strings.fui
Normal file
Binary file not shown.
4
src/asm/68k/amigatest/Makefile
Normal file
4
src/asm/68k/amigatest/Makefile
Normal 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
|
56
src/asm/68k/amigatest/README.md
Normal file
56
src/asm/68k/amigatest/README.md
Normal 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
|
386
src/asm/68k/amigatest/player.s
Normal file
386
src/asm/68k/amigatest/player.s
Normal 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
301
src/engine/cmdStream.cpp
Normal file
|
@ -0,0 +1,301 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "cmdStream.h"
|
||||
#include "engine.h"
|
||||
#include "../ta-log.h"
|
||||
|
||||
void DivCSPlayer::cleanup() {
|
||||
delete b;
|
||||
}
|
||||
|
||||
bool DivCSPlayer::tick() {
|
||||
bool ticked=false;
|
||||
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||
bool sendVolume=false;
|
||||
if (chan[i].readPos==0) continue;
|
||||
|
||||
ticked=true;
|
||||
|
||||
chan[i].waitTicks--;
|
||||
while (chan[i].waitTicks<=0) {
|
||||
stream.seek(chan[i].readPos,SEEK_SET);
|
||||
unsigned char next=stream.readC();
|
||||
unsigned char command=0;
|
||||
|
||||
if (next<0xb3) { // note
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,next-60));
|
||||
} else if (next>=0xd0 && next<=0xdf) {
|
||||
command=fastCmds[next&15];
|
||||
} else if (next>=0xe0 && next<=0xef) { // preset delay
|
||||
chan[i].waitTicks=fastDelays[next&15];
|
||||
} else switch (next) {
|
||||
case 0xb4: // note on null
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL));
|
||||
break;
|
||||
case 0xb5: // note off
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i));
|
||||
break;
|
||||
case 0xb6: // note off env
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF_ENV,i));
|
||||
break;
|
||||
case 0xb7: // env release
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_ENV_RELEASE,i));
|
||||
break;
|
||||
case 0xb8: case 0xbe: case 0xc0: case 0xc2:
|
||||
case 0xc3: case 0xc4: case 0xc5: case 0xc6:
|
||||
case 0xc7: case 0xc8: case 0xc9: case 0xca:
|
||||
command=next-0xb4;
|
||||
break;
|
||||
case 0xf7:
|
||||
command=stream.readC();
|
||||
break;
|
||||
case 0xf8:
|
||||
logE("TODO: CALL");
|
||||
break;
|
||||
case 0xf9:
|
||||
logE("TODO: RET");
|
||||
break;
|
||||
case 0xfa:
|
||||
logE("TODO: JMP");
|
||||
break;
|
||||
case 0xfb:
|
||||
logE("TODO: RATE");
|
||||
break;
|
||||
case 0xfc:
|
||||
chan[i].waitTicks=(unsigned short)stream.readS();
|
||||
break;
|
||||
case 0xfd:
|
||||
chan[i].waitTicks=(unsigned char)stream.readC();
|
||||
break;
|
||||
case 0xfe:
|
||||
chan[i].waitTicks=1;
|
||||
break;
|
||||
case 0xff:
|
||||
chan[i].readPos=0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (chan[i].readPos==0) break;
|
||||
|
||||
if (command) {
|
||||
int arg0=0;
|
||||
int arg1=0;
|
||||
switch (command) {
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
case DIV_CMD_HINT_VIBRATO_RANGE:
|
||||
case DIV_CMD_HINT_VIBRATO_SHAPE:
|
||||
case DIV_CMD_HINT_PITCH:
|
||||
case DIV_CMD_HINT_VOLUME:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
case DIV_CMD_HINT_VIBRATO:
|
||||
case DIV_CMD_HINT_ARPEGGIO:
|
||||
case DIV_CMD_HINT_PORTA:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
arg1=(unsigned char)stream.readC();
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
arg1=(arg0&0x40)?1:0;
|
||||
arg0=(arg0&0x80)?1:0;
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
arg0=(short)stream.readS();
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
case DIV_CMD_SAMPLE_FREQ:
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
case DIV_CMD_SAMPLE_DIR:
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
case DIV_CMD_FM_LFO:
|
||||
case DIV_CMD_FM_LFO_WAVE:
|
||||
case DIV_CMD_FM_FB:
|
||||
case DIV_CMD_FM_EXTCH:
|
||||
case DIV_CMD_FM_AM_DEPTH:
|
||||
case DIV_CMD_FM_PM_DEPTH:
|
||||
case DIV_CMD_STD_NOISE_FREQ:
|
||||
case DIV_CMD_STD_NOISE_MODE:
|
||||
case DIV_CMD_WAVE:
|
||||
case DIV_CMD_GB_SWEEP_TIME:
|
||||
case DIV_CMD_GB_SWEEP_DIR:
|
||||
case DIV_CMD_PCE_LFO_MODE:
|
||||
case DIV_CMD_PCE_LFO_SPEED:
|
||||
case DIV_CMD_NES_DMC:
|
||||
case DIV_CMD_C64_CUTOFF:
|
||||
case DIV_CMD_C64_RESONANCE:
|
||||
case DIV_CMD_C64_FILTER_MODE:
|
||||
case DIV_CMD_C64_RESET_TIME:
|
||||
case DIV_CMD_C64_RESET_MASK:
|
||||
case DIV_CMD_C64_FILTER_RESET:
|
||||
case DIV_CMD_C64_DUTY_RESET:
|
||||
case DIV_CMD_C64_EXTENDED:
|
||||
case DIV_CMD_AY_ENVELOPE_SET:
|
||||
case DIV_CMD_AY_ENVELOPE_LOW:
|
||||
case DIV_CMD_AY_ENVELOPE_HIGH:
|
||||
case DIV_CMD_AY_ENVELOPE_SLIDE:
|
||||
case DIV_CMD_AY_NOISE_MASK_AND:
|
||||
case DIV_CMD_AY_NOISE_MASK_OR:
|
||||
case DIV_CMD_AY_AUTO_ENVELOPE:
|
||||
case DIV_CMD_FDS_MOD_DEPTH:
|
||||
case DIV_CMD_FDS_MOD_HIGH:
|
||||
case DIV_CMD_FDS_MOD_LOW:
|
||||
case DIV_CMD_FDS_MOD_POS:
|
||||
case DIV_CMD_FDS_MOD_WAVE:
|
||||
case DIV_CMD_SAA_ENVELOPE:
|
||||
case DIV_CMD_AMIGA_FILTER:
|
||||
case DIV_CMD_AMIGA_AM:
|
||||
case DIV_CMD_AMIGA_PM:
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
case DIV_CMD_MACRO_ON:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
break;
|
||||
case DIV_CMD_FM_TL:
|
||||
case DIV_CMD_FM_AM:
|
||||
case DIV_CMD_FM_AR:
|
||||
case DIV_CMD_FM_DR:
|
||||
case DIV_CMD_FM_SL:
|
||||
case DIV_CMD_FM_D2R:
|
||||
case DIV_CMD_FM_RR:
|
||||
case DIV_CMD_FM_DT:
|
||||
case DIV_CMD_FM_DT2:
|
||||
case DIV_CMD_FM_RS:
|
||||
case DIV_CMD_FM_KSR:
|
||||
case DIV_CMD_FM_VIB:
|
||||
case DIV_CMD_FM_SUS:
|
||||
case DIV_CMD_FM_WS:
|
||||
case DIV_CMD_FM_SSG:
|
||||
case DIV_CMD_FM_REV:
|
||||
case DIV_CMD_FM_EG_SHIFT:
|
||||
case DIV_CMD_FM_MULT:
|
||||
case DIV_CMD_FM_FINE:
|
||||
case DIV_CMD_AY_IO_WRITE:
|
||||
case DIV_CMD_AY_AUTO_PWM:
|
||||
case DIV_CMD_SURROUND_PANNING:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
arg1=(unsigned char)stream.readC();
|
||||
break;
|
||||
case DIV_CMD_C64_FINE_DUTY:
|
||||
case DIV_CMD_C64_FINE_CUTOFF:
|
||||
case DIV_CMD_LYNX_LFSR_LOAD:
|
||||
arg0=(unsigned short)stream.readS();
|
||||
break;
|
||||
case DIV_CMD_FM_FIXFREQ:
|
||||
arg0=(unsigned short)stream.readS();
|
||||
arg1=arg0&0x7ff;
|
||||
arg0>>=12;
|
||||
break;
|
||||
case DIV_CMD_NES_SWEEP:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
arg1=arg0&0x77;
|
||||
arg0=(arg0&8)?1:0;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case DIV_CMD_HINT_VOLUME:
|
||||
chan[i].volume=arg0<<8;
|
||||
sendVolume=true;
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
chan[i].volSpeed=arg0;
|
||||
break;
|
||||
default: // dispatch it
|
||||
e->dispatchCmd(DivCommand((DivDispatchCmds)command,i,arg0,arg1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
chan[i].readPos=stream.tell();
|
||||
}
|
||||
|
||||
if (sendVolume || chan[i].volSpeed!=0) {
|
||||
chan[i].volume+=chan[i].volSpeed;
|
||||
if (chan[i].volume<0) {
|
||||
chan[i].volume=0;
|
||||
}
|
||||
if (chan[i].volume>chan[i].volMax) {
|
||||
chan[i].volume=chan[i].volMax;
|
||||
}
|
||||
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
}
|
||||
}
|
||||
|
||||
return ticked;
|
||||
}
|
||||
|
||||
bool DivCSPlayer::init() {
|
||||
unsigned char magic[4];
|
||||
stream.seek(0,SEEK_SET);
|
||||
stream.read(magic,4);
|
||||
|
||||
if (memcmp(magic,"FCS",4)!=0) return false;
|
||||
|
||||
unsigned int chans=stream.readI();
|
||||
|
||||
for (unsigned int i=0; i<chans; i++) {
|
||||
if (i>=DIV_MAX_CHANS) {
|
||||
stream.readI();
|
||||
continue;
|
||||
}
|
||||
if ((int)i>=e->getTotalChannelCount()) {
|
||||
stream.readI();
|
||||
continue;
|
||||
}
|
||||
chan[i].readPos=stream.readI();
|
||||
}
|
||||
|
||||
stream.read(fastDelays,16);
|
||||
stream.read(fastCmds,16);
|
||||
|
||||
// initialize state
|
||||
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||
chan[i].volMax=(e->getDispatch(e->dispatchOfChan[i])->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,e->dispatchChanOfChan[i]))<<8)|0xff;
|
||||
chan[i].volume=chan[i].volMax;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// DivEngine
|
||||
|
||||
bool DivEngine::playStream(unsigned char* f, size_t length) {
|
||||
BUSY_BEGIN;
|
||||
cmdStreamInt=new DivCSPlayer(this,f,length);
|
||||
if (!cmdStreamInt->init()) {
|
||||
logE("not a command stream!");
|
||||
lastError="not a command stream";
|
||||
delete[] f;
|
||||
delete cmdStreamInt;
|
||||
cmdStreamInt=NULL;
|
||||
BUSY_END;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!playing) {
|
||||
reset();
|
||||
freelance=true;
|
||||
playing=true;
|
||||
}
|
||||
BUSY_END;
|
||||
return true;
|
||||
}
|
59
src/engine/cmdStream.h
Normal file
59
src/engine/cmdStream.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef _CMD_STREAM_H
|
||||
#define _CMD_STREAM_H
|
||||
|
||||
#include "defines.h"
|
||||
#include "safeReader.h"
|
||||
|
||||
class DivEngine;
|
||||
|
||||
struct DivCSChannelState {
|
||||
unsigned int readPos;
|
||||
int waitTicks;
|
||||
|
||||
int volume, volMax, volSpeed;
|
||||
|
||||
DivCSChannelState():
|
||||
readPos(0),
|
||||
waitTicks(0),
|
||||
volume(0x7f00),
|
||||
volMax(0),
|
||||
volSpeed(0) {}
|
||||
};
|
||||
|
||||
class DivCSPlayer {
|
||||
DivEngine* e;
|
||||
unsigned char* b;
|
||||
SafeReader stream;
|
||||
DivCSChannelState chan[DIV_MAX_CHANS];
|
||||
unsigned char fastDelays[16];
|
||||
unsigned char fastCmds[16];
|
||||
public:
|
||||
void cleanup();
|
||||
bool tick();
|
||||
bool init();
|
||||
DivCSPlayer(DivEngine* en, unsigned char* buf, size_t len):
|
||||
e(en),
|
||||
b(buf),
|
||||
stream(buf,len) {}
|
||||
};
|
||||
|
||||
#endif
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
37
src/engine/export.cpp
Normal 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
75
src/engine/export.h
Normal 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
|
26
src/engine/export/abstract.cpp
Normal file
26
src/engine/export/abstract.cpp
Normal 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>();
|
||||
}
|
276
src/engine/export/amigaValidation.cpp
Normal file
276
src/engine/export/amigaValidation.cpp
Normal 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;
|
||||
}
|
26
src/engine/export/amigaValidation.h
Normal file
26
src/engine/export/amigaValidation.h
Normal 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() {}
|
||||
};
|
|
@ -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";
|
||||
|
|
|
@ -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) \
|
||||
macroList[macroListLen]=&m; \
|
||||
macroSource[macroListLen++]=&s;
|
||||
if (!m.masked) { \
|
||||
macroList[macroListLen]=&m; \
|
||||
macroSource[macroListLen++]=&s; \
|
||||
}
|
||||
|
||||
void DivMacroInt::init(DivInstrument* which) {
|
||||
ins=which;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,19 +493,28 @@ 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;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
|||
}
|
||||
}
|
||||
}
|
||||
output=output*chan[0].vol*chan[0].envVol/16384;
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -39,12 +39,19 @@ 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();
|
||||
|
||||
// sound stream update overrides
|
||||
void sound_stream_update(short** outputs, int len);
|
||||
|
||||
|
||||
private:
|
||||
struct channel_def
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
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);
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
drawSongInfo(true);
|
||||
if (ImGui::BeginTabBar("MobileSong")) {
|
||||
if (ImGui::BeginTabItem("Song Info")) {
|
||||
drawSongInfo(true);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Subsongs")) {
|
||||
drawSubSongs(true);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Speed")) {
|
||||
drawSpeed(true);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
86
src/gui/fmPreview.cpp
Normal 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;
|
||||
}
|
||||
}
|
182
src/gui/gui.cpp
182
src/gui/gui.cpp
|
@ -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:
|
||||
|
@ -3668,18 +3748,53 @@ bool FurnaceGUI::loop() {
|
|||
}
|
||||
if (numZSMCompat > 0) {
|
||||
if (ImGui::BeginMenu("export ZSM...")) {
|
||||
ImGui::Text("Commander X16 Zsound Music File");
|
||||
if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) {
|
||||
if (zsmExportTickRate<1) zsmExportTickRate=1;
|
||||
if (zsmExportTickRate>44100) zsmExportTickRate=44100;
|
||||
ImGui::Text("Commander X16 Zsound Music File");
|
||||
if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) {
|
||||
if (zsmExportTickRate<1) zsmExportTickRate=1;
|
||||
if (zsmExportTickRate>44100) zsmExportTickRate=44100;
|
||||
}
|
||||
ImGui::Checkbox("loop",&zsmExportLoop);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Begin Export")) {
|
||||
openFileDialog(GUI_FILE_EXPORT_ZSM);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
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;
|
||||
}
|
||||
ImGui::Checkbox("loop",&zsmExportLoop);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Begin Export")) {
|
||||
openFileDialog(GUI_FILE_EXPORT_ZSM);
|
||||
ImGui::CloseCurrentPopup();
|
||||
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;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
showError(fmt::sprintf("Done! Baked %d files.",(int)out.size()));
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginMenu("export command stream...")) {
|
||||
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,42 +1280,67 @@ inline bool enBit30(const int val) {
|
|||
|
||||
|
||||
void FurnaceGUI::kvsConfig(DivInstrument* ins) {
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("(click to configure TL scaling)");
|
||||
}
|
||||
int opCount=4;
|
||||
if (ins->type==DIV_INS_OPLL) opCount=2;
|
||||
if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2;
|
||||
if (ImGui::BeginPopupContextItem("IKVSOpt",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||
ImGui::Text("operator level changes with volume?");
|
||||
if (ImGui::BeginTable("KVSTable",4,ImGuiTableFlags_BordersInner)) {
|
||||
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
|
||||
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed);
|
||||
ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch);
|
||||
for (int i=0; i<4; i++) {
|
||||
int o=(opCount==4)?orderedOps[i]:i;
|
||||
if (!(i&1)) ImGui::TableNextRow();
|
||||
const char* label="AUTO##OPKVS";
|
||||
if (ins->fm.op[o].kvs==0) {
|
||||
label="NO##OPKVS";
|
||||
} else if (ins->fm.op[o].kvs==1) {
|
||||
label="YES##OPKVS";
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d",i+1);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID(o);
|
||||
if (ImGui::Button(label,ImVec2(ImGui::GetContentRegionAvail().x,0.0f))) {
|
||||
if (++ins->fm.op[o].kvs>2) ins->fm.op[o].kvs=0;
|
||||
PARAMETER;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
if (ins->type==DIV_INS_FM && fmPreviewOn) {
|
||||
if (ImGui::IsItemHovered()) {
|
||||
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");
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
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;
|
||||
if (ImGui::BeginPopupContextItem("IKVSOpt",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||
ImGui::Text("operator level changes with volume?");
|
||||
if (ImGui::BeginTable("KVSTable",4,ImGuiTableFlags_BordersInner)) {
|
||||
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
|
||||
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed);
|
||||
ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch);
|
||||
for (int i=0; i<4; i++) {
|
||||
int o=(opCount==4)?orderedOps[i]:i;
|
||||
if (!(i&1)) ImGui::TableNextRow();
|
||||
const char* label="AUTO##OPKVS";
|
||||
if (ins->fm.op[o].kvs==0) {
|
||||
label="NO##OPKVS";
|
||||
} else if (ins->fm.op[o].kvs==1) {
|
||||
label="YES##OPKVS";
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d",i+1);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID(o);
|
||||
if (ImGui::Button(label,ImVec2(ImGui::GetContentRegionAvail().x,0.0f))) {
|
||||
if (++ins->fm.op[o].kvs>2) ins->fm.op[o].kvs=0;
|
||||
PARAMETER;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
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();
|
||||
drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
|
||||
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) {
|
||||
|
|
|
@ -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,242 +223,189 @@ 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;
|
||||
ImVec2 prevSpacing=ImGui::GetStyle().ItemSpacing;
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(1.0f*dpiScale,1.0f*dpiScale));
|
||||
ImGui::Columns(2,NULL,false);
|
||||
ImGui::SetColumnWidth(-1,regionX-24.0f*dpiScale);
|
||||
int displayChans=0;
|
||||
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||
if (e->curSubSong->chanShow[i]) displayChans++;
|
||||
}
|
||||
ImGui::PushFont(patFont);
|
||||
bool tooSmall=((displayChans+1)>((ImGui::GetContentRegionAvail().x)/(ImGui::CalcTextSize("AA").x+2.0*ImGui::GetStyle().ItemInnerSpacing.x)));
|
||||
ImGui::PopFont();
|
||||
if (ImGui::BeginTable("OrdersTable",1+displayChans,(tooSmall?ImGuiTableFlags_SizingFixedFit:ImGuiTableFlags_SizingStretchSame)|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY)) {
|
||||
ImGui::PushFont(patFont);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,prevSpacing);
|
||||
ImGui::TableSetupScrollFreeze(1,1);
|
||||
float lineHeight=(ImGui::GetTextLineHeight()+4*dpiScale);
|
||||
if (e->isPlaying()) {
|
||||
if (followOrders) {
|
||||
ImGui::SetScrollY((e->getOrder()+1)*lineHeight-(ImGui::GetContentRegionAvail().y/2));
|
||||
}
|
||||
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);
|
||||
}
|
||||
ImGui::TableNextRow(0,lineHeight);
|
||||
ImVec2 ra=ImGui::GetContentRegionAvail();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]);
|
||||
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||
if (!e->curSubSong->chanShow[i]) continue;
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s",e->getChannelShortName(i));
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
for (int i=0; i<e->curSubSong->ordersLen; i++) {
|
||||
ImGui::TableNextRow(0,lineHeight);
|
||||
if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE]));
|
||||
ImGui::TableNextColumn();
|
||||
if ((!followPattern && curOrder==i) || (followPattern && oldOrder1==i)) {
|
||||
// draw a border
|
||||
ImDrawList* dl=ImGui::GetWindowDrawList();
|
||||
ImVec2 rBegin=ImGui::GetCursorScreenPos();
|
||||
rBegin.y-=ImGui::GetStyle().CellPadding.y;
|
||||
ImVec2 rEnd=ImVec2(rBegin.x+ra.x,rBegin.y+lineHeight);
|
||||
dl->AddRect(rBegin,rEnd,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_SELECTED]),2.0f*dpiScale);
|
||||
}
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]);
|
||||
bool highlightLoop=(i>=loopOrder && i<=loopEnd);
|
||||
if (highlightLoop) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(uiColors[GUI_COLOR_SONG_LOOP]));
|
||||
if (settings.orderRowsBase==1) {
|
||||
snprintf(selID,4096,"%.2X##O_S%.2x",i,i);
|
||||
} else {
|
||||
snprintf(selID,4096,"%d##O_S%.2x",i,i);
|
||||
}
|
||||
if (ImGui::Selectable(selID)) {
|
||||
setOrder(i);
|
||||
curNibble=false;
|
||||
orderCursor=-1;
|
||||
|
||||
if (orderEditMode==0) {
|
||||
handleUnimportant;
|
||||
ImVec2 prevSpacing=ImGui::GetStyle().ItemSpacing;
|
||||
if (settings.orderButtonPos!=0) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(1.0f*dpiScale,1.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++;
|
||||
}
|
||||
ImGui::PushFont(patFont);
|
||||
bool tooSmall=((displayChans+1)>((ImGui::GetContentRegionAvail().x)/(ImGui::CalcTextSize("AA").x+2.0*ImGui::GetStyle().ItemInnerSpacing.x)));
|
||||
ImGui::PopFont();
|
||||
if (ImGui::BeginTable("OrdersTable",1+displayChans,(tooSmall?ImGuiTableFlags_SizingFixedFit:ImGuiTableFlags_SizingStretchSame)|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY)) {
|
||||
ImGui::PushFont(patFont);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,prevSpacing);
|
||||
ImGui::TableSetupScrollFreeze(1,1);
|
||||
float lineHeight=(ImGui::GetTextLineHeight()+4*dpiScale);
|
||||
if (e->isPlaying()) {
|
||||
if (followOrders) {
|
||||
ImGui::SetScrollY((e->getOrder()+1)*lineHeight-(ImGui::GetContentRegionAvail().y/2));
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
for (int j=0; j<e->getTotalChannelCount(); j++) {
|
||||
if (!e->curSubSong->chanShow[j]) continue;
|
||||
ImGui::TableNextRow(0,lineHeight);
|
||||
ImVec2 ra=ImGui::GetContentRegionAvail();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]);
|
||||
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||
if (!e->curSubSong->chanShow[i]) continue;
|
||||
ImGui::TableNextColumn();
|
||||
DivPattern* pat=e->curPat[j].getPattern(e->curOrders->ord[j][i],false);
|
||||
/*if (!pat->name.empty()) {
|
||||
snprintf(selID,4096,"%s##O_%.2x_%.2x",pat->name.c_str(),j,i);
|
||||
} else {*/
|
||||
snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->curOrders->ord[j][i],j,i);
|
||||
//}
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->curOrders->ord[j][i]==e->curOrders->ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]);
|
||||
if (ImGui::Selectable(selID,settings.ordersCursor?(cursor.xCoarse==j && oldOrder1!=i):false)) {
|
||||
if (curOrder==i) {
|
||||
if (orderEditMode==0) {
|
||||
prepareUndo(GUI_UNDO_CHANGE_ORDER);
|
||||
e->lockSave([this,i,j]() {
|
||||
if (changeAllOrders) {
|
||||
for (int k=0; k<e->getTotalChannelCount(); k++) {
|
||||
if (e->curOrders->ord[k][i]<(unsigned char)(DIV_MAX_PATTERNS-1)) e->curOrders->ord[k][i]++;
|
||||
}
|
||||
} else {
|
||||
if (e->curOrders->ord[j][i]<(unsigned char)(DIV_MAX_PATTERNS-1)) e->curOrders->ord[j][i]++;
|
||||
}
|
||||
});
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
makeUndo(GUI_UNDO_CHANGE_ORDER);
|
||||
} else {
|
||||
orderCursor=j;
|
||||
curNibble=false;
|
||||
}
|
||||
} else {
|
||||
setOrder(i);
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
if (orderEditMode!=0) {
|
||||
orderCursor=j;
|
||||
curNibble=false;
|
||||
}
|
||||
}
|
||||
ImGui::Text("%s",e->getChannelShortName(i));
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
for (int i=0; i<e->curSubSong->ordersLen; i++) {
|
||||
ImGui::TableNextRow(0,lineHeight);
|
||||
if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE]));
|
||||
ImGui::TableNextColumn();
|
||||
if ((!followPattern && curOrder==i) || (followPattern && oldOrder1==i)) {
|
||||
// draw a border
|
||||
ImDrawList* dl=ImGui::GetWindowDrawList();
|
||||
ImVec2 rBegin=ImGui::GetCursorScreenPos();
|
||||
rBegin.y-=ImGui::GetStyle().CellPadding.y;
|
||||
ImVec2 rEnd=ImVec2(rBegin.x+ra.x,rBegin.y+lineHeight);
|
||||
dl->AddRect(rBegin,rEnd,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_SELECTED]),2.0f*dpiScale);
|
||||
}
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]);
|
||||
bool highlightLoop=(i>=loopOrder && i<=loopEnd);
|
||||
if (highlightLoop) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(uiColors[GUI_COLOR_SONG_LOOP]));
|
||||
if (settings.orderRowsBase==1) {
|
||||
snprintf(selID,4096,"%.2X##O_S%.2x",i,i);
|
||||
} else {
|
||||
snprintf(selID,4096,"%d##O_S%.2x",i,i);
|
||||
}
|
||||
if (ImGui::Selectable(selID)) {
|
||||
setOrder(i);
|
||||
curNibble=false;
|
||||
orderCursor=-1;
|
||||
|
||||
if (orderEditMode==0) {
|
||||
handleUnimportant;
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
if (orderEditMode!=0 && curOrder==i && orderCursor==j) {
|
||||
// draw a border
|
||||
ImDrawList* dl=ImGui::GetWindowDrawList();
|
||||
dl->AddRect(ImGui::GetItemRectMin(),ImGui::GetItemRectMax(),ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]),2.0f*dpiScale);
|
||||
}
|
||||
if (!pat->name.empty() && ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("%s",pat->name.c_str());
|
||||
}
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||
if (curOrder==i) {
|
||||
if (orderEditMode==0) {
|
||||
prepareUndo(GUI_UNDO_CHANGE_ORDER);
|
||||
e->lockSave([this,i,j]() {
|
||||
if (changeAllOrders) {
|
||||
for (int k=0; k<e->getTotalChannelCount(); k++) {
|
||||
if (e->curOrders->ord[k][i]>0) e->curOrders->ord[k][i]--;
|
||||
for (int j=0; j<e->getTotalChannelCount(); j++) {
|
||||
if (!e->curSubSong->chanShow[j]) continue;
|
||||
ImGui::TableNextColumn();
|
||||
DivPattern* pat=e->curPat[j].getPattern(e->curOrders->ord[j][i],false);
|
||||
/*if (!pat->name.empty()) {
|
||||
snprintf(selID,4096,"%s##O_%.2x_%.2x",pat->name.c_str(),j,i);
|
||||
} else {*/
|
||||
snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->curOrders->ord[j][i],j,i);
|
||||
//}
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->curOrders->ord[j][i]==e->curOrders->ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]);
|
||||
if (ImGui::Selectable(selID,settings.ordersCursor?(cursor.xCoarse==j && oldOrder1!=i):false)) {
|
||||
if (curOrder==i) {
|
||||
if (orderEditMode==0) {
|
||||
prepareUndo(GUI_UNDO_CHANGE_ORDER);
|
||||
e->lockSave([this,i,j]() {
|
||||
if (changeAllOrders) {
|
||||
for (int k=0; k<e->getTotalChannelCount(); k++) {
|
||||
if (e->curOrders->ord[k][i]<(unsigned char)(DIV_MAX_PATTERNS-1)) e->curOrders->ord[k][i]++;
|
||||
}
|
||||
} else {
|
||||
if (e->curOrders->ord[j][i]<(unsigned char)(DIV_MAX_PATTERNS-1)) e->curOrders->ord[j][i]++;
|
||||
}
|
||||
} else {
|
||||
if (e->curOrders->ord[j][i]>0) e->curOrders->ord[j][i]--;
|
||||
}
|
||||
});
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
makeUndo(GUI_UNDO_CHANGE_ORDER);
|
||||
});
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
makeUndo(GUI_UNDO_CHANGE_ORDER);
|
||||
} else {
|
||||
orderCursor=j;
|
||||
curNibble=false;
|
||||
}
|
||||
} else {
|
||||
orderCursor=j;
|
||||
curNibble=false;
|
||||
setOrder(i);
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
if (orderEditMode!=0) {
|
||||
orderCursor=j;
|
||||
curNibble=false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setOrder(i);
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
if (orderEditMode!=0) {
|
||||
orderCursor=j;
|
||||
curNibble=false;
|
||||
|
||||
if (orderEditMode==0) {
|
||||
handleUnimportant;
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
if (orderEditMode!=0 && curOrder==i && orderCursor==j) {
|
||||
// draw a border
|
||||
ImDrawList* dl=ImGui::GetWindowDrawList();
|
||||
dl->AddRect(ImGui::GetItemRectMin(),ImGui::GetItemRectMax(),ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]),2.0f*dpiScale);
|
||||
}
|
||||
if (!pat->name.empty() && ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("%s",pat->name.c_str());
|
||||
}
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||
if (curOrder==i) {
|
||||
if (orderEditMode==0) {
|
||||
prepareUndo(GUI_UNDO_CHANGE_ORDER);
|
||||
e->lockSave([this,i,j]() {
|
||||
if (changeAllOrders) {
|
||||
for (int k=0; k<e->getTotalChannelCount(); k++) {
|
||||
if (e->curOrders->ord[k][i]>0) e->curOrders->ord[k][i]--;
|
||||
}
|
||||
} else {
|
||||
if (e->curOrders->ord[j][i]>0) e->curOrders->ord[j][i]--;
|
||||
}
|
||||
});
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
makeUndo(GUI_UNDO_CHANGE_ORDER);
|
||||
} else {
|
||||
orderCursor=j;
|
||||
curNibble=false;
|
||||
}
|
||||
} else {
|
||||
setOrder(i);
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
if (orderEditMode!=0) {
|
||||
orderCursor=j;
|
||||
curNibble=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopFont();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopFont();
|
||||
|
||||
if (settings.orderButtonPos==2) {
|
||||
ImGui::TableNextColumn();
|
||||
drawOrderButtons();
|
||||
}
|
||||
|
||||
if (settings.orderButtonPos!=0) {
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_ORDERS;
|
||||
oldOrder1=e->getOrder();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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;
|
||||
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH));
|
||||
if (ImGui::Begin("Subsongs",&subSongsOpen,globalWinFlags)) {
|
||||
if (!subSongsOpen && !asChild) return;
|
||||
if (!asChild) {
|
||||
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH));
|
||||
}
|
||||
bool began=asChild?ImGui::BeginChild("Subsongs"):ImGui::Begin("Subsongs",&subSongsOpen,globalWinFlags);
|
||||
if (began) {
|
||||
char id[1024];
|
||||
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;
|
||||
ImGui::End();
|
||||
if (!asChild && ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS;
|
||||
if (asChild) {
|
||||
ImGui::EndChild();
|
||||
} else {
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in a new issue