mirror of
https://github.com/tildearrow/furnace.git
synced 2024-12-04 18:27:25 +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
|
CMakeSettings.json
|
||||||
CMakePresets.json
|
CMakePresets.json
|
||||||
extern/imgui_patched/examples/
|
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/sm8521.c
|
||||||
|
|
||||||
src/engine/platform/sound/d65010g031.c
|
src/engine/platform/sound/d65modified.c
|
||||||
|
|
||||||
src/engine/platform/oplAInterface.cpp
|
src/engine/platform/oplAInterface.cpp
|
||||||
src/engine/platform/ym2608Interface.cpp
|
src/engine/platform/ym2608Interface.cpp
|
||||||
|
@ -469,10 +469,12 @@ src/engine/blip_buf.c
|
||||||
src/engine/brrUtils.c
|
src/engine/brrUtils.c
|
||||||
src/engine/safeReader.cpp
|
src/engine/safeReader.cpp
|
||||||
src/engine/safeWriter.cpp
|
src/engine/safeWriter.cpp
|
||||||
|
src/engine/cmdStream.cpp
|
||||||
src/engine/config.cpp
|
src/engine/config.cpp
|
||||||
src/engine/configEngine.cpp
|
src/engine/configEngine.cpp
|
||||||
src/engine/dispatchContainer.cpp
|
src/engine/dispatchContainer.cpp
|
||||||
src/engine/engine.cpp
|
src/engine/engine.cpp
|
||||||
|
src/engine/export.cpp
|
||||||
src/engine/fileOps.cpp
|
src/engine/fileOps.cpp
|
||||||
src/engine/fileOpsIns.cpp
|
src/engine/fileOpsIns.cpp
|
||||||
src/engine/filter.cpp
|
src/engine/filter.cpp
|
||||||
|
@ -488,6 +490,7 @@ src/engine/waveSynth.cpp
|
||||||
src/engine/vgmOps.cpp
|
src/engine/vgmOps.cpp
|
||||||
src/engine/zsmOps.cpp
|
src/engine/zsmOps.cpp
|
||||||
src/engine/zsm.cpp
|
src/engine/zsm.cpp
|
||||||
|
|
||||||
src/engine/platform/abstract.cpp
|
src/engine/platform/abstract.cpp
|
||||||
src/engine/platform/genesis.cpp
|
src/engine/platform/genesis.cpp
|
||||||
src/engine/platform/genesisext.cpp
|
src/engine/platform/genesisext.cpp
|
||||||
|
@ -550,6 +553,9 @@ src/engine/platform/sm8521.cpp
|
||||||
src/engine/platform/pv1000.cpp
|
src/engine/platform/pv1000.cpp
|
||||||
src/engine/platform/pcmdac.cpp
|
src/engine/platform/pcmdac.cpp
|
||||||
src/engine/platform/dummy.cpp
|
src/engine/platform/dummy.cpp
|
||||||
|
|
||||||
|
src/engine/export/abstract.cpp
|
||||||
|
src/engine/export/amigaValidation.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if (USE_SNDFILE)
|
if (USE_SNDFILE)
|
||||||
|
@ -619,6 +625,7 @@ src/gui/editing.cpp
|
||||||
src/gui/editControls.cpp
|
src/gui/editControls.cpp
|
||||||
src/gui/effectList.cpp
|
src/gui/effectList.cpp
|
||||||
src/gui/findReplace.cpp
|
src/gui/findReplace.cpp
|
||||||
|
src/gui/fmPreview.cpp
|
||||||
src/gui/gradient.cpp
|
src/gui/gradient.cpp
|
||||||
src/gui/grooves.cpp
|
src/gui/grooves.cpp
|
||||||
src/gui/insEdit.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 "ImGuiFileDialog.h"
|
||||||
|
#include "../../src/ta-log.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
@ -1202,11 +1203,13 @@ namespace IGFD
|
||||||
SetDefaultFileName(".");
|
SetDefaultFileName(".");
|
||||||
else
|
else
|
||||||
SetDefaultFileName("");
|
SetDefaultFileName("");
|
||||||
|
logV("IGFD: OpenCurrentPath()");
|
||||||
ScanDir(vFileDialogInternal, GetCurrentPath());
|
ScanDir(vFileDialogInternal, GetCurrentPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
void IGFD::FileManager::SortFields(const FileDialogInternal& vFileDialogInternal, const SortingFieldEnum& vSortingField, const bool vCanChangeOrder)
|
void IGFD::FileManager::SortFields(const FileDialogInternal& vFileDialogInternal, const SortingFieldEnum& vSortingField, const bool vCanChangeOrder)
|
||||||
{
|
{
|
||||||
|
logV("IGFD: SortFields()");
|
||||||
if (vSortingField != SortingFieldEnum::FIELD_NONE)
|
if (vSortingField != SortingFieldEnum::FIELD_NONE)
|
||||||
{
|
{
|
||||||
puHeaderFileName = tableHeaderFileNameString;
|
puHeaderFileName = tableHeaderFileNameString;
|
||||||
|
@ -1216,10 +1219,17 @@ namespace IGFD
|
||||||
#ifdef USE_THUMBNAILS
|
#ifdef USE_THUMBNAILS
|
||||||
puHeaderFileThumbnails = tableHeaderFileThumbnailsString;
|
puHeaderFileThumbnails = tableHeaderFileThumbnailsString;
|
||||||
#endif // #ifdef USE_THUMBNAILS
|
#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)
|
if (vSortingField == SortingFieldEnum::FIELD_FILENAME)
|
||||||
{
|
{
|
||||||
|
logV("IGFD: sorting by name");
|
||||||
if (vCanChangeOrder && puSortingField == vSortingField) {
|
if (vCanChangeOrder && puSortingField == vSortingField) {
|
||||||
//printf("Change the sorting\n");
|
//printf("Change the sorting\n");
|
||||||
puSortingDirection[0] = true;//!puSortingDirection[0];
|
puSortingDirection[0] = true;//!puSortingDirection[0];
|
||||||
|
@ -1289,6 +1299,7 @@ namespace IGFD
|
||||||
}
|
}
|
||||||
else if (vSortingField == SortingFieldEnum::FIELD_TYPE)
|
else if (vSortingField == SortingFieldEnum::FIELD_TYPE)
|
||||||
{
|
{
|
||||||
|
logV("IGFD: sorting by type");
|
||||||
if (vCanChangeOrder && puSortingField == vSortingField)
|
if (vCanChangeOrder && puSortingField == vSortingField)
|
||||||
puSortingDirection[1] = !puSortingDirection[1];
|
puSortingDirection[1] = !puSortingDirection[1];
|
||||||
|
|
||||||
|
@ -1327,6 +1338,7 @@ namespace IGFD
|
||||||
}
|
}
|
||||||
else if (vSortingField == SortingFieldEnum::FIELD_SIZE)
|
else if (vSortingField == SortingFieldEnum::FIELD_SIZE)
|
||||||
{
|
{
|
||||||
|
logV("IGFD: sorting by size");
|
||||||
if (vCanChangeOrder && puSortingField == vSortingField)
|
if (vCanChangeOrder && puSortingField == vSortingField)
|
||||||
puSortingDirection[2] = !puSortingDirection[2];
|
puSortingDirection[2] = !puSortingDirection[2];
|
||||||
|
|
||||||
|
@ -1367,6 +1379,7 @@ namespace IGFD
|
||||||
}
|
}
|
||||||
else if (vSortingField == SortingFieldEnum::FIELD_DATE)
|
else if (vSortingField == SortingFieldEnum::FIELD_DATE)
|
||||||
{
|
{
|
||||||
|
logV("IGFD: sorting by date");
|
||||||
if (vCanChangeOrder && puSortingField == vSortingField)
|
if (vCanChangeOrder && puSortingField == vSortingField)
|
||||||
puSortingDirection[3] = !puSortingDirection[3];
|
puSortingDirection[3] = !puSortingDirection[3];
|
||||||
|
|
||||||
|
@ -1458,6 +1471,8 @@ namespace IGFD
|
||||||
puSortingField = vSortingField;
|
puSortingField = vSortingField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logV("IGFD: applying filtering on file list");
|
||||||
|
|
||||||
ApplyFilteringOnFileList(vFileDialogInternal);
|
ApplyFilteringOnFileList(vFileDialogInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1518,14 +1533,17 @@ namespace IGFD
|
||||||
void IGFD::FileManager::ScanDir(const FileDialogInternal& vFileDialogInternal, const std::string& vPath)
|
void IGFD::FileManager::ScanDir(const FileDialogInternal& vFileDialogInternal, const std::string& vPath)
|
||||||
{
|
{
|
||||||
std::string path = vPath;
|
std::string path = vPath;
|
||||||
|
logV("IGFD: ScanDir(%s)",vPath);
|
||||||
|
|
||||||
if (prCurrentPathDecomposition.empty())
|
if (prCurrentPathDecomposition.empty())
|
||||||
{
|
{
|
||||||
|
logV("IGFD: the current path decomposition is empty. setting.");
|
||||||
SetCurrentDir(path);
|
SetCurrentDir(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!prCurrentPathDecomposition.empty())
|
if (!prCurrentPathDecomposition.empty())
|
||||||
{
|
{
|
||||||
|
logV("IGFD: the current path decomposition is not empty. trying.");
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
if (path == puFsRoot)
|
if (path == puFsRoot)
|
||||||
path += std::string(1u, PATH_SEP);
|
path += std::string(1u, PATH_SEP);
|
||||||
|
@ -1553,6 +1571,7 @@ namespace IGFD
|
||||||
#else // dirent
|
#else // dirent
|
||||||
struct dirent** files = nullptr;
|
struct dirent** files = nullptr;
|
||||||
int n = scandir(path.c_str(), &files, nullptr, inAlphaSort);
|
int n = scandir(path.c_str(), &files, nullptr, inAlphaSort);
|
||||||
|
logV("IGFD: %d entries in directory",n);
|
||||||
if (n>0)
|
if (n>0)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
@ -1620,11 +1639,18 @@ namespace IGFD
|
||||||
}
|
}
|
||||||
|
|
||||||
free(files);
|
free(files);
|
||||||
}
|
} else {
|
||||||
|
logV("IGFD: it's empty");
|
||||||
|
}
|
||||||
#endif // USE_STD_FILESYSTEM
|
#endif // USE_STD_FILESYSTEM
|
||||||
|
|
||||||
|
logV("IGFD: sorting fields...");
|
||||||
SortFields(vFileDialogInternal, puSortingField, false);
|
SortFields(vFileDialogInternal, puSortingField, false);
|
||||||
}
|
} else {
|
||||||
|
logE("IGFD: current path decomposition is empty!");
|
||||||
|
}
|
||||||
|
|
||||||
|
fileListActuallyEmpty=prFileList.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IGFD::FileManager::GetDrives()
|
bool IGFD::FileManager::GetDrives()
|
||||||
|
@ -2436,7 +2462,7 @@ namespace IGFD
|
||||||
|
|
||||||
void IGFD::FileDialogInternal::ResetForNewDialog()
|
void IGFD::FileDialogInternal::ResetForNewDialog()
|
||||||
{
|
{
|
||||||
|
puFileManager.fileListActuallyEmpty=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -3737,16 +3763,17 @@ namespace IGFD
|
||||||
fdFilter.SetDefaultFilterIfNotDefined();
|
fdFilter.SetDefaultFilterIfNotDefined();
|
||||||
|
|
||||||
// init list of files
|
// 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
|
IGFD::Utils::ReplaceString(fdFile.puDLGDefaultFileName, fdFile.puDLGpath, ""); // local path
|
||||||
if (!fdFile.puDLGDefaultFileName.empty())
|
if (!fdFile.puDLGDefaultFileName.empty())
|
||||||
{
|
{
|
||||||
fdFile.SetDefaultFileName(fdFile.puDLGDefaultFileName);
|
fdFile.SetDefaultFileName(fdFile.puDLGDefaultFileName);
|
||||||
fdFilter.SetSelectedFilterWithExt(fdFilter.puDLGdefaultExt);
|
fdFilter.SetSelectedFilterWithExt(fdFilter.puDLGdefaultExt);
|
||||||
}
|
} else if (fdFile.puDLGDirectoryMode) { // directory mode
|
||||||
else if (fdFile.puDLGDirectoryMode) // directory mode
|
|
||||||
fdFile.SetDefaultFileName(".");
|
fdFile.SetDefaultFileName(".");
|
||||||
|
}
|
||||||
|
logV("IGFD: fdFile.IsFileListEmpty() and !fdFile.puShowDrives");
|
||||||
fdFile.ScanDir(prFileDialogInternal, fdFile.puDLGpath);
|
fdFile.ScanDir(prFileDialogInternal, fdFile.puDLGpath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
extern/igfd/ImGuiFileDialog.h
vendored
1
extern/igfd/ImGuiFileDialog.h
vendored
|
@ -875,6 +875,7 @@ namespace IGFD
|
||||||
#endif
|
#endif
|
||||||
SortingFieldEnum puSortingField = SortingFieldEnum::FIELD_FILENAME; // detail view sorting column
|
SortingFieldEnum puSortingField = SortingFieldEnum::FIELD_FILENAME; // detail view sorting column
|
||||||
bool puShowDrives = false; // drives are shown (only on os windows)
|
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 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
|
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
|
* - 0xffffffff: reset
|
||||||
*/
|
*/
|
||||||
unsigned int addr;
|
unsigned int addr;
|
||||||
unsigned short val;
|
unsigned int val;
|
||||||
DivRegWrite(unsigned int a, unsigned short v):
|
DivRegWrite(unsigned int a, unsigned int v):
|
||||||
addr(a), val(v) {}
|
addr(a), val(v) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DivDelayedWrite {
|
struct DivDelayedWrite {
|
||||||
int time;
|
int time;
|
||||||
DivRegWrite write;
|
DivRegWrite write;
|
||||||
DivDelayedWrite(int t, unsigned int a, unsigned short v):
|
DivDelayedWrite(int t, unsigned int a, unsigned int v):
|
||||||
time(t),
|
time(t),
|
||||||
write(a,v) {}
|
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 {
|
struct DivDispatchOscBuffer {
|
||||||
bool follow;
|
bool follow;
|
||||||
unsigned int rate;
|
unsigned int rate;
|
||||||
|
@ -371,18 +383,29 @@ class DivDispatch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the state of a channel.
|
* get the state of a channel.
|
||||||
|
* @param chan the channel.
|
||||||
* @return a pointer, or NULL.
|
* @return a pointer, or NULL.
|
||||||
*/
|
*/
|
||||||
virtual void* getChanState(int chan);
|
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.
|
* @return a pointer, or NULL.
|
||||||
*/
|
*/
|
||||||
virtual DivMacroInt* getChanMacroInt(int chan);
|
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.
|
* get an oscilloscope buffer for a channel.
|
||||||
|
* @param chan the channel.
|
||||||
* @return a pointer to a DivDispatchOscBuffer, or NULL if not supported.
|
* @return a pointer to a DivDispatchOscBuffer, or NULL if not supported.
|
||||||
*/
|
*/
|
||||||
virtual DivDispatchOscBuffer* getOscBuffer(int chan);
|
virtual DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||||
|
|
|
@ -1588,7 +1588,7 @@ void DivEngine::checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries) {
|
||||||
// get unsorted directory
|
// get unsorted directory
|
||||||
DivAssetDir* unsortedDir=NULL;
|
DivAssetDir* unsortedDir=NULL;
|
||||||
for (DivAssetDir& i: dir) {
|
for (DivAssetDir& i: dir) {
|
||||||
if (i.name=="Unsorted") {
|
if (i.name.empty()) {
|
||||||
unsortedDir=&i;
|
unsortedDir=&i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1596,7 +1596,7 @@ void DivEngine::checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries) {
|
||||||
|
|
||||||
// create unsorted directory if it doesn't exist
|
// create unsorted directory if it doesn't exist
|
||||||
if (unsortedDir==NULL) {
|
if (unsortedDir==NULL) {
|
||||||
dir.push_back(DivAssetDir("Unsorted"));
|
dir.push_back(DivAssetDir(""));
|
||||||
unsortedDir=&(*dir.rbegin());
|
unsortedDir=&(*dir.rbegin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2119,6 +2119,11 @@ DivMacroInt* DivEngine::getMacroInt(int chan) {
|
||||||
return disCont[dispatchOfChan[chan]].dispatch->getChanMacroInt(dispatchChanOfChan[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) {
|
DivDispatchOscBuffer* DivEngine::getOscBuffer(int chan) {
|
||||||
if (chan<0 || chan>=chans) return NULL;
|
if (chan<0 || chan>=chans) return NULL;
|
||||||
return disCont[dispatchOfChan[chan]].dispatch->getOscBuffer(dispatchChanOfChan[chan]);
|
return disCont[dispatchOfChan[chan]].dispatch->getOscBuffer(dispatchChanOfChan[chan]);
|
||||||
|
@ -2498,6 +2503,10 @@ void DivEngine::recalcChans() {
|
||||||
if (isInsTypePossible[i]) possibleInsTypes.push_back((DivInstrumentType)i);
|
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;
|
hasLoadedSomething=true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2576,6 +2585,10 @@ int DivEngine::divToFileRate(int drate) {
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DivEngine::testFunction() {
|
||||||
|
logI("it works!");
|
||||||
|
}
|
||||||
|
|
||||||
int DivEngine::getEffectiveSampleRate(int rate) {
|
int DivEngine::getEffectiveSampleRate(int rate) {
|
||||||
if (rate<1) return 0;
|
if (rate<1) return 0;
|
||||||
switch (song.system[0]) {
|
switch (song.system[0]) {
|
||||||
|
@ -4358,6 +4371,7 @@ bool DivEngine::initAudioBackend() {
|
||||||
lowLatency=getConfInt("lowLatency",0);
|
lowLatency=getConfInt("lowLatency",0);
|
||||||
metroVol=(float)(getConfInt("metroVol",100))/100.0f;
|
metroVol=(float)(getConfInt("metroVol",100))/100.0f;
|
||||||
midiOutClock=getConfInt("midiOutClock",0);
|
midiOutClock=getConfInt("midiOutClock",0);
|
||||||
|
midiOutProgramChange = getConfInt("midiOutProgramChange",0);
|
||||||
midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE);
|
midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE);
|
||||||
if (metroVol<0.0f) metroVol=0.0f;
|
if (metroVol<0.0f) metroVol=0.0f;
|
||||||
if (metroVol>2.0f) metroVol=2.0f;
|
if (metroVol>2.0f) metroVol=2.0f;
|
||||||
|
|
|
@ -23,8 +23,10 @@
|
||||||
#include "instrument.h"
|
#include "instrument.h"
|
||||||
#include "song.h"
|
#include "song.h"
|
||||||
#include "dispatch.h"
|
#include "dispatch.h"
|
||||||
|
#include "export.h"
|
||||||
#include "dataErrors.h"
|
#include "dataErrors.h"
|
||||||
#include "safeWriter.h"
|
#include "safeWriter.h"
|
||||||
|
#include "cmdStream.h"
|
||||||
#include "../audio/taAudio.h"
|
#include "../audio/taAudio.h"
|
||||||
#include "blip_buf.h"
|
#include "blip_buf.h"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
@ -47,11 +49,17 @@
|
||||||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
#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_VERSION "dev145"
|
||||||
#define DIV_ENGINE_VERSION 145
|
#define DIV_ENGINE_VERSION 145
|
||||||
// for imports
|
// for imports
|
||||||
#define DIV_VERSION_MOD 0xff01
|
#define DIV_VERSION_MOD 0xff01
|
||||||
#define DIV_VERSION_FC 0xff02
|
#define DIV_VERSION_FC 0xff02
|
||||||
|
#define DIV_VERSION_S3M 0xff03
|
||||||
|
#define DIV_VERSION_FTM 0xff04
|
||||||
|
|
||||||
// "Namco C163"
|
// "Namco C163"
|
||||||
#define DIV_C163_DEFAULT_NAME "Namco 163"
|
#define DIV_C163_DEFAULT_NAME "Namco 163"
|
||||||
|
@ -359,6 +367,7 @@ class DivEngine {
|
||||||
bool systemsRegistered;
|
bool systemsRegistered;
|
||||||
bool hasLoadedSomething;
|
bool hasLoadedSomething;
|
||||||
bool midiOutClock;
|
bool midiOutClock;
|
||||||
|
bool midiOutProgramChange;
|
||||||
int midiOutMode;
|
int midiOutMode;
|
||||||
int softLockCount;
|
int softLockCount;
|
||||||
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats, curSpeed;
|
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 sysFileMapFur[DIV_MAX_CHIP_DEFS];
|
||||||
static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS];
|
static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS];
|
||||||
|
|
||||||
|
DivCSPlayer* cmdStreamInt;
|
||||||
|
|
||||||
struct SamplePreview {
|
struct SamplePreview {
|
||||||
double rate;
|
double rate;
|
||||||
int sample;
|
int sample;
|
||||||
|
@ -441,7 +452,6 @@ class DivEngine {
|
||||||
// MIDI stuff
|
// MIDI stuff
|
||||||
std::function<int(const TAMidiMessage&)> midiCallback=[](const TAMidiMessage&) -> int {return -2;};
|
std::function<int(const TAMidiMessage&)> midiCallback=[](const TAMidiMessage&) -> int {return -2;};
|
||||||
|
|
||||||
int dispatchCmd(DivCommand c);
|
|
||||||
void processRow(int i, bool afterDelay);
|
void processRow(int i, bool afterDelay);
|
||||||
void nextOrder();
|
void nextOrder();
|
||||||
void nextRow();
|
void nextRow();
|
||||||
|
@ -454,9 +464,12 @@ class DivEngine {
|
||||||
void reset();
|
void reset();
|
||||||
void playSub(bool preserveDrift, int goalRow=0);
|
void playSub(bool preserveDrift, int goalRow=0);
|
||||||
|
|
||||||
|
void testFunction();
|
||||||
|
|
||||||
bool loadDMF(unsigned char* file, size_t len);
|
bool loadDMF(unsigned char* file, size_t len);
|
||||||
bool loadFur(unsigned char* file, size_t len);
|
bool loadFur(unsigned char* file, size_t len);
|
||||||
bool loadMod(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 loadFTM(unsigned char* file, size_t len);
|
||||||
bool loadFC(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
|
// check whether an asset directory is complete
|
||||||
void checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries);
|
void checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries);
|
||||||
|
|
||||||
|
// add every export method here
|
||||||
|
friend class DivROMExport;
|
||||||
|
friend class DivExportAmigaValidation;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DivSong song;
|
DivSong song;
|
||||||
DivOrders* curOrders;
|
DivOrders* curOrders;
|
||||||
|
@ -522,6 +539,8 @@ class DivEngine {
|
||||||
void createNewFromDefaults();
|
void createNewFromDefaults();
|
||||||
// load a file.
|
// load a file.
|
||||||
bool load(unsigned char* f, size_t length);
|
bool load(unsigned char* f, size_t length);
|
||||||
|
// play a binary command stream.
|
||||||
|
bool playStream(unsigned char* f, size_t length);
|
||||||
// save as .dmf.
|
// save as .dmf.
|
||||||
SafeWriter* saveDMF(unsigned char version);
|
SafeWriter* saveDMF(unsigned char version);
|
||||||
// save as .fur.
|
// save as .fur.
|
||||||
|
@ -529,7 +548,7 @@ class DivEngine {
|
||||||
SafeWriter* saveFur(bool notPrimary=false);
|
SafeWriter* saveFur(bool notPrimary=false);
|
||||||
// build a ROM file (TODO).
|
// build a ROM file (TODO).
|
||||||
// specify system to build ROM for.
|
// specify system to build ROM for.
|
||||||
SafeWriter* buildROM(int sys);
|
std::vector<DivROMExportOutput> buildROM(DivROMExportOptions sys);
|
||||||
// dump to VGM.
|
// dump to VGM.
|
||||||
// set trailingTicks to:
|
// set trailingTicks to:
|
||||||
// - 0 to add one tick of trailing
|
// - 0 to add one tick of trailing
|
||||||
|
@ -552,6 +571,9 @@ class DivEngine {
|
||||||
// notify wavetable change
|
// notify wavetable change
|
||||||
void notifyWaveChange(int wave);
|
void notifyWaveChange(int wave);
|
||||||
|
|
||||||
|
// dispatch a command
|
||||||
|
int dispatchCmd(DivCommand c);
|
||||||
|
|
||||||
// get system IDs
|
// get system IDs
|
||||||
static DivSystem systemFromFileFur(unsigned char val);
|
static DivSystem systemFromFileFur(unsigned char val);
|
||||||
static unsigned char systemToFileFur(DivSystem val);
|
static unsigned char systemToFileFur(DivSystem val);
|
||||||
|
@ -901,6 +923,9 @@ class DivEngine {
|
||||||
// get macro interpreter
|
// get macro interpreter
|
||||||
DivMacroInt* getMacroInt(int chan);
|
DivMacroInt* getMacroInt(int chan);
|
||||||
|
|
||||||
|
// get sample position
|
||||||
|
DivSamplePos getSamplePos(int chan);
|
||||||
|
|
||||||
// get osc buffer
|
// get osc buffer
|
||||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||||
|
|
||||||
|
@ -1094,6 +1119,7 @@ class DivEngine {
|
||||||
systemsRegistered(false),
|
systemsRegistered(false),
|
||||||
hasLoadedSomething(false),
|
hasLoadedSomething(false),
|
||||||
midiOutClock(false),
|
midiOutClock(false),
|
||||||
|
midiOutProgramChange(false),
|
||||||
midiOutMode(DIV_MIDI_MODE_NOTE),
|
midiOutMode(DIV_MIDI_MODE_NOTE),
|
||||||
softLockCount(0),
|
softLockCount(0),
|
||||||
subticks(0),
|
subticks(0),
|
||||||
|
@ -1133,6 +1159,7 @@ class DivEngine {
|
||||||
audioEngine(DIV_AUDIO_NULL),
|
audioEngine(DIV_AUDIO_NULL),
|
||||||
exportMode(DIV_EXPORT_MODE_ONE),
|
exportMode(DIV_EXPORT_MODE_ONE),
|
||||||
exportFadeOut(0.0),
|
exportFadeOut(0.0),
|
||||||
|
cmdStreamInt(NULL),
|
||||||
midiBaseChan(0),
|
midiBaseChan(0),
|
||||||
midiPoly(true),
|
midiPoly(true),
|
||||||
midiAgeCounter(0),
|
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_FTM_MAGIC "FamiTracker Module"
|
||||||
#define DIV_FC13_MAGIC "SMOD"
|
#define DIV_FC13_MAGIC "SMOD"
|
||||||
#define DIV_FC14_MAGIC "FC14"
|
#define DIV_FC14_MAGIC "FC14"
|
||||||
|
#define DIV_S3M_MAGIC "SCRM"
|
||||||
|
|
||||||
struct InflateBlock {
|
struct InflateBlock {
|
||||||
unsigned char* buf;
|
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) {
|
bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
||||||
struct InvalidHeaderException {};
|
struct InvalidHeaderException {};
|
||||||
bool success=false;
|
bool success=false;
|
||||||
|
@ -3820,12 +4061,58 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
||||||
|
|
||||||
#define CHECK_BLOCK_VERSION(x) \
|
#define CHECK_BLOCK_VERSION(x) \
|
||||||
if (blockVersion>x) { \
|
if (blockVersion>x) { \
|
||||||
logE("incompatible block version %d for %s!",blockVersion,blockName); \
|
logW("incompatible block version %d for %s!",blockVersion,blockName); \
|
||||||
lastError="incompatible block version"; \
|
|
||||||
delete[] file; \
|
|
||||||
return false; \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
||||||
SafeReader reader=SafeReader(file,len);
|
SafeReader reader=SafeReader(file,len);
|
||||||
warnings="";
|
warnings="";
|
||||||
|
@ -3837,6 +4124,9 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
||||||
unsigned int n163Chans=0;
|
unsigned int n163Chans=0;
|
||||||
bool hasSequence[256][8];
|
bool hasSequence[256][8];
|
||||||
unsigned char sequenceIndex[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(hasSequence,0,256*8*sizeof(bool));
|
||||||
memset(sequenceIndex,0,256*8);
|
memset(sequenceIndex,0,256*8);
|
||||||
|
@ -3857,6 +4147,14 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (DivSubSong* i: ds.subsong) {
|
||||||
|
i->clearData();
|
||||||
|
delete i;
|
||||||
|
}
|
||||||
|
ds.subsong.clear();
|
||||||
|
|
||||||
|
ds.linearPitch=0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
blockName=reader.readString(3);
|
blockName=reader.readString(3);
|
||||||
if (blockName=="END") {
|
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);
|
logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize);
|
||||||
if (blockName=="PARAMS") {
|
if (blockName=="PARAMS") {
|
||||||
CHECK_BLOCK_VERSION(6);
|
// versions 7-9 don't change anything?
|
||||||
|
CHECK_BLOCK_VERSION(9);
|
||||||
unsigned int oldSpeedTempo=0;
|
unsigned int oldSpeedTempo=0;
|
||||||
if (blockVersion<=1) {
|
if (blockVersion<=1) {
|
||||||
oldSpeedTempo=reader.readI();
|
oldSpeedTempo=reader.readI();
|
||||||
|
@ -3884,15 +4183,32 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
||||||
}
|
}
|
||||||
tchans=reader.readI();
|
tchans=reader.readI();
|
||||||
unsigned int pal=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;
|
unsigned int newVibrato=0;
|
||||||
|
bool sweepReset=false;
|
||||||
unsigned int speedSplitPoint=0;
|
unsigned int speedSplitPoint=0;
|
||||||
if (blockVersion>=3) {
|
if (blockVersion>=3) {
|
||||||
newVibrato=reader.readI();
|
newVibrato=reader.readI();
|
||||||
}
|
}
|
||||||
if (blockVersion>=4) {
|
if (blockVersion>=9) {
|
||||||
ds.subsong[0]->hilightA=reader.readI();
|
sweepReset=reader.readI();
|
||||||
ds.subsong[0]->hilightB=reader.readI();
|
}
|
||||||
|
if (blockVersion>=4 && blockVersion<7) {
|
||||||
|
hilightA=reader.readI();
|
||||||
|
hilightB=reader.readI();
|
||||||
}
|
}
|
||||||
if (expansions&8) if (blockVersion>=5) { // N163 channels
|
if (expansions&8) if (blockVersion>=5) { // N163 channels
|
||||||
n163Chans=reader.readI();
|
n163Chans=reader.readI();
|
||||||
|
@ -3901,20 +4217,24 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
||||||
speedSplitPoint=reader.readI();
|
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("old speed/tempo: %d",oldSpeedTempo);
|
||||||
logV("expansions: %x",expansions);
|
logV("expansions: %x",expansions);
|
||||||
logV("channels: %d",tchans);
|
logV("channels: %d",tchans);
|
||||||
logV("PAL: %d",pal);
|
logV("PAL: %d",pal);
|
||||||
logV("custom Hz: %d",customHz);
|
logV("custom Hz: %f",customHz);
|
||||||
logV("new vibrato: %d",newVibrato);
|
logV("new vibrato: %d",newVibrato);
|
||||||
logV("N163 channels: %d",n163Chans);
|
logV("N163 channels: %d",n163Chans);
|
||||||
logV("highlight 1: %d",ds.subsong[0]->hilightA);
|
logV("highlight 1: %d",hilightA);
|
||||||
logV("highlight 2: %d",ds.subsong[0]->hilightB);
|
logV("highlight 2: %d",hilightB);
|
||||||
logV("split point: %d",speedSplitPoint);
|
logV("split point: %d",speedSplitPoint);
|
||||||
|
logV("sweep reset: %d",sweepReset);
|
||||||
if (customHz!=0) {
|
|
||||||
ds.subsong[0]->hz=customHz;
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize channels
|
// initialize channels
|
||||||
int systemID=0;
|
int systemID=0;
|
||||||
|
@ -3959,28 +4279,46 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
||||||
CHECK_BLOCK_VERSION(1);
|
CHECK_BLOCK_VERSION(1);
|
||||||
ds.name=reader.readString(32);
|
ds.name=reader.readString(32);
|
||||||
ds.author=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") {
|
} else if (blockName=="HEADER") {
|
||||||
CHECK_BLOCK_VERSION(3);
|
CHECK_BLOCK_VERSION(4);
|
||||||
unsigned char totalSongs=reader.readC();
|
unsigned char totalSongs=reader.readC();
|
||||||
logV("%d songs:",totalSongs+1);
|
logV("%d songs:",totalSongs+1);
|
||||||
for (int i=0; i<=totalSongs; i++) {
|
for (int i=0; i<=totalSongs; i++) {
|
||||||
String subSongName=reader.readString();
|
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);
|
logV("- %s",subSongName);
|
||||||
}
|
}
|
||||||
for (unsigned int i=0; i<tchans; i++) {
|
for (unsigned int i=0; i<tchans; i++) {
|
||||||
|
// TODO: obey channel ID
|
||||||
unsigned char chID=reader.readC();
|
unsigned char chID=reader.readC();
|
||||||
logV("for channel ID %d",chID);
|
logV("for channel ID %d",chID);
|
||||||
for (int j=0; j<=totalSongs; j++) {
|
for (int j=0; j<=totalSongs; j++) {
|
||||||
unsigned char effectCols=reader.readC();
|
unsigned char effectCols=reader.readC();
|
||||||
if (j==0) {
|
ds.subsong[j]->pat[i].effectCols=effectCols+1;
|
||||||
ds.subsong[0]->pat[i].effectCols=effectCols+1;
|
|
||||||
}
|
|
||||||
logV("- song %d has %d effect columns",j,effectCols);
|
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") {
|
} else if (blockName=="INSTRUMENTS") {
|
||||||
CHECK_BLOCK_VERSION(6);
|
CHECK_BLOCK_VERSION(6);
|
||||||
|
|
||||||
|
reader.seek(blockSize,SEEK_CUR);
|
||||||
|
|
||||||
|
/*
|
||||||
ds.insLen=reader.readI();
|
ds.insLen=reader.readI();
|
||||||
if (ds.insLen<0 || ds.insLen>256) {
|
if (ds.insLen<0 || ds.insLen>256) {
|
||||||
logE("too many instruments/out of range!");
|
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());
|
ins->name=reader.readString((unsigned int)reader.readI());
|
||||||
logV("- %d: %s",insIndex,ins->name);
|
logV("- %d: %s",insIndex,ins->name);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
} else if (blockName=="SEQUENCES") {
|
} else if (blockName=="SEQUENCES") {
|
||||||
CHECK_BLOCK_VERSION(6);
|
CHECK_BLOCK_VERSION(6);
|
||||||
|
reader.seek(blockSize,SEEK_CUR);
|
||||||
} else if (blockName=="FRAMES") {
|
} else if (blockName=="FRAMES") {
|
||||||
CHECK_BLOCK_VERSION(3);
|
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") {
|
} 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") {
|
} else if (blockName=="DPCM SAMPLES") {
|
||||||
CHECK_BLOCK_VERSION(1);
|
CHECK_BLOCK_VERSION(1);
|
||||||
|
reader.seek(blockSize,SEEK_CUR);
|
||||||
} else if (blockName=="SEQUENCES_VRC6") {
|
} else if (blockName=="SEQUENCES_VRC6") {
|
||||||
// where are the 5B and FDS sequences?
|
// where are the 5B and FDS sequences?
|
||||||
CHECK_BLOCK_VERSION(6);
|
CHECK_BLOCK_VERSION(6);
|
||||||
|
reader.seek(blockSize,SEEK_CUR);
|
||||||
} else if (blockName=="SEQUENCES_N163") {
|
} else if (blockName=="SEQUENCES_N163") {
|
||||||
CHECK_BLOCK_VERSION(1);
|
CHECK_BLOCK_VERSION(1);
|
||||||
|
reader.seek(blockSize,SEEK_CUR);
|
||||||
} else if (blockName=="COMMENTS") {
|
} else if (blockName=="COMMENTS") {
|
||||||
CHECK_BLOCK_VERSION(1);
|
CHECK_BLOCK_VERSION(1);
|
||||||
|
reader.seek(blockSize,SEEK_CUR);
|
||||||
} else {
|
} else {
|
||||||
logE("block %s is unknown!",blockName);
|
logE("block %s is unknown!",blockName);
|
||||||
lastError="unknown block "+blockName;
|
lastError="unknown block "+blockName;
|
||||||
|
@ -4169,6 +4617,27 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
||||||
return false;
|
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) {
|
} catch (EndOfFileException& e) {
|
||||||
logE("premature end of file!");
|
logE("premature end of file!");
|
||||||
lastError="incomplete file";
|
lastError="incomplete file";
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "macroInt.h"
|
#include "macroInt.h"
|
||||||
#include "instrument.h"
|
#include "instrument.h"
|
||||||
#include "engine.h"
|
#include "engine.h"
|
||||||
|
#include "../ta-log.h"
|
||||||
|
|
||||||
#define ADSR_LOW source.val[0]
|
#define ADSR_LOW source.val[0]
|
||||||
#define ADSR_HIGH source.val[1]
|
#define ADSR_HIGH source.val[1]
|
||||||
|
@ -52,6 +53,7 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic
|
||||||
}
|
}
|
||||||
if (masked) {
|
if (masked) {
|
||||||
had=false;
|
had=false;
|
||||||
|
has=false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (delay>0) {
|
if (delay>0) {
|
||||||
|
@ -246,8 +248,10 @@ void DivMacroInt::setEngine(DivEngine* eng) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#define ADD_MACRO(m,s) \
|
#define ADD_MACRO(m,s) \
|
||||||
macroList[macroListLen]=&m; \
|
if (!m.masked) { \
|
||||||
macroSource[macroListLen++]=&s;
|
macroList[macroListLen]=&m; \
|
||||||
|
macroSource[macroListLen++]=&s; \
|
||||||
|
}
|
||||||
|
|
||||||
void DivMacroInt::init(DivInstrument* which) {
|
void DivMacroInt::init(DivInstrument* which) {
|
||||||
ins=which;
|
ins=which;
|
||||||
|
|
|
@ -37,6 +37,10 @@ DivMacroInt* DivDispatch::getChanMacroInt(int chan) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DivSamplePos DivDispatch::getSamplePos(int chan) {
|
||||||
|
return DivSamplePos();
|
||||||
|
}
|
||||||
|
|
||||||
DivDispatchOscBuffer* DivDispatch::getOscBuffer(int chan) {
|
DivDispatchOscBuffer* DivDispatch::getOscBuffer(int chan) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,6 +213,10 @@ void DivPlatformAmiga::rWrite(unsigned short addr, unsigned short val) {
|
||||||
//logV("%.3x = %.4x",addr,val);
|
//logV("%.3x = %.4x",addr,val);
|
||||||
regPool[addr>>1]=val;
|
regPool[addr>>1]=val;
|
||||||
|
|
||||||
|
if (!skipRegisterWrites && dumpWrites) {
|
||||||
|
addWrite(addr,val);
|
||||||
|
}
|
||||||
|
|
||||||
switch (addr&0x1fe) {
|
switch (addr&0x1fe) {
|
||||||
case 0x96: { // DMACON
|
case 0x96: { // DMACON
|
||||||
if (val&32768) {
|
if (val&32768) {
|
||||||
|
@ -400,6 +404,29 @@ void DivPlatformAmiga::tick(bool sysTick) {
|
||||||
chan[i].keyOn=true;
|
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) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
|
//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);
|
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);
|
chWrite(i,6,chan[i].freq);
|
||||||
|
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
rWrite(0x96,1<<i);
|
|
||||||
if (chan[i].useWave) {
|
if (chan[i].useWave) {
|
||||||
rWrite(0x9a,(128<<i));
|
rWrite(0x9a,(128<<i));
|
||||||
chWrite(i,0,0);
|
chWrite(i,0,0);
|
||||||
chWrite(i,2,i<<8);
|
chWrite(i,2,i<<8);
|
||||||
chWrite(i,4,chan[i].audLen);
|
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 {
|
} else {
|
||||||
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||||
DivSample* s=parent->getSample(chan[i].sample);
|
DivSample* s=parent->getSample(chan[i].sample);
|
||||||
|
@ -432,13 +462,21 @@ void DivPlatformAmiga::tick(bool sysTick) {
|
||||||
chWrite(i,0,0);
|
chWrite(i,0,0);
|
||||||
chWrite(i,2,0x400);
|
chWrite(i,2,0x400);
|
||||||
chWrite(i,4,1);
|
chWrite(i,4,1);
|
||||||
|
if (dumpWrites) {
|
||||||
|
addWrite(0x200+i,0x400);
|
||||||
|
addWrite(0x204+i,1);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
chWrite(i,0,start>>16);
|
chWrite(i,0,start>>16);
|
||||||
chWrite(i,2,start);
|
chWrite(i,2,start);
|
||||||
chWrite(i,4,len);
|
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()) {
|
if (s->isLoopable()) {
|
||||||
int loopPos=(sampleOff[chan[i].sample]+s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT))&(~1);
|
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;
|
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,0,0);
|
||||||
chWrite(i,2,0x400);
|
chWrite(i,2,0x400);
|
||||||
chWrite(i,4,1);
|
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].keyOn) chan[i].keyOn=false;
|
||||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||||
chan[i].freqChanged=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++) {
|
for (int i=0; i<4; i++) {
|
||||||
if (chan[i].writeVol) {
|
if (chan[i].writeVol) {
|
||||||
chan[i].writeVol=false;
|
chan[i].writeVol=false;
|
||||||
|
@ -718,6 +765,18 @@ DivMacroInt* DivPlatformAmiga::getChanMacroInt(int ch) {
|
||||||
return &chan[ch].std;
|
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) {
|
void DivPlatformAmiga::notifyInsChange(int ins) {
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
if (chan[i].ins==ins) {
|
if (chan[i].ins==ins) {
|
||||||
|
|
|
@ -116,6 +116,7 @@ class DivPlatformAmiga: public DivDispatch {
|
||||||
|
|
||||||
friend void putDispatchChip(void*,int);
|
friend void putDispatchChip(void*,int);
|
||||||
friend void putDispatchChan(void*,int,int);
|
friend void putDispatchChan(void*,int,int);
|
||||||
|
friend class DivExportAmigaValidation;
|
||||||
|
|
||||||
void irq(int ch);
|
void irq(int ch);
|
||||||
void rWrite(unsigned short addr, unsigned short val);
|
void rWrite(unsigned short addr, unsigned short val);
|
||||||
|
@ -136,6 +137,7 @@ class DivPlatformAmiga: public DivDispatch {
|
||||||
int getOutputCount();
|
int getOutputCount();
|
||||||
bool keyOffAffectsArp(int ch);
|
bool keyOffAffectsArp(int ch);
|
||||||
DivMacroInt* getChanMacroInt(int ch);
|
DivMacroInt* getChanMacroInt(int ch);
|
||||||
|
DivSamplePos getSamplePos(int ch);
|
||||||
void setFlags(const DivConfig& flags);
|
void setFlags(const DivConfig& flags);
|
||||||
void notifyInsChange(int ins);
|
void notifyInsChange(int ins);
|
||||||
void notifyWaveChange(int wave);
|
void notifyWaveChange(int wave);
|
||||||
|
|
|
@ -700,6 +700,15 @@ DivMacroInt* DivPlatformAY8910::getChanMacroInt(int ch) {
|
||||||
return &chan[ch].std;
|
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) {
|
DivDispatchOscBuffer* DivPlatformAY8910::getOscBuffer(int ch) {
|
||||||
return oscBuf[ch];
|
return oscBuf[ch];
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,7 @@ class DivPlatformAY8910: public DivDispatch {
|
||||||
int getOutputCount();
|
int getOutputCount();
|
||||||
bool keyOffAffectsArp(int ch);
|
bool keyOffAffectsArp(int ch);
|
||||||
DivMacroInt* getChanMacroInt(int ch);
|
DivMacroInt* getChanMacroInt(int ch);
|
||||||
|
DivSamplePos getSamplePos(int ch);
|
||||||
bool getDCOffRequired();
|
bool getDCOffRequired();
|
||||||
void notifyInsDeletion(void* ins);
|
void notifyInsDeletion(void* ins);
|
||||||
void poke(unsigned int addr, unsigned short val);
|
void poke(unsigned int addr, unsigned short val);
|
||||||
|
|
|
@ -696,6 +696,15 @@ DivMacroInt* DivPlatformAY8930::getChanMacroInt(int ch) {
|
||||||
return &chan[ch].std;
|
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) {
|
DivDispatchOscBuffer* DivPlatformAY8930::getOscBuffer(int ch) {
|
||||||
return oscBuf[ch];
|
return oscBuf[ch];
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,6 +145,7 @@ class DivPlatformAY8930: public DivDispatch {
|
||||||
int getOutputCount();
|
int getOutputCount();
|
||||||
bool keyOffAffectsArp(int ch);
|
bool keyOffAffectsArp(int ch);
|
||||||
DivMacroInt* getChanMacroInt(int ch);
|
DivMacroInt* getChanMacroInt(int ch);
|
||||||
|
DivSamplePos getSamplePos(int ch);
|
||||||
void notifyInsDeletion(void* ins);
|
void notifyInsDeletion(void* ins);
|
||||||
void poke(unsigned int addr, unsigned short val);
|
void poke(unsigned int addr, unsigned short val);
|
||||||
void poke(std::vector<DivRegWrite>& wlist);
|
void poke(std::vector<DivRegWrite>& wlist);
|
||||||
|
|
|
@ -336,6 +336,18 @@ DivMacroInt* DivPlatformGA20::getChanMacroInt(int ch) {
|
||||||
return &chan[ch].std;
|
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) {
|
DivDispatchOscBuffer* DivPlatformGA20::getOscBuffer(int ch) {
|
||||||
return oscBuf[ch];
|
return oscBuf[ch];
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ class DivPlatformGA20: public DivDispatch, public iremga20_intf {
|
||||||
virtual int dispatch(DivCommand c) override;
|
virtual int dispatch(DivCommand c) override;
|
||||||
virtual void* getChanState(int chan) override;
|
virtual void* getChanState(int chan) override;
|
||||||
virtual DivMacroInt* getChanMacroInt(int ch) override;
|
virtual DivMacroInt* getChanMacroInt(int ch) override;
|
||||||
|
virtual DivSamplePos getSamplePos(int ch) override;
|
||||||
virtual DivDispatchOscBuffer* getOscBuffer(int chan) override;
|
virtual DivDispatchOscBuffer* getOscBuffer(int chan) override;
|
||||||
virtual unsigned char* getRegisterPool() override;
|
virtual unsigned char* getRegisterPool() override;
|
||||||
virtual int getRegisterPoolSize() override;
|
virtual int getRegisterPoolSize() override;
|
||||||
|
|
|
@ -172,7 +172,14 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) {
|
||||||
flushFirst=false;
|
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);
|
//OPN2_Write(&fm,0,0);
|
||||||
if (i==5) {
|
if (i==5) {
|
||||||
if (fm.dacen) {
|
if (fm.dacen) {
|
||||||
|
@ -1202,6 +1209,17 @@ DivMacroInt* DivPlatformGenesis::getChanMacroInt(int ch) {
|
||||||
return &chan[ch].std;
|
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) {
|
DivDispatchOscBuffer* DivPlatformGenesis::getOscBuffer(int ch) {
|
||||||
return oscBuf[ch];
|
return oscBuf[ch];
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
|
||||||
int dispatch(DivCommand c);
|
int dispatch(DivCommand c);
|
||||||
void* getChanState(int chan);
|
void* getChanState(int chan);
|
||||||
DivMacroInt* getChanMacroInt(int ch);
|
DivMacroInt* getChanMacroInt(int ch);
|
||||||
|
DivSamplePos getSamplePos(int ch);
|
||||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||||
unsigned char* getRegisterPool();
|
unsigned char* getRegisterPool();
|
||||||
int getRegisterPoolSize();
|
int getRegisterPoolSize();
|
||||||
|
|
|
@ -415,6 +415,16 @@ DivMacroInt* DivPlatformLynx::getChanMacroInt(int ch) {
|
||||||
return &chan[ch].std;
|
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) {
|
DivDispatchOscBuffer* DivPlatformLynx::getOscBuffer(int ch) {
|
||||||
return oscBuf[ch];
|
return oscBuf[ch];
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ class DivPlatformLynx: public DivDispatch {
|
||||||
int dispatch(DivCommand c);
|
int dispatch(DivCommand c);
|
||||||
void* getChanState(int chan);
|
void* getChanState(int chan);
|
||||||
DivMacroInt* getChanMacroInt(int ch);
|
DivMacroInt* getChanMacroInt(int ch);
|
||||||
|
DivSamplePos getSamplePos(int ch);
|
||||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||||
unsigned char* getRegisterPool();
|
unsigned char* getRegisterPool();
|
||||||
int getRegisterPoolSize();
|
int getRegisterPoolSize();
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
#include "n163.h"
|
#include "n163.h"
|
||||||
#include "../engine.h"
|
#include "../engine.h"
|
||||||
|
#include "../../ta-log.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#define rRead(a,v) n163.addr_w(a); n163.data_r(v);
|
#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) {
|
void DivPlatformN163::updateWaveCh(int ch) {
|
||||||
if (ch<=chanMax) {
|
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);
|
updateWave(ch,-1,chan[ch].wavePos,chan[ch].waveLen);
|
||||||
if (chan[ch].active && !isMuted[ch]) {
|
if (chan[ch].active && !isMuted[ch]) {
|
||||||
chan[ch].volumeChanged=true;
|
chan[ch].volumeChanged=true;
|
||||||
|
@ -337,15 +339,15 @@ int DivPlatformN163::dispatch(DivCommand c) {
|
||||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_N163);
|
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_N163);
|
||||||
if (chan[c.chan].insChanged) {
|
if (chan[c.chan].insChanged) {
|
||||||
chan[c.chan].wave=ins->n163.wave;
|
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].wavePos=ins->n163.wavePos;
|
||||||
chan[c.chan].waveLen=ins->n163.waveLen;
|
chan[c.chan].waveLen=ins->n163.waveLen;
|
||||||
chan[c.chan].waveMode=ins->n163.waveMode;
|
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;
|
chan[c.chan].waveChanged=true;
|
||||||
if (chan[c.chan].waveMode&0x3 || ins->ws.enabled) {
|
if (chan[c.chan].waveMode&0x3 || ins->ws.enabled) {
|
||||||
chan[c.chan].waveUpdated=true;
|
chan[c.chan].waveUpdated=true;
|
||||||
}
|
}
|
||||||
chan[c.chan].insChanged=false;
|
|
||||||
}
|
}
|
||||||
if (c.value!=DIV_NOTE_NULL) {
|
if (c.value!=DIV_NOTE_NULL) {
|
||||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
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].macroInit(ins);
|
||||||
chan[c.chan].ws.init(ins,chan[c.chan].waveLen,15,chan[c.chan].insChanged);
|
chan[c.chan].ws.init(ins,chan[c.chan].waveLen,15,chan[c.chan].insChanged);
|
||||||
|
chan[c.chan].insChanged=false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_NOTE_OFF:
|
case DIV_CMD_NOTE_OFF:
|
||||||
|
|
|
@ -183,6 +183,7 @@ void DivPlatformNamcoWSG::acquire(short** buf, size_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformNamcoWSG::updateWave(int ch) {
|
void DivPlatformNamcoWSG::updateWave(int ch) {
|
||||||
|
if (romMode) return;
|
||||||
if (devType==30) {
|
if (devType==30) {
|
||||||
for (int i=0; i<32; i++) {
|
for (int i=0; i<32; i++) {
|
||||||
((namco_cus30_device*)namco)->namcos1_cus30_w(i+ch*32,chan[ch].ws.output[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(0x1d,(chan[2].freq>>12)&15);
|
||||||
rWrite(0x1e,(chan[2].freq>>16)&15);
|
rWrite(0x1e,(chan[2].freq>>16)&15);
|
||||||
|
|
||||||
rWrite(0x05,0);
|
rWrite(0x05,romMode?(chan[0].wave&7):0);
|
||||||
rWrite(0x0a,1);
|
rWrite(0x0a,romMode?(chan[1].wave&7):1);
|
||||||
rWrite(0x0f,2);
|
rWrite(0x0f,romMode?(chan[2].wave&7):2);
|
||||||
break;
|
break;
|
||||||
case 15:
|
case 15:
|
||||||
for (int i=0; i<8; i++) {
|
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)+0x04,chan[i].freq&0xff);
|
||||||
rWrite((i<<3)+0x05,(chan[i].freq>>8)&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;
|
break;
|
||||||
case 30:
|
case 30:
|
||||||
|
@ -496,10 +497,11 @@ void DivPlatformNamcoWSG::reset() {
|
||||||
if (dumpWrites) {
|
if (dumpWrites) {
|
||||||
addWrite(0xffffffff,0);
|
addWrite(0xffffffff,0);
|
||||||
}
|
}
|
||||||
// TODO: wave memory
|
|
||||||
namco->set_voices(chans);
|
namco->set_voices(chans);
|
||||||
namco->set_stereo((devType==2 || devType==30));
|
namco->set_stereo((devType==2 || devType==30));
|
||||||
namco->device_start(NULL);
|
namco->device_start(NULL);
|
||||||
|
|
||||||
|
updateROMWaves();
|
||||||
}
|
}
|
||||||
|
|
||||||
int DivPlatformNamcoWSG::getOutputCount() {
|
int DivPlatformNamcoWSG::getOutputCount() {
|
||||||
|
@ -510,6 +512,27 @@ bool DivPlatformNamcoWSG::keyOffAffectsArp(int ch) {
|
||||||
return true;
|
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) {
|
void DivPlatformNamcoWSG::notifyWaveChange(int wave) {
|
||||||
for (int i=0; i<chans; i++) {
|
for (int i=0; i<chans; i++) {
|
||||||
if (chan[i].wave==wave) {
|
if (chan[i].wave==wave) {
|
||||||
|
@ -517,6 +540,7 @@ void DivPlatformNamcoWSG::notifyWaveChange(int wave) {
|
||||||
updateWave(i);
|
updateWave(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
updateROMWaves();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformNamcoWSG::notifyInsDeletion(void* ins) {
|
void DivPlatformNamcoWSG::notifyInsDeletion(void* ins) {
|
||||||
|
@ -552,6 +576,8 @@ void DivPlatformNamcoWSG::setFlags(const DivConfig& flags) {
|
||||||
oscBuf[i]->rate=rate;
|
oscBuf[i]->rate=rate;
|
||||||
}
|
}
|
||||||
newNoise=flags.getBool("newNoise",true);
|
newNoise=flags.getBool("newNoise",true);
|
||||||
|
romMode=flags.getBool("romMode",true);
|
||||||
|
if (devType==30) romMode=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformNamcoWSG::poke(unsigned int addr, unsigned short val) {
|
void DivPlatformNamcoWSG::poke(unsigned int addr, unsigned short val) {
|
||||||
|
|
|
@ -50,8 +50,10 @@ class DivPlatformNamcoWSG: public DivDispatch {
|
||||||
namco_audio_device* namco;
|
namco_audio_device* namco;
|
||||||
int devType, chans;
|
int devType, chans;
|
||||||
bool newNoise;
|
bool newNoise;
|
||||||
|
bool romMode;
|
||||||
unsigned char regPool[512];
|
unsigned char regPool[512];
|
||||||
void updateWave(int ch);
|
void updateWave(int ch);
|
||||||
|
void updateROMWaves();
|
||||||
friend void putDispatchChip(void*,int);
|
friend void putDispatchChip(void*,int);
|
||||||
friend void putDispatchChan(void*,int,int);
|
friend void putDispatchChan(void*,int,int);
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -505,6 +505,16 @@ DivMacroInt* DivPlatformPCE::getChanMacroInt(int ch) {
|
||||||
return &chan[ch].std;
|
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) {
|
DivDispatchOscBuffer* DivPlatformPCE::getOscBuffer(int ch) {
|
||||||
return oscBuf[ch];
|
return oscBuf[ch];
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,7 @@ class DivPlatformPCE: public DivDispatch {
|
||||||
int dispatch(DivCommand c);
|
int dispatch(DivCommand c);
|
||||||
void* getChanState(int chan);
|
void* getChanState(int chan);
|
||||||
DivMacroInt* getChanMacroInt(int ch);
|
DivMacroInt* getChanMacroInt(int ch);
|
||||||
|
DivSamplePos getSamplePos(int ch);
|
||||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||||
unsigned char* getRegisterPool();
|
unsigned char* getRegisterPool();
|
||||||
int getRegisterPoolSize();
|
int getRegisterPoolSize();
|
||||||
|
|
|
@ -30,7 +30,7 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
|
||||||
const int depthScale=(15-outDepth);
|
const int depthScale=(15-outDepth);
|
||||||
int output=0;
|
int output=0;
|
||||||
for (size_t h=0; h<len; h++) {
|
for (size_t h=0; h<len; h++) {
|
||||||
if (!chan[0].active || isMuted) {
|
if (!chan[0].active) {
|
||||||
buf[0][h]=0;
|
buf[0][h]=0;
|
||||||
buf[1][h]=0;
|
buf[1][h]=0;
|
||||||
oscBuf->data[oscBuf->needle++]=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;
|
oscBuf->data[oscBuf->needle++]=output;
|
||||||
if (outStereo) {
|
if (outStereo) {
|
||||||
buf[0][h]=((output*chan[0].panL)>>(depthScale+8))<<depthScale;
|
buf[0][h]=((output*chan[0].panL)>>(depthScale+8))<<depthScale;
|
||||||
|
@ -437,6 +441,15 @@ DivMacroInt* DivPlatformPCMDAC::getChanMacroInt(int ch) {
|
||||||
return &chan[0].std;
|
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) {
|
void DivPlatformPCMDAC::notifyInsChange(int ins) {
|
||||||
if (chan[0].ins==ins) {
|
if (chan[0].ins==ins) {
|
||||||
chan[0].insChanged=true;
|
chan[0].insChanged=true;
|
||||||
|
|
|
@ -79,6 +79,7 @@ class DivPlatformPCMDAC: public DivDispatch {
|
||||||
void muteChannel(int ch, bool mute);
|
void muteChannel(int ch, bool mute);
|
||||||
int getOutputCount();
|
int getOutputCount();
|
||||||
DivMacroInt* getChanMacroInt(int ch);
|
DivMacroInt* getChanMacroInt(int ch);
|
||||||
|
DivSamplePos getSamplePos(int ch);
|
||||||
void setFlags(const DivConfig& flags);
|
void setFlags(const DivConfig& flags);
|
||||||
void notifyInsChange(int ins);
|
void notifyInsChange(int ins);
|
||||||
void notifyWaveChange(int wave);
|
void notifyWaveChange(int wave);
|
||||||
|
|
|
@ -40,7 +40,7 @@ void DivPlatformPV1000::acquire(short** buf, size_t len) {
|
||||||
for (size_t h=0; h<len; h++) {
|
for (size_t h=0; h<len; h++) {
|
||||||
short samp;
|
short samp;
|
||||||
samp=d65010g031_sound_tick(&d65010g031,1);
|
samp=d65010g031_sound_tick(&d65010g031,1);
|
||||||
buf[0][h]=samp<<12;
|
buf[0][h]=samp;
|
||||||
for (int i=0; i<3; i++) {
|
for (int i=0; i<3; i++) {
|
||||||
oscBuf[i]->data[oscBuf[i]->needle++]=(d65010g031.square[i].out<<12);
|
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);
|
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) {
|
int DivPlatformPV1000::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||||
parent=p;
|
parent=p;
|
||||||
dumpWrites=false;
|
dumpWrites=false;
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
#define _PV1000_H
|
#define _PV1000_H
|
||||||
|
|
||||||
#include "../dispatch.h"
|
#include "../dispatch.h"
|
||||||
#include "sound/d65010g031.h"
|
#include "sound/d65modified.h"
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
class DivPlatformPV1000: public DivDispatch {
|
class DivPlatformPV1000: public DivDispatch {
|
||||||
|
@ -55,6 +55,7 @@ class DivPlatformPV1000: public DivDispatch {
|
||||||
void poke(unsigned int addr, unsigned short val);
|
void poke(unsigned int addr, unsigned short val);
|
||||||
void poke(std::vector<DivRegWrite>& wlist);
|
void poke(std::vector<DivRegWrite>& wlist);
|
||||||
const char** getRegisterSheet();
|
const char** getRegisterSheet();
|
||||||
|
bool getDCOffRequired();
|
||||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||||
void quit();
|
void quit();
|
||||||
~DivPlatformPV1000();
|
~DivPlatformPV1000();
|
||||||
|
|
|
@ -307,6 +307,7 @@ void DivPlatformRF5C68::forceIns() {
|
||||||
chan[i].insChanged=true;
|
chan[i].insChanged=true;
|
||||||
chan[i].freqChanged=true;
|
chan[i].freqChanged=true;
|
||||||
chan[i].sample=-1;
|
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;
|
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) {
|
DivDispatchOscBuffer* DivPlatformSegaPCM::getOscBuffer(int ch) {
|
||||||
return oscBuf[ch];
|
return oscBuf[ch];
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,7 @@ class DivPlatformSegaPCM: public DivDispatch {
|
||||||
int dispatch(DivCommand c);
|
int dispatch(DivCommand c);
|
||||||
void* getChanState(int chan);
|
void* getChanState(int chan);
|
||||||
DivMacroInt* getChanMacroInt(int ch);
|
DivMacroInt* getChanMacroInt(int ch);
|
||||||
|
DivSamplePos getSamplePos(int ch);
|
||||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||||
unsigned char* getRegisterPool();
|
unsigned char* getRegisterPool();
|
||||||
int getRegisterPoolSize();
|
int getRegisterPoolSize();
|
||||||
|
|
|
@ -681,6 +681,20 @@ DivMacroInt* DivPlatformSNES::getChanMacroInt(int ch) {
|
||||||
return &chan[ch].std;
|
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) {
|
DivDispatchOscBuffer* DivPlatformSNES::getOscBuffer(int ch) {
|
||||||
return oscBuf[ch];
|
return oscBuf[ch];
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,7 @@ class DivPlatformSNES: public DivDispatch {
|
||||||
int dispatch(DivCommand c);
|
int dispatch(DivCommand c);
|
||||||
void* getChanState(int chan);
|
void* getChanState(int chan);
|
||||||
DivMacroInt* getChanMacroInt(int ch);
|
DivMacroInt* getChanMacroInt(int ch);
|
||||||
|
DivSamplePos getSamplePos(int ch);
|
||||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||||
unsigned char* getRegisterPool();
|
unsigned char* getRegisterPool();
|
||||||
int getRegisterPoolSize();
|
int getRegisterPoolSize();
|
||||||
|
|
|
@ -34,9 +34,31 @@ freely, subject to the following restrictions:
|
||||||
TODO:
|
TODO:
|
||||||
- needs hardware test
|
- 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>
|
#include <stdlib.h>
|
||||||
|
|
||||||
static int d65010g031_max(int a, int b) { return (a > b) ? a : b; }
|
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;
|
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 d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle)
|
||||||
{
|
{
|
||||||
int out = 0;
|
int out = 0;
|
||||||
for (int i = 0; i < 3; i++)
|
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;
|
return out;
|
||||||
}
|
}
|
|
@ -39,12 +39,19 @@ public:
|
||||||
u8 read(u32 offset);
|
u8 read(u32 offset);
|
||||||
|
|
||||||
inline void set_mute(const int ch, const bool mute) { m_channel[ch & 3].mute = mute; }
|
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
|
// device-level overrides
|
||||||
void device_reset();
|
void device_reset();
|
||||||
|
|
||||||
// sound stream update overrides
|
// sound stream update overrides
|
||||||
void sound_stream_update(short** outputs, int len);
|
void sound_stream_update(short** outputs, int len);
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct channel_def
|
struct channel_def
|
||||||
|
|
|
@ -134,6 +134,18 @@ uint8_t* segapcm_device::get_ram() {
|
||||||
return m_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) {
|
void segapcm_device::mute(int ch, bool doMute) {
|
||||||
m_muted[ch&15]=doMute;
|
m_muted[ch&15]=doMute;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,8 @@ public:
|
||||||
void write(unsigned int offset, uint8_t data);
|
void write(unsigned int offset, uint8_t data);
|
||||||
uint8_t read(unsigned int offset);
|
uint8_t read(unsigned int offset);
|
||||||
uint8_t* get_ram();
|
uint8_t* get_ram();
|
||||||
|
unsigned int get_addr(int ch);
|
||||||
|
bool is_playing(int ch);
|
||||||
void mute(int ch, bool doMute);
|
void mute(int ch, bool doMute);
|
||||||
|
|
||||||
// device-level overrides
|
// device-level overrides
|
||||||
|
|
|
@ -123,6 +123,9 @@ public:
|
||||||
uint8_t t_envx_out;
|
uint8_t t_envx_out;
|
||||||
sample_t out[2]; // Furnace addition, for per-channel oscilloscope
|
sample_t out[2]; // Furnace addition, for per-channel oscilloscope
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Furnace addition, gets a voice
|
||||||
|
const voice_t* get_voice(int n);
|
||||||
private:
|
private:
|
||||||
enum { brr_block_size = 9 };
|
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
|
#if !SPC_NO_COPY_STATE_FUNCS
|
||||||
|
|
||||||
class SPC_State_Copier {
|
class SPC_State_Copier {
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
#include "vic20.h"
|
#include "vic20.h"
|
||||||
#include "../engine.h"
|
#include "../engine.h"
|
||||||
|
#include "../../ta-log.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#define rWrite(a,v) {regPool[(a)]=(v)&0xff; vic_sound_machine_store(vic,a,(v)&0xff);}
|
#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) {
|
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) {
|
void DivPlatformVIC20::tick(bool sysTick) {
|
||||||
|
@ -99,6 +98,20 @@ void DivPlatformVIC20::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
chan[i].freqChanged=true;
|
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].std.wave.had) {
|
||||||
if (chan[i].wave!=chan[i].std.wave.val) {
|
if (chan[i].wave!=chan[i].std.wave.val) {
|
||||||
chan[i].wave=chan[i].std.wave.val&0x0f;
|
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].freqChanged=true;
|
||||||
chan[c.chan].note=c.value;
|
chan[c.chan].note=c.value;
|
||||||
}
|
}
|
||||||
|
chan[c.chan].onOff=true;
|
||||||
chan[c.chan].active=true;
|
chan[c.chan].active=true;
|
||||||
chan[c.chan].keyOn=true;
|
chan[c.chan].keyOn=true;
|
||||||
chan[c.chan].macroInit(ins);
|
chan[c.chan].macroInit(ins);
|
||||||
|
|
|
@ -27,10 +27,12 @@
|
||||||
class DivPlatformVIC20: public DivDispatch {
|
class DivPlatformVIC20: public DivDispatch {
|
||||||
struct Channel: public SharedChannel<int> {
|
struct Channel: public SharedChannel<int> {
|
||||||
int wave, waveWriteCycle;
|
int wave, waveWriteCycle;
|
||||||
|
bool onOff;
|
||||||
Channel():
|
Channel():
|
||||||
SharedChannel<int>(15),
|
SharedChannel<int>(15),
|
||||||
wave(0),
|
wave(0),
|
||||||
waveWriteCycle(-1) {}
|
waveWriteCycle(-1),
|
||||||
|
onOff(true) {}
|
||||||
};
|
};
|
||||||
Channel chan[4];
|
Channel chan[4];
|
||||||
DivDispatchOscBuffer* oscBuf[4];
|
DivDispatchOscBuffer* oscBuf[4];
|
||||||
|
|
|
@ -448,6 +448,16 @@ DivMacroInt* DivPlatformVRC6::getChanMacroInt(int ch) {
|
||||||
return &chan[ch].std;
|
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) {
|
DivDispatchOscBuffer* DivPlatformVRC6::getOscBuffer(int ch) {
|
||||||
return oscBuf[ch];
|
return oscBuf[ch];
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ class DivPlatformVRC6: public DivDispatch, public vrcvi_intf {
|
||||||
int dispatch(DivCommand c);
|
int dispatch(DivCommand c);
|
||||||
void* getChanState(int chan);
|
void* getChanState(int chan);
|
||||||
DivMacroInt* getChanMacroInt(int ch);
|
DivMacroInt* getChanMacroInt(int ch);
|
||||||
|
DivSamplePos getSamplePos(int ch);
|
||||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||||
unsigned char* getRegisterPool();
|
unsigned char* getRegisterPool();
|
||||||
int getRegisterPoolSize();
|
int getRegisterPoolSize();
|
||||||
|
|
|
@ -278,7 +278,7 @@ int DivEngine::dispatchCmd(DivCommand c) {
|
||||||
cmdStream.push_back(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 (output->midiOut->isDeviceOpen()) {
|
||||||
if (midiOutMode==DIV_MIDI_MODE_NOTE) {
|
if (midiOutMode==DIV_MIDI_MODE_NOTE) {
|
||||||
int scaledVol=(chan[c.chan].volume*127)/MAX(1,chan[c.chan].volMax);
|
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;
|
chan[c.chan].curMidiNote=-1;
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_INSTRUMENT:
|
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));
|
output->midiOut->send(TAMidiMessage(0xc0|(c.chan&15),c.value,0));
|
||||||
}
|
}
|
||||||
break;
|
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;
|
firstTick=false;
|
||||||
|
|
||||||
if (shallStop) {
|
if (shallStop) {
|
||||||
|
|
|
@ -236,10 +236,14 @@ String SafeReader::readString(size_t stlen) {
|
||||||
#endif
|
#endif
|
||||||
size_t curPos=0;
|
size_t curPos=0;
|
||||||
if (isEOF()) throw EndOfFileException(this, len);
|
if (isEOF()) throw EndOfFileException(this, len);
|
||||||
|
bool zero=false;
|
||||||
|
|
||||||
while (!isEOF() && curPos<stlen) {
|
while (!isEOF() && curPos<stlen) {
|
||||||
unsigned char c=readC();
|
unsigned char c=readC();
|
||||||
if (c!=0) ret.push_back(c);
|
if (c==0) {
|
||||||
|
zero=true;
|
||||||
|
}
|
||||||
|
if (!zero) ret.push_back(c);
|
||||||
curPos++;
|
curPos++;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -144,6 +144,16 @@ int SafeWriter::writeI(int val) {
|
||||||
return write(&val,4);
|
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) {
|
int SafeWriter::writeL(int64_t val) {
|
||||||
return write(&val,8);
|
return write(&val,8);
|
||||||
}
|
}
|
||||||
|
|
|
@ -789,7 +789,7 @@ void DivEngine::registerSystems() {
|
||||||
{"S1", "S2", "S3"},
|
{"S1", "S2", "S3"},
|
||||||
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
|
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
|
||||||
{DIV_INS_AY, DIV_INS_AY, DIV_INS_AY},
|
{DIV_INS_AY, DIV_INS_AY, DIV_INS_AY},
|
||||||
{},
|
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
|
||||||
{},
|
{},
|
||||||
ayPostEffectHandlerMap
|
ayPostEffectHandlerMap
|
||||||
);
|
);
|
||||||
|
@ -870,7 +870,7 @@ void DivEngine::registerSystems() {
|
||||||
{"S1", "S2", "S3"},
|
{"S1", "S2", "S3"},
|
||||||
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
|
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
|
||||||
{DIV_INS_AY8930, DIV_INS_AY8930, DIV_INS_AY8930},
|
{DIV_INS_AY8930, DIV_INS_AY8930, DIV_INS_AY8930},
|
||||||
{},
|
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
|
||||||
{},
|
{},
|
||||||
ay8930PostEffectHandlerMap
|
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);
|
logD("writing stream command %x:%x with stream ID %d",write.addr,write.val,streamID);
|
||||||
switch (write.addr&0xff) {
|
switch (write.addr&0xff) {
|
||||||
case 0: // play sample
|
case 0: // play sample
|
||||||
if (write.val<song.sampleLen) {
|
if (write.val<(unsigned int)song.sampleLen) {
|
||||||
if (playingSample[streamID]!=write.val) {
|
if (playingSample[streamID]!=(int)write.val) {
|
||||||
pendingFreq[streamID]=write.val;
|
pendingFreq[streamID]=write.val;
|
||||||
} else {
|
} else {
|
||||||
DivSample* sample=song.sample[write.val];
|
DivSample* sample=song.sample[write.val];
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "waveSynth.h"
|
#include "waveSynth.h"
|
||||||
#include "engine.h"
|
#include "engine.h"
|
||||||
#include "instrument.h"
|
#include "instrument.h"
|
||||||
|
#include "../ta-log.h"
|
||||||
|
|
||||||
bool DivWaveSynth::activeChanged() {
|
bool DivWaveSynth::activeChanged() {
|
||||||
if (activeChangedB) {
|
if (activeChangedB) {
|
||||||
|
@ -211,6 +212,7 @@ void DivWaveSynth::setWidth(int val) {
|
||||||
|
|
||||||
void DivWaveSynth::changeWave1(int num) {
|
void DivWaveSynth::changeWave1(int num) {
|
||||||
DivWavetable* w1=e->getWave(num);
|
DivWavetable* w1=e->getWave(num);
|
||||||
|
logV("changeWave1 (%d)",width);
|
||||||
if (width<1) return;
|
if (width<1) return;
|
||||||
for (int i=0; i<width; i++) {
|
for (int i=0; i<width; i++) {
|
||||||
if (w1->max<1 || w1->len<1) {
|
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))) {
|
if (ImGui::Selectable(name.c_str(),(i==-1)?(curIns<0 || curIns>=e->song.insLen):(curIns==i))) {
|
||||||
curIns=i;
|
curIns=i;
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
|
updateFMPreview=true;
|
||||||
}
|
}
|
||||||
if (wantScrollList && curIns==i) ImGui::SetScrollHereY();
|
if (wantScrollList && curIns==i) ImGui::SetScrollHereY();
|
||||||
if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) {
|
if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) {
|
||||||
nextWindow=GUI_WINDOW_PATTERN;
|
nextWindow=GUI_WINDOW_PATTERN;
|
||||||
curIns=i;
|
curIns=i;
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
|
updateFMPreview=true;
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered() && i>=0 && !mobileUI) {
|
if (ImGui::IsItemHovered() && i>=0 && !mobileUI) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
|
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
|
||||||
|
@ -474,6 +476,7 @@ void FurnaceGUI::drawInsList(bool asChild) {
|
||||||
if (i>=0) {
|
if (i>=0) {
|
||||||
if (ImGui::BeginPopupContextItem("InsRightMenu")) {
|
if (ImGui::BeginPopupContextItem("InsRightMenu")) {
|
||||||
curIns=i;
|
curIns=i;
|
||||||
|
updateFMPreview=true;
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
|
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
|
||||||
if (ImGui::MenuItem("replace...")) {
|
if (ImGui::MenuItem("replace...")) {
|
||||||
doAction((curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN);
|
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();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Pattern Advance")) e->haltWhen(DIV_HALT_PATTERN);
|
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();
|
if (ImGui::Button("Panic")) e->syncReset();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Abort")) {
|
if (ImGui::Button("Abort")) {
|
||||||
|
@ -519,6 +521,14 @@ void FurnaceGUI::drawDebug() {
|
||||||
ImGui::InputFloat("maxRr",&maxRr);
|
ImGui::InputFloat("maxRr",&maxRr);
|
||||||
ImGui::TreePop();
|
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::TreeNode("User Interface")) {
|
||||||
if (ImGui::Button("Inspect")) {
|
if (ImGui::Button("Inspect")) {
|
||||||
inspectorOpen=!inspectorOpen;
|
inspectorOpen=!inspectorOpen;
|
||||||
|
|
|
@ -127,6 +127,7 @@ void FurnaceGUI::doAction(int what) {
|
||||||
}
|
}
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
wantScrollList=true;
|
wantScrollList=true;
|
||||||
|
updateFMPreview=true;
|
||||||
break;
|
break;
|
||||||
case GUI_ACTION_INS_DOWN:
|
case GUI_ACTION_INS_DOWN:
|
||||||
if (++curIns>=(int)e->song.ins.size()) {
|
if (++curIns>=(int)e->song.ins.size()) {
|
||||||
|
@ -134,6 +135,7 @@ void FurnaceGUI::doAction(int what) {
|
||||||
}
|
}
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
wantScrollList=true;
|
wantScrollList=true;
|
||||||
|
updateFMPreview=true;
|
||||||
break;
|
break;
|
||||||
case GUI_ACTION_STEP_UP:
|
case GUI_ACTION_STEP_UP:
|
||||||
if (++editStep>64) editStep=64;
|
if (++editStep>64) editStep=64;
|
||||||
|
@ -593,6 +595,7 @@ void FurnaceGUI::doAction(int what) {
|
||||||
wantScrollList=true;
|
wantScrollList=true;
|
||||||
MARK_MODIFIED;
|
MARK_MODIFIED;
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
|
updateFMPreview=true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GUI_ACTION_INS_LIST_DUPLICATE:
|
case GUI_ACTION_INS_LIST_DUPLICATE:
|
||||||
|
@ -606,6 +609,7 @@ void FurnaceGUI::doAction(int what) {
|
||||||
wantScrollList=true;
|
wantScrollList=true;
|
||||||
MARK_MODIFIED;
|
MARK_MODIFIED;
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
|
updateFMPreview=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -653,11 +657,13 @@ void FurnaceGUI::doAction(int what) {
|
||||||
if (--curIns<0) curIns=0;
|
if (--curIns<0) curIns=0;
|
||||||
wantScrollList=true;
|
wantScrollList=true;
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
|
updateFMPreview=true;
|
||||||
break;
|
break;
|
||||||
case GUI_ACTION_INS_LIST_DOWN:
|
case GUI_ACTION_INS_LIST_DOWN:
|
||||||
if (++curIns>=(int)e->song.ins.size()) curIns=((int)e->song.ins.size())-1;
|
if (++curIns>=(int)e->song.ins.size()) curIns=((int)e->song.ins.size())-1;
|
||||||
wantScrollList=true;
|
wantScrollList=true;
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
|
updateFMPreview=true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GUI_ACTION_WAVE_LIST_ADD:
|
case GUI_ACTION_WAVE_LIST_ADD:
|
||||||
|
@ -1366,6 +1372,7 @@ void FurnaceGUI::doAction(int what) {
|
||||||
nextWindow=GUI_WINDOW_INS_EDIT;
|
nextWindow=GUI_WINDOW_INS_EDIT;
|
||||||
MARK_MODIFIED;
|
MARK_MODIFIED;
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
|
updateFMPreview=true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,9 @@
|
||||||
|
|
||||||
#define _USE_MATH_DEFINES
|
#define _USE_MATH_DEFINES
|
||||||
#include "gui.h"
|
#include "gui.h"
|
||||||
|
#include "../fileutils.h"
|
||||||
#include "IconsFontAwesome4.h"
|
#include "IconsFontAwesome4.h"
|
||||||
|
#include "misc/cpp/imgui_stdlib.h"
|
||||||
#include <fmt/printf.h>
|
#include <fmt/printf.h>
|
||||||
|
|
||||||
// 0: all directions
|
// 0: all directions
|
||||||
|
@ -514,11 +516,31 @@ void FurnaceGUI::drawMobileControls() {
|
||||||
openFileDialog(GUI_FILE_EXPORT_VGM);
|
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();
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case GUI_SCENE_CHANNELS:
|
case GUI_SCENE_CHANNELS:
|
||||||
|
@ -575,6 +597,40 @@ void FurnaceGUI::drawMobileControls() {
|
||||||
if (ImGui::Button("Switch to Desktop Mode")) {
|
if (ImGui::Button("Switch to Desktop Mode")) {
|
||||||
toggleMobileUI(!mobileUI);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1005,6 +1005,13 @@ void FurnaceGUI::doUndo() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (curOrder>=e->curSubSong->ordersLen) {
|
||||||
|
curOrder=e->curSubSong->ordersLen-1;
|
||||||
|
oldOrder=curOrder;
|
||||||
|
oldOrder1=curOrder;
|
||||||
|
e->setOrder(curOrder);
|
||||||
|
}
|
||||||
|
|
||||||
undoHist.pop_back();
|
undoHist.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1058,5 +1065,12 @@ void FurnaceGUI::doRedo() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (curOrder>=e->curSubSong->ordersLen) {
|
||||||
|
curOrder=e->curSubSong->ordersLen-1;
|
||||||
|
oldOrder=curOrder;
|
||||||
|
oldOrder1=curOrder;
|
||||||
|
e->setOrder(curOrder);
|
||||||
|
}
|
||||||
|
|
||||||
redoHist.pop_back();
|
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) {
|
if (settings.absorbInsInput) {
|
||||||
curIns=pat->data[cursor.y][target];
|
curIns=pat->data[cursor.y][target];
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
|
updateFMPreview=true;
|
||||||
}
|
}
|
||||||
makeUndo(GUI_UNDO_PATTERN_EDIT);
|
makeUndo(GUI_UNDO_PATTERN_EDIT);
|
||||||
if (direct) {
|
if (direct) {
|
||||||
|
@ -1736,9 +1737,18 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
||||||
if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir();
|
if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir();
|
||||||
hasOpened=fileDialog->openSave(
|
hasOpened=fileDialog->openSave(
|
||||||
"Export Command Stream",
|
"Export Command Stream",
|
||||||
{"text file", "*.txt",
|
{"text file", "*.txt"},
|
||||||
"binary file", "*.bin"},
|
"text file{.txt}",
|
||||||
"text file{.txt},binary file{.bin}",
|
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,
|
workingDirROMExport,
|
||||||
dpiScale
|
dpiScale
|
||||||
);
|
);
|
||||||
|
@ -1839,6 +1849,17 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
||||||
dpiScale
|
dpiScale
|
||||||
);
|
);
|
||||||
break;
|
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:
|
case GUI_FILE_TEST_OPEN:
|
||||||
if (!dirExists(workingDirTest)) workingDirTest=getHomeDir();
|
if (!dirExists(workingDirTest)) workingDirTest=getHomeDir();
|
||||||
hasOpened=fileDialog->openLoad(
|
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) {
|
void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
|
||||||
e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut);
|
e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut);
|
||||||
displayExporting=true;
|
displayExporting=true;
|
||||||
|
@ -3404,6 +3483,7 @@ bool FurnaceGUI::loop() {
|
||||||
curIns=msg.data[0];
|
curIns=msg.data[0];
|
||||||
if (curIns>=(int)e->song.ins.size()) curIns=e->song.ins.size()-1;
|
if (curIns>=(int)e->song.ins.size()) curIns=e->song.ins.size()-1;
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
|
updateFMPreview=true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TA_MIDI_CONTROL:
|
case TA_MIDI_CONTROL:
|
||||||
|
@ -3668,18 +3748,53 @@ bool FurnaceGUI::loop() {
|
||||||
}
|
}
|
||||||
if (numZSMCompat > 0) {
|
if (numZSMCompat > 0) {
|
||||||
if (ImGui::BeginMenu("export ZSM...")) {
|
if (ImGui::BeginMenu("export ZSM...")) {
|
||||||
ImGui::Text("Commander X16 Zsound Music File");
|
ImGui::Text("Commander X16 Zsound Music File");
|
||||||
if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) {
|
if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) {
|
||||||
if (zsmExportTickRate<1) zsmExportTickRate=1;
|
if (zsmExportTickRate<1) zsmExportTickRate=1;
|
||||||
if (zsmExportTickRate>44100) zsmExportTickRate=44100;
|
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);
|
for (DivROMExportOutput& i: out) {
|
||||||
ImGui::SameLine();
|
String path=workingDirROMExport+i.name;
|
||||||
if (ImGui::Button("Begin Export")) {
|
FILE* outFile=ps_fopen(path.c_str(),"wb");
|
||||||
openFileDialog(GUI_FILE_EXPORT_ZSM);
|
if (outFile!=NULL) {
|
||||||
ImGui::CloseCurrentPopup();
|
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...")) {
|
if (ImGui::BeginMenu("export command stream...")) {
|
||||||
|
@ -3690,7 +3805,10 @@ bool FurnaceGUI::loop() {
|
||||||
|
|
||||||
"technical/development use only!"
|
"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);
|
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM);
|
||||||
}
|
}
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
|
@ -4123,6 +4241,7 @@ bool FurnaceGUI::loop() {
|
||||||
} else {
|
} else {
|
||||||
curIns=prevIns;
|
curIns=prevIns;
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
|
updateFMPreview=true;
|
||||||
}
|
}
|
||||||
prevIns=-3;
|
prevIns=-3;
|
||||||
}
|
}
|
||||||
|
@ -4167,6 +4286,7 @@ bool FurnaceGUI::loop() {
|
||||||
break;
|
break;
|
||||||
case GUI_FILE_EXPORT_ROM:
|
case GUI_FILE_EXPORT_ROM:
|
||||||
case GUI_FILE_EXPORT_CMDSTREAM:
|
case GUI_FILE_EXPORT_CMDSTREAM:
|
||||||
|
case GUI_FILE_EXPORT_CMDSTREAM_BINARY:
|
||||||
workingDirROMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
workingDirROMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||||
break;
|
break;
|
||||||
case GUI_FILE_LOAD_MAIN_FONT:
|
case GUI_FILE_LOAD_MAIN_FONT:
|
||||||
|
@ -4190,6 +4310,9 @@ bool FurnaceGUI::loop() {
|
||||||
case GUI_FILE_MU5_ROM_OPEN:
|
case GUI_FILE_MU5_ROM_OPEN:
|
||||||
workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||||
break;
|
break;
|
||||||
|
case GUI_FILE_CMDSTREAM_OPEN:
|
||||||
|
workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||||
|
break;
|
||||||
case GUI_FILE_TEST_OPEN:
|
case GUI_FILE_TEST_OPEN:
|
||||||
case GUI_FILE_TEST_OPEN_MULTI:
|
case GUI_FILE_TEST_OPEN_MULTI:
|
||||||
case GUI_FILE_TEST_SAVE:
|
case GUI_FILE_TEST_SAVE:
|
||||||
|
@ -4254,9 +4377,10 @@ bool FurnaceGUI::loop() {
|
||||||
checkExtension(".zsm");
|
checkExtension(".zsm");
|
||||||
}
|
}
|
||||||
if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM) {
|
if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM) {
|
||||||
// we can't tell whether the user chose .txt or .bin in the system file picker
|
checkExtension(".txt");
|
||||||
const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="text file")?".txt":".bin";
|
}
|
||||||
checkExtensionDual(".txt",".bin",fallbackExt);
|
if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM_BINARY) {
|
||||||
|
checkExtension(".bin");
|
||||||
}
|
}
|
||||||
if (curFileDialog==GUI_FILE_EXPORT_COLORS) {
|
if (curFileDialog==GUI_FILE_EXPORT_COLORS) {
|
||||||
checkExtension(".cfgc");
|
checkExtension(".cfgc");
|
||||||
|
@ -4598,15 +4722,9 @@ bool FurnaceGUI::loop() {
|
||||||
case GUI_FILE_EXPORT_ROM:
|
case GUI_FILE_EXPORT_ROM:
|
||||||
showError("Coming soon!");
|
showError("Coming soon!");
|
||||||
break;
|
break;
|
||||||
case GUI_FILE_EXPORT_CMDSTREAM: {
|
case GUI_FILE_EXPORT_CMDSTREAM:
|
||||||
String lowerCase=fileName;
|
case GUI_FILE_EXPORT_CMDSTREAM_BINARY: {
|
||||||
for (char& i: lowerCase) {
|
bool isBinary=(curFileDialog==GUI_FILE_EXPORT_CMDSTREAM_BINARY);
|
||||||
if (i>='A' && i<='Z') i+='a'-'A';
|
|
||||||
}
|
|
||||||
bool isBinary=true;
|
|
||||||
if ((lowerCase.size()<4 || lowerCase.rfind(".bin")!=lowerCase.size()-4)) {
|
|
||||||
isBinary=false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SafeWriter* w=e->saveCommand(isBinary);
|
SafeWriter* w=e->saveCommand(isBinary);
|
||||||
if (w!=NULL) {
|
if (w!=NULL) {
|
||||||
|
@ -4660,6 +4778,11 @@ bool FurnaceGUI::loop() {
|
||||||
case GUI_FILE_MU5_ROM_OPEN:
|
case GUI_FILE_MU5_ROM_OPEN:
|
||||||
settings.mu5Path=copyOfName;
|
settings.mu5Path=copyOfName;
|
||||||
break;
|
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:
|
case GUI_FILE_TEST_OPEN:
|
||||||
showWarning(fmt::sprintf("You opened: %s",copyOfName),GUI_WARN_GENERIC);
|
showWarning(fmt::sprintf("You opened: %s",copyOfName),GUI_WARN_GENERIC);
|
||||||
break;
|
break;
|
||||||
|
@ -5120,6 +5243,7 @@ bool FurnaceGUI::loop() {
|
||||||
if (i!=DIV_INS_AMIGA) e->song.ins[curIns]->amiga.useSample=true;
|
if (i!=DIV_INS_AMIGA) e->song.ins[curIns]->amiga.useSample=true;
|
||||||
nextWindow=GUI_WINDOW_INS_EDIT;
|
nextWindow=GUI_WINDOW_INS_EDIT;
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
|
updateFMPreview=true;
|
||||||
}
|
}
|
||||||
MARK_MODIFIED;
|
MARK_MODIFIED;
|
||||||
}
|
}
|
||||||
|
@ -5435,6 +5559,7 @@ bool FurnaceGUI::init() {
|
||||||
waveSigned=e->getConfBool("waveSigned",false);
|
waveSigned=e->getConfBool("waveSigned",false);
|
||||||
waveGenVisible=e->getConfBool("waveGenVisible",false);
|
waveGenVisible=e->getConfBool("waveGenVisible",false);
|
||||||
waveEditStyle=e->getConfInt("waveEditStyle",0);
|
waveEditStyle=e->getConfInt("waveEditStyle",0);
|
||||||
|
extraChannelButtons=e->getConfInt("extraChannelButtons",0);
|
||||||
lockLayout=e->getConfBool("lockLayout",false);
|
lockLayout=e->getConfBool("lockLayout",false);
|
||||||
#ifdef IS_MOBILE
|
#ifdef IS_MOBILE
|
||||||
fullScreen=true;
|
fullScreen=true;
|
||||||
|
@ -5849,6 +5974,7 @@ void FurnaceGUI::commitState() {
|
||||||
e->setConf("waveSigned",waveSigned);
|
e->setConf("waveSigned",waveSigned);
|
||||||
e->setConf("waveGenVisible",waveGenVisible);
|
e->setConf("waveGenVisible",waveGenVisible);
|
||||||
e->setConf("waveEditStyle",waveEditStyle);
|
e->setConf("waveEditStyle",waveEditStyle);
|
||||||
|
e->setConf("extraChannelButtons",extraChannelButtons);
|
||||||
e->setConf("lockLayout",lockLayout);
|
e->setConf("lockLayout",lockLayout);
|
||||||
e->setConf("fullScreen",fullScreen);
|
e->setConf("fullScreen",fullScreen);
|
||||||
e->setConf("mobileUI",mobileUI);
|
e->setConf("mobileUI",mobileUI);
|
||||||
|
@ -5979,6 +6105,10 @@ FurnaceGUI::FurnaceGUI():
|
||||||
mobileEditButtonPos(0.7f,0.7f),
|
mobileEditButtonPos(0.7f,0.7f),
|
||||||
mobileEditButtonSize(60.0f,60.0f),
|
mobileEditButtonSize(60.0f,60.0f),
|
||||||
curSysSection(NULL),
|
curSysSection(NULL),
|
||||||
|
updateFMPreview(true),
|
||||||
|
fmPreviewOn(false),
|
||||||
|
fmPreviewPaused(false),
|
||||||
|
fmPreviewOPN(NULL),
|
||||||
pendingRawSampleDepth(8),
|
pendingRawSampleDepth(8),
|
||||||
pendingRawSampleChannels(1),
|
pendingRawSampleChannels(1),
|
||||||
pendingRawSampleUnsigned(false),
|
pendingRawSampleUnsigned(false),
|
||||||
|
|
|
@ -68,6 +68,8 @@
|
||||||
|
|
||||||
#define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str()
|
#define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str()
|
||||||
|
|
||||||
|
#define FM_PREVIEW_SIZE 512
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - add colors for FM envelope and waveform
|
// - add colors for FM envelope and waveform
|
||||||
// - maybe add "alternate" color for FM modulators/carriers (a bit difficult)
|
// - maybe add "alternate" color for FM modulators/carriers (a bit difficult)
|
||||||
|
@ -368,6 +370,7 @@ enum FurnaceGUIFileDialogs {
|
||||||
GUI_FILE_EXPORT_VGM,
|
GUI_FILE_EXPORT_VGM,
|
||||||
GUI_FILE_EXPORT_ZSM,
|
GUI_FILE_EXPORT_ZSM,
|
||||||
GUI_FILE_EXPORT_CMDSTREAM,
|
GUI_FILE_EXPORT_CMDSTREAM,
|
||||||
|
GUI_FILE_EXPORT_CMDSTREAM_BINARY,
|
||||||
GUI_FILE_EXPORT_ROM,
|
GUI_FILE_EXPORT_ROM,
|
||||||
GUI_FILE_LOAD_MAIN_FONT,
|
GUI_FILE_LOAD_MAIN_FONT,
|
||||||
GUI_FILE_LOAD_PAT_FONT,
|
GUI_FILE_LOAD_PAT_FONT,
|
||||||
|
@ -380,6 +383,7 @@ enum FurnaceGUIFileDialogs {
|
||||||
GUI_FILE_YRW801_ROM_OPEN,
|
GUI_FILE_YRW801_ROM_OPEN,
|
||||||
GUI_FILE_TG100_ROM_OPEN,
|
GUI_FILE_TG100_ROM_OPEN,
|
||||||
GUI_FILE_MU5_ROM_OPEN,
|
GUI_FILE_MU5_ROM_OPEN,
|
||||||
|
GUI_FILE_CMDSTREAM_OPEN,
|
||||||
|
|
||||||
GUI_FILE_TEST_OPEN,
|
GUI_FILE_TEST_OPEN,
|
||||||
GUI_FILE_TEST_OPEN_MULTI,
|
GUI_FILE_TEST_OPEN_MULTI,
|
||||||
|
@ -1192,6 +1196,9 @@ class FurnaceGUI {
|
||||||
ImVec2 mobileEditButtonPos, mobileEditButtonSize;
|
ImVec2 mobileEditButtonPos, mobileEditButtonSize;
|
||||||
const int* curSysSection;
|
const int* curSysSection;
|
||||||
DivInstrumentFM opllPreview;
|
DivInstrumentFM opllPreview;
|
||||||
|
short fmPreview[FM_PREVIEW_SIZE];
|
||||||
|
bool updateFMPreview, fmPreviewOn, fmPreviewPaused;
|
||||||
|
void* fmPreviewOPN;
|
||||||
|
|
||||||
String pendingRawSample;
|
String pendingRawSample;
|
||||||
int pendingRawSampleDepth, pendingRawSampleChannels;
|
int pendingRawSampleDepth, pendingRawSampleChannels;
|
||||||
|
@ -1357,6 +1364,7 @@ class FurnaceGUI {
|
||||||
int channelFont;
|
int channelFont;
|
||||||
int channelTextCenter;
|
int channelTextCenter;
|
||||||
int midiOutClock;
|
int midiOutClock;
|
||||||
|
int midiOutProgramChange;
|
||||||
int midiOutMode;
|
int midiOutMode;
|
||||||
int maxRecentFile;
|
int maxRecentFile;
|
||||||
int centerPattern;
|
int centerPattern;
|
||||||
|
@ -1369,6 +1377,8 @@ class FurnaceGUI {
|
||||||
int oneDigitEffects;
|
int oneDigitEffects;
|
||||||
int disableFadeIn;
|
int disableFadeIn;
|
||||||
int alwaysPlayIntro;
|
int alwaysPlayIntro;
|
||||||
|
int iCannotWait;
|
||||||
|
int orderButtonPos;
|
||||||
unsigned int maxUndoSteps;
|
unsigned int maxUndoSteps;
|
||||||
String mainFontPath;
|
String mainFontPath;
|
||||||
String patFontPath;
|
String patFontPath;
|
||||||
|
@ -1497,6 +1507,7 @@ class FurnaceGUI {
|
||||||
channelFont(1),
|
channelFont(1),
|
||||||
channelTextCenter(1),
|
channelTextCenter(1),
|
||||||
midiOutClock(0),
|
midiOutClock(0),
|
||||||
|
midiOutProgramChange(0),
|
||||||
midiOutMode(1),
|
midiOutMode(1),
|
||||||
maxRecentFile(10),
|
maxRecentFile(10),
|
||||||
centerPattern(0),
|
centerPattern(0),
|
||||||
|
@ -1509,6 +1520,8 @@ class FurnaceGUI {
|
||||||
oneDigitEffects(0),
|
oneDigitEffects(0),
|
||||||
disableFadeIn(0),
|
disableFadeIn(0),
|
||||||
alwaysPlayIntro(0),
|
alwaysPlayIntro(0),
|
||||||
|
iCannotWait(0),
|
||||||
|
orderButtonPos(2),
|
||||||
maxUndoSteps(100),
|
maxUndoSteps(100),
|
||||||
mainFontPath(""),
|
mainFontPath(""),
|
||||||
patFontPath(""),
|
patFontPath(""),
|
||||||
|
@ -1885,6 +1898,8 @@ class FurnaceGUI {
|
||||||
void drawGBEnv(unsigned char vol, unsigned char len, unsigned char sLen, bool dir, const ImVec2& size);
|
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);
|
bool drawSysConf(int chan, DivSystem type, DivConfig& flags, bool modifyOnChange);
|
||||||
void kvsConfig(DivInstrument* ins);
|
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.
|
// 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);
|
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 drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float availableWidth, int index);
|
||||||
void drawMacros(std::vector<FurnaceGUIMacroDesc>& macros, FurnaceGUIMacroEditState& state);
|
void drawMacros(std::vector<FurnaceGUIMacroDesc>& macros, FurnaceGUIMacroEditState& state);
|
||||||
|
|
||||||
|
void drawOrderButtons();
|
||||||
|
|
||||||
void actualWaveList();
|
void actualWaveList();
|
||||||
void actualSampleList();
|
void actualSampleList();
|
||||||
|
|
||||||
|
@ -1966,7 +1983,7 @@ class FurnaceGUI {
|
||||||
void drawNewSong();
|
void drawNewSong();
|
||||||
void drawLog();
|
void drawLog();
|
||||||
void drawEffectList();
|
void drawEffectList();
|
||||||
void drawSubSongs();
|
void drawSubSongs(bool asChild=false);
|
||||||
void drawFindReplace();
|
void drawFindReplace();
|
||||||
void drawSpoiler();
|
void drawSpoiler();
|
||||||
void drawClock();
|
void drawClock();
|
||||||
|
@ -2058,6 +2075,7 @@ class FurnaceGUI {
|
||||||
void openFileDialog(FurnaceGUIFileDialogs type);
|
void openFileDialog(FurnaceGUIFileDialogs type);
|
||||||
int save(String path, int dmfVersion);
|
int save(String path, int dmfVersion);
|
||||||
int load(String path);
|
int load(String path);
|
||||||
|
int loadStream(String path);
|
||||||
void pushRecentFile(String path);
|
void pushRecentFile(String path);
|
||||||
void exportAudio(String path, DivAudioExportModes mode);
|
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
|
"enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
const char* n163UpdateBits[8]={
|
/*const char* n163UpdateBits[8]={
|
||||||
"now", "every waveform changed", NULL
|
"now", "every waveform changed", NULL
|
||||||
};
|
};*/
|
||||||
|
|
||||||
const char* suControlBits[5]={
|
const char* suControlBits[5]={
|
||||||
"ring mod", "low pass", "high pass", "band pass", NULL
|
"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) { \
|
#define P(x) if (x) { \
|
||||||
MARK_MODIFIED; \
|
MARK_MODIFIED; \
|
||||||
e->notifyInsChange(curIns); \
|
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) {
|
String genericGuide(float value) {
|
||||||
return fmt::sprintf("%d",(int)value);
|
return fmt::sprintf("%d",(int)value);
|
||||||
|
@ -1279,42 +1280,67 @@ inline bool enBit30(const int val) {
|
||||||
|
|
||||||
|
|
||||||
void FurnaceGUI::kvsConfig(DivInstrument* ins) {
|
void FurnaceGUI::kvsConfig(DivInstrument* ins) {
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ins->type==DIV_INS_FM && fmPreviewOn) {
|
||||||
ImGui::SetTooltip("(click to configure TL scaling)");
|
if (ImGui::IsItemHovered()) {
|
||||||
}
|
ImGui::SetTooltip("left click to restart\nmiddle click to pause\nright click to see algorithm");
|
||||||
int opCount=4;
|
}
|
||||||
if (ins->type==DIV_INS_OPLL) opCount=2;
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||||
if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2;
|
updateFMPreview=true;
|
||||||
if (ImGui::BeginPopupContextItem("IKVSOpt",ImGuiPopupFlags_MouseButtonLeft)) {
|
}
|
||||||
ImGui::Text("operator level changes with volume?");
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) {
|
||||||
if (ImGui::BeginTable("KVSTable",4,ImGuiTableFlags_BordersInner)) {
|
fmPreviewPaused=!fmPreviewPaused;
|
||||||
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
|
}
|
||||||
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch);
|
} else {
|
||||||
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed);
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch);
|
ImGui::SetTooltip("left click to configure TL scaling\nright click to see FM preview");
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
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) {
|
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)) {
|
if (ImGui::Selectable(name.c_str(),curIns==(int)i)) {
|
||||||
curIns=i;
|
curIns=i;
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
|
updateFMPreview=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndCombo();
|
ImGui::EndCombo();
|
||||||
|
@ -2148,6 +2175,10 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DivInstrument* ins=e->song.ins[curIns];
|
DivInstrument* ins=e->song.ins[curIns];
|
||||||
|
if (updateFMPreview) {
|
||||||
|
renderFMPreview(ins->fm);
|
||||||
|
updateFMPreview=false;
|
||||||
|
}
|
||||||
if (settings.insEditColorize) {
|
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));
|
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;
|
curIns=i;
|
||||||
ins=e->song.ins[curIns];
|
ins=e->song.ins[curIns];
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
|
updateFMPreview=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndCombo();
|
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_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); rightClickable
|
||||||
P(CWSliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); rightClickable
|
P(CWSliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); rightClickable
|
||||||
ImGui::TableNextColumn();
|
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);
|
kvsConfig(ins);
|
||||||
break;
|
break;
|
||||||
case DIV_INS_OPZ:
|
case DIV_INS_OPZ:
|
||||||
|
@ -4262,9 +4302,18 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
}
|
}
|
||||||
popToggleColors();
|
popToggleColors();
|
||||||
|
|
||||||
P(ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff));
|
if (ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff)) {
|
||||||
P(ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs));
|
ins->std.volMacro.vZoom=-1;
|
||||||
P(ImGui::Checkbox("Absolute Duty Macro",&ins->c64.dutyIsAbs));
|
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));
|
P(ImGui::Checkbox("Don't test/gate before new note",&ins->c64.noTest));
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
|
@ -5116,7 +5165,7 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
dutyMax=ins->amiga.useSample?0:255;
|
dutyMax=ins->amiga.useSample?0:255;
|
||||||
}
|
}
|
||||||
if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC ||
|
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_FM || ins->type==DIV_INS_K007232 || ins->type==DIV_INS_GA20 ||
|
||||||
ins->type==DIV_INS_SM8521 || ins->type==DIV_INS_PV1000) {
|
ins->type==DIV_INS_SM8521 || ins->type==DIV_INS_PV1000) {
|
||||||
dutyMax=0;
|
dutyMax=0;
|
||||||
|
@ -5133,6 +5182,10 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
dutyLabel="Noise";
|
dutyLabel="Noise";
|
||||||
dutyMax=1;
|
dutyMax=1;
|
||||||
}
|
}
|
||||||
|
if (ins->type==DIV_INS_VIC) {
|
||||||
|
dutyLabel="On/Off";
|
||||||
|
dutyMax=1;
|
||||||
|
}
|
||||||
if (ins->type==DIV_INS_SWAN) {
|
if (ins->type==DIV_INS_SWAN) {
|
||||||
dutyLabel="Noise";
|
dutyLabel="Noise";
|
||||||
dutyMax=ins->amiga.useSample?0:8;
|
dutyMax=ins->amiga.useSample?0:8;
|
||||||
|
@ -5149,10 +5202,10 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
dutyLabel="Duty";
|
dutyLabel="Duty";
|
||||||
dutyMax=63;
|
dutyMax=63;
|
||||||
}
|
}
|
||||||
if (ins->type==DIV_INS_N163) {
|
/*if (ins->type==DIV_INS_N163) {
|
||||||
dutyLabel="Waveform pos.";
|
dutyLabel="Waveform pos.";
|
||||||
dutyMax=255;
|
dutyMax=255;
|
||||||
}
|
}*/
|
||||||
if (ins->type==DIV_INS_VRC6) {
|
if (ins->type==DIV_INS_VRC6) {
|
||||||
dutyLabel="Duty";
|
dutyLabel="Duty";
|
||||||
dutyMax=ins->amiga.useSample?0:7;
|
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));
|
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) {
|
} 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));
|
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) {
|
/*} else if (ins->type==DIV_INS_N163) {
|
||||||
macroList.push_back(FurnaceGUIMacroDesc("Wave Length",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
macroList.push_back(FurnaceGUIMacroDesc("Wave Length",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));*/
|
||||||
} else if (ins->type==DIV_INS_FDS) {
|
} else if (ins->type==DIV_INS_FDS) {
|
||||||
macroList.push_back(FurnaceGUIMacroDesc("Mod Depth",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
macroList.push_back(FurnaceGUIMacroDesc("Mod Depth",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||||
} else if (ins->type==DIV_INS_SU) {
|
} else if (ins->type==DIV_INS_SU) {
|
||||||
|
@ -5455,8 +5508,8 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
if (ex2Max>0) {
|
if (ex2Max>0) {
|
||||||
if (ins->type==DIV_INS_C64) {
|
if (ins->type==DIV_INS_C64) {
|
||||||
macroList.push_back(FurnaceGUIMacroDesc("Resonance",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER]));
|
macroList.push_back(FurnaceGUIMacroDesc("Resonance",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||||
} else if (ins->type==DIV_INS_N163) {
|
/*} 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));
|
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) {
|
} else if (ins->type==DIV_INS_FDS) {
|
||||||
macroList.push_back(FurnaceGUIMacroDesc("Mod Speed",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
macroList.push_back(FurnaceGUIMacroDesc("Mod Speed",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||||
} else if (ins->type==DIV_INS_SU) {
|
} else if (ins->type==DIV_INS_SU) {
|
||||||
|
|
|
@ -91,6 +91,122 @@ void FurnaceGUI::drawMobileOrderSel() {
|
||||||
ImGui::End();
|
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() {
|
void FurnaceGUI::drawOrders() {
|
||||||
static char selID[4096];
|
static char selID[4096];
|
||||||
if (nextWindow==GUI_WINDOW_ORDERS) {
|
if (nextWindow==GUI_WINDOW_ORDERS) {
|
||||||
|
@ -107,242 +223,189 @@ void FurnaceGUI::drawOrders() {
|
||||||
} else {
|
} else {
|
||||||
//ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH));
|
//ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH));
|
||||||
}
|
}
|
||||||
if (ImGui::Begin("Orders",&ordersOpen,globalWinFlags)) {
|
if (ImGui::Begin("Orders",&ordersOpen,globalWinFlags|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) {
|
||||||
float regionX=ImGui::GetContentRegionAvail().x;
|
if (ImGui::BeginTable("OrdColumn",(settings.orderButtonPos==0)?1:2,ImGuiTableFlags_BordersInnerV)) {
|
||||||
ImVec2 prevSpacing=ImGui::GetStyle().ItemSpacing;
|
if (settings.orderButtonPos==2) {
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(1.0f*dpiScale,1.0f*dpiScale));
|
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch);
|
||||||
ImGui::Columns(2,NULL,false);
|
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed);
|
||||||
ImGui::SetColumnWidth(-1,regionX-24.0f*dpiScale);
|
} else if (settings.orderButtonPos==1) {
|
||||||
int displayChans=0;
|
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
|
||||||
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch);
|
||||||
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::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) {
|
ImVec2 prevSpacing=ImGui::GetStyle().ItemSpacing;
|
||||||
handleUnimportant;
|
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();
|
ImGui::TableNextRow(0,lineHeight);
|
||||||
for (int j=0; j<e->getTotalChannelCount(); j++) {
|
ImVec2 ra=ImGui::GetContentRegionAvail();
|
||||||
if (!e->curSubSong->chanShow[j]) continue;
|
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::TableNextColumn();
|
||||||
DivPattern* pat=e->curPat[j].getPattern(e->curOrders->ord[j][i],false);
|
ImGui::Text("%s",e->getChannelShortName(i));
|
||||||
/*if (!pat->name.empty()) {
|
}
|
||||||
snprintf(selID,4096,"%s##O_%.2x_%.2x",pat->name.c_str(),j,i);
|
ImGui::PopStyleColor();
|
||||||
} else {*/
|
for (int i=0; i<e->curSubSong->ordersLen; i++) {
|
||||||
snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->curOrders->ord[j][i],j,i);
|
ImGui::TableNextRow(0,lineHeight);
|
||||||
//}
|
if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE]));
|
||||||
|
ImGui::TableNextColumn();
|
||||||
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 ((!followPattern && curOrder==i) || (followPattern && oldOrder1==i)) {
|
||||||
if (ImGui::Selectable(selID,settings.ordersCursor?(cursor.xCoarse==j && oldOrder1!=i):false)) {
|
// draw a border
|
||||||
if (curOrder==i) {
|
ImDrawList* dl=ImGui::GetWindowDrawList();
|
||||||
if (orderEditMode==0) {
|
ImVec2 rBegin=ImGui::GetCursorScreenPos();
|
||||||
prepareUndo(GUI_UNDO_CHANGE_ORDER);
|
rBegin.y-=ImGui::GetStyle().CellPadding.y;
|
||||||
e->lockSave([this,i,j]() {
|
ImVec2 rEnd=ImVec2(rBegin.x+ra.x,rBegin.y+lineHeight);
|
||||||
if (changeAllOrders) {
|
dl->AddRect(rBegin,rEnd,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_SELECTED]),2.0f*dpiScale);
|
||||||
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]++;
|
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]);
|
||||||
}
|
bool highlightLoop=(i>=loopOrder && i<=loopEnd);
|
||||||
} else {
|
if (highlightLoop) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(uiColors[GUI_COLOR_SONG_LOOP]));
|
||||||
if (e->curOrders->ord[j][i]<(unsigned char)(DIV_MAX_PATTERNS-1)) e->curOrders->ord[j][i]++;
|
if (settings.orderRowsBase==1) {
|
||||||
}
|
snprintf(selID,4096,"%.2X##O_S%.2x",i,i);
|
||||||
});
|
} else {
|
||||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
snprintf(selID,4096,"%d##O_S%.2x",i,i);
|
||||||
makeUndo(GUI_UNDO_CHANGE_ORDER);
|
}
|
||||||
} else {
|
if (ImGui::Selectable(selID)) {
|
||||||
orderCursor=j;
|
setOrder(i);
|
||||||
curNibble=false;
|
curNibble=false;
|
||||||
}
|
orderCursor=-1;
|
||||||
} else {
|
|
||||||
setOrder(i);
|
|
||||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
|
||||||
if (orderEditMode!=0) {
|
|
||||||
orderCursor=j;
|
|
||||||
curNibble=false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orderEditMode==0) {
|
if (orderEditMode==0) {
|
||||||
handleUnimportant;
|
handleUnimportant;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
if (orderEditMode!=0 && curOrder==i && orderCursor==j) {
|
for (int j=0; j<e->getTotalChannelCount(); j++) {
|
||||||
// draw a border
|
if (!e->curSubSong->chanShow[j]) continue;
|
||||||
ImDrawList* dl=ImGui::GetWindowDrawList();
|
ImGui::TableNextColumn();
|
||||||
dl->AddRect(ImGui::GetItemRectMin(),ImGui::GetItemRectMax(),ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]),2.0f*dpiScale);
|
DivPattern* pat=e->curPat[j].getPattern(e->curOrders->ord[j][i],false);
|
||||||
}
|
/*if (!pat->name.empty()) {
|
||||||
if (!pat->name.empty() && ImGui::IsItemHovered()) {
|
snprintf(selID,4096,"%s##O_%.2x_%.2x",pat->name.c_str(),j,i);
|
||||||
ImGui::SetTooltip("%s",pat->name.c_str());
|
} else {*/
|
||||||
}
|
snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->curOrders->ord[j][i],j,i);
|
||||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
//}
|
||||||
if (curOrder==i) {
|
|
||||||
if (orderEditMode==0) {
|
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]);
|
||||||
prepareUndo(GUI_UNDO_CHANGE_ORDER);
|
if (ImGui::Selectable(selID,settings.ordersCursor?(cursor.xCoarse==j && oldOrder1!=i):false)) {
|
||||||
e->lockSave([this,i,j]() {
|
if (curOrder==i) {
|
||||||
if (changeAllOrders) {
|
if (orderEditMode==0) {
|
||||||
for (int k=0; k<e->getTotalChannelCount(); k++) {
|
prepareUndo(GUI_UNDO_CHANGE_ORDER);
|
||||||
if (e->curOrders->ord[k][i]>0) e->curOrders->ord[k][i]--;
|
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);
|
||||||
});
|
} else {
|
||||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
orderCursor=j;
|
||||||
makeUndo(GUI_UNDO_CHANGE_ORDER);
|
curNibble=false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
orderCursor=j;
|
setOrder(i);
|
||||||
curNibble=false;
|
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||||
|
if (orderEditMode!=0) {
|
||||||
|
orderCursor=j;
|
||||||
|
curNibble=false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setOrder(i);
|
if (orderEditMode==0) {
|
||||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
handleUnimportant;
|
||||||
if (orderEditMode!=0) {
|
}
|
||||||
orderCursor=j;
|
}
|
||||||
curNibble=false;
|
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::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;
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_ORDERS;
|
||||||
oldOrder1=e->getOrder();
|
oldOrder1=e->getOrder();
|
||||||
|
|
|
@ -1367,6 +1367,7 @@ void FurnaceGUI::drawSampleEdit() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dl->PushClipRect(rectMin,rectMax);
|
||||||
if (e->isPreviewingSample()) {
|
if (e->isPreviewingSample()) {
|
||||||
if (!statusBar2.empty()) {
|
if (!statusBar2.empty()) {
|
||||||
statusBar2+=" | ";
|
statusBar2+=" | ";
|
||||||
|
@ -1380,7 +1381,6 @@ void FurnaceGUI::drawSampleEdit() {
|
||||||
end^=start;
|
end^=start;
|
||||||
start^=end;
|
start^=end;
|
||||||
}
|
}
|
||||||
ImDrawList* dl=ImGui::GetWindowDrawList();
|
|
||||||
ImVec2 p1=rectMin;
|
ImVec2 p1=rectMin;
|
||||||
p1.x+=(e->getSamplePreviewPos()-samplePos)/sampleZoom;
|
p1.x+=(e->getSamplePreviewPos()-samplePos)/sampleZoom;
|
||||||
ImVec4 posColor=uiColors[GUI_COLOR_SAMPLE_NEEDLE];
|
ImVec4 posColor=uiColors[GUI_COLOR_SAMPLE_NEEDLE];
|
||||||
|
@ -1390,8 +1390,8 @@ void FurnaceGUI::drawSampleEdit() {
|
||||||
posTrail2.w=0.0f;
|
posTrail2.w=0.0f;
|
||||||
float trailDistance=(e->getSamplePreviewRate()/100.0f)/sampleZoom;
|
float trailDistance=(e->getSamplePreviewRate()/100.0f)/sampleZoom;
|
||||||
|
|
||||||
if (p1.x<rectMin.x) p1.x=rectMin.x;
|
//if (p1.x<rectMin.x) p1.x=rectMin.x;
|
||||||
if (p1.x>rectMax.x) p1.x=rectMax.x;
|
//if (p1.x>rectMax.x) p1.x=rectMax.x;
|
||||||
|
|
||||||
ImVec2 p2=p1;
|
ImVec2 p2=p1;
|
||||||
p2.y=rectMax.y;
|
p2.y=rectMax.y;
|
||||||
|
@ -1407,6 +1407,46 @@ void FurnaceGUI::drawSampleEdit() {
|
||||||
dl->AddLine(p1,p2,ImGui::GetColorU32(posColor));
|
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) {
|
if (drawSelection) {
|
||||||
int start=sampleSelStart;
|
int start=sampleSelStart;
|
||||||
int end=sampleSelEnd;
|
int end=sampleSelEnd;
|
||||||
|
@ -1415,7 +1455,6 @@ void FurnaceGUI::drawSampleEdit() {
|
||||||
end^=start;
|
end^=start;
|
||||||
start^=end;
|
start^=end;
|
||||||
}
|
}
|
||||||
ImDrawList* dl=ImGui::GetWindowDrawList();
|
|
||||||
ImVec2 p1=rectMin;
|
ImVec2 p1=rectMin;
|
||||||
p1.x+=(start-samplePos)/sampleZoom;
|
p1.x+=(start-samplePos)/sampleZoom;
|
||||||
|
|
||||||
|
|
|
@ -1149,6 +1149,11 @@ void FurnaceGUI::drawSettings() {
|
||||||
settings.midiOutClock=midiOutClockB;
|
settings.midiOutClock=midiOutClockB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool midiOutProgramChangeB=settings.midiOutProgramChange;
|
||||||
|
if (ImGui::Checkbox("Send Program Change",&midiOutProgramChangeB)) {
|
||||||
|
settings.midiOutProgramChange=midiOutProgramChangeB;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1447,6 +1452,17 @@ void FurnaceGUI::drawSettings() {
|
||||||
settings.controlLayout=3;
|
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:");
|
ImGui::Text("FM parameter editor layout:");
|
||||||
if (ImGui::RadioButton("Modern##fml0",settings.fmLayout==0)) {
|
if (ImGui::RadioButton("Modern##fml0",settings.fmLayout==0)) {
|
||||||
settings.fmLayout=0;
|
settings.fmLayout=0;
|
||||||
|
@ -2416,6 +2432,7 @@ void FurnaceGUI::drawSettings() {
|
||||||
// "Debug" - toggles mobile UI
|
// "Debug" - toggles mobile UI
|
||||||
// "Nice Amiga cover of the song!" - enables hidden systems (YMU759/SoundUnit/Dummy)
|
// "Nice Amiga cover of the song!" - enables hidden systems (YMU759/SoundUnit/Dummy)
|
||||||
// "42 63" - enables all instrument types
|
// "42 63" - enables all instrument types
|
||||||
|
// "????" - enables stuff
|
||||||
if (ImGui::BeginTabItem("Cheat Codes")) {
|
if (ImGui::BeginTabItem("Cheat Codes")) {
|
||||||
ImVec2 settingsViewSize=ImGui::GetContentRegionAvail();
|
ImVec2 settingsViewSize=ImGui::GetContentRegionAvail();
|
||||||
settingsViewSize.y-=ImGui::GetFrameHeight()+ImGui::GetStyle().WindowPadding.y;
|
settingsViewSize.y-=ImGui::GetFrameHeight()+ImGui::GetStyle().WindowPadding.y;
|
||||||
|
@ -2607,6 +2624,7 @@ void FurnaceGUI::syncSettings() {
|
||||||
settings.channelTextCenter=e->getConfInt("channelTextCenter",1);
|
settings.channelTextCenter=e->getConfInt("channelTextCenter",1);
|
||||||
settings.maxRecentFile=e->getConfInt("maxRecentFile",10);
|
settings.maxRecentFile=e->getConfInt("maxRecentFile",10);
|
||||||
settings.midiOutClock=e->getConfInt("midiOutClock",0);
|
settings.midiOutClock=e->getConfInt("midiOutClock",0);
|
||||||
|
settings.midiOutProgramChange=e->getConfInt("midiOutProgramChange",0);
|
||||||
settings.midiOutMode=e->getConfInt("midiOutMode",1);
|
settings.midiOutMode=e->getConfInt("midiOutMode",1);
|
||||||
settings.centerPattern=e->getConfInt("centerPattern",0);
|
settings.centerPattern=e->getConfInt("centerPattern",0);
|
||||||
settings.ordersCursor=e->getConfInt("ordersCursor",1);
|
settings.ordersCursor=e->getConfInt("ordersCursor",1);
|
||||||
|
@ -2619,6 +2637,8 @@ void FurnaceGUI::syncSettings() {
|
||||||
settings.disableFadeIn=e->getConfInt("disableFadeIn",0);
|
settings.disableFadeIn=e->getConfInt("disableFadeIn",0);
|
||||||
settings.alwaysPlayIntro=e->getConfInt("alwaysPlayIntro",0);
|
settings.alwaysPlayIntro=e->getConfInt("alwaysPlayIntro",0);
|
||||||
settings.cursorFollowsOrder=e->getConfInt("cursorFollowsOrder",1);
|
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.mainFontSize,2,96);
|
||||||
clampSetting(settings.patFontSize,2,96);
|
clampSetting(settings.patFontSize,2,96);
|
||||||
|
@ -2724,6 +2744,7 @@ void FurnaceGUI::syncSettings() {
|
||||||
clampSetting(settings.channelTextCenter,0,1);
|
clampSetting(settings.channelTextCenter,0,1);
|
||||||
clampSetting(settings.maxRecentFile,0,30);
|
clampSetting(settings.maxRecentFile,0,30);
|
||||||
clampSetting(settings.midiOutClock,0,1);
|
clampSetting(settings.midiOutClock,0,1);
|
||||||
|
clampSetting(settings.midiOutProgramChange,0,1);
|
||||||
clampSetting(settings.midiOutMode,0,2);
|
clampSetting(settings.midiOutMode,0,2);
|
||||||
clampSetting(settings.centerPattern,0,1);
|
clampSetting(settings.centerPattern,0,1);
|
||||||
clampSetting(settings.ordersCursor,0,1);
|
clampSetting(settings.ordersCursor,0,1);
|
||||||
|
@ -2734,6 +2755,8 @@ void FurnaceGUI::syncSettings() {
|
||||||
clampSetting(settings.disableFadeIn,0,1);
|
clampSetting(settings.disableFadeIn,0,1);
|
||||||
clampSetting(settings.alwaysPlayIntro,0,3);
|
clampSetting(settings.alwaysPlayIntro,0,3);
|
||||||
clampSetting(settings.cursorFollowsOrder,0,1);
|
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.exportLoops<0.0) settings.exportLoops=0.0;
|
||||||
if (settings.exportFadeOut<0.0) settings.exportFadeOut=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("channelTextCenter",settings.channelTextCenter);
|
||||||
e->setConf("maxRecentFile",settings.maxRecentFile);
|
e->setConf("maxRecentFile",settings.maxRecentFile);
|
||||||
e->setConf("midiOutClock",settings.midiOutClock);
|
e->setConf("midiOutClock",settings.midiOutClock);
|
||||||
|
e->setConf("midiOutProgramChange",settings.midiOutProgramChange);
|
||||||
e->setConf("midiOutMode",settings.midiOutMode);
|
e->setConf("midiOutMode",settings.midiOutMode);
|
||||||
e->setConf("centerPattern",settings.centerPattern);
|
e->setConf("centerPattern",settings.centerPattern);
|
||||||
e->setConf("ordersCursor",settings.ordersCursor);
|
e->setConf("ordersCursor",settings.ordersCursor);
|
||||||
|
@ -2943,7 +2967,9 @@ void FurnaceGUI::commitSettings() {
|
||||||
e->setConf("oneDigitEffects",settings.oneDigitEffects);
|
e->setConf("oneDigitEffects",settings.oneDigitEffects);
|
||||||
e->setConf("disableFadeIn",settings.disableFadeIn);
|
e->setConf("disableFadeIn",settings.disableFadeIn);
|
||||||
e->setConf("alwaysPlayIntro",settings.alwaysPlayIntro);
|
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
|
// colors
|
||||||
for (int i=0; i<GUI_COLOR_MAX; i++) {
|
for (int i=0; i<GUI_COLOR_MAX; i++) {
|
||||||
|
|
|
@ -4,15 +4,18 @@
|
||||||
#include "misc/cpp/imgui_stdlib.h"
|
#include "misc/cpp/imgui_stdlib.h"
|
||||||
#include "intConst.h"
|
#include "intConst.h"
|
||||||
|
|
||||||
void FurnaceGUI::drawSubSongs() {
|
void FurnaceGUI::drawSubSongs(bool asChild) {
|
||||||
if (nextWindow==GUI_WINDOW_SUBSONGS) {
|
if (nextWindow==GUI_WINDOW_SUBSONGS) {
|
||||||
subSongsOpen=true;
|
subSongsOpen=true;
|
||||||
ImGui::SetNextWindowFocus();
|
ImGui::SetNextWindowFocus();
|
||||||
nextWindow=GUI_WINDOW_NOTHING;
|
nextWindow=GUI_WINDOW_NOTHING;
|
||||||
}
|
}
|
||||||
if (!subSongsOpen) return;
|
if (!subSongsOpen && !asChild) return;
|
||||||
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH));
|
if (!asChild) {
|
||||||
if (ImGui::Begin("Subsongs",&subSongsOpen,globalWinFlags)) {
|
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];
|
char id[1024];
|
||||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing()*2.0f-ImGui::GetStyle().ItemSpacing.x);
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing()*2.0f-ImGui::GetStyle().ItemSpacing.x);
|
||||||
if (e->curSubSong->name.empty()) {
|
if (e->curSubSong->name.empty()) {
|
||||||
|
@ -107,12 +110,16 @@ void FurnaceGUI::drawSubSongs() {
|
||||||
MARK_MODIFIED;
|
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)) {
|
if (ImGui::InputTextMultiline("##SubSongNotes",&e->curSubSong->notes,ImGui::GetContentRegionAvail(),ImGuiInputTextFlags_UndoRedo)) {
|
||||||
MARK_MODIFIED;
|
MARK_MODIFIED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS;
|
if (!asChild && ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS;
|
||||||
ImGui::End();
|
if (asChild) {
|
||||||
|
ImGui::EndChild();
|
||||||
|
} else {
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -621,6 +621,9 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
|
||||||
chipType=3;
|
chipType=3;
|
||||||
altered=true;
|
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);
|
ImGui::BeginDisabled(type==DIV_SYSTEM_AY8910 && chipType==2);
|
||||||
if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) {
|
if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) {
|
||||||
|
@ -1738,6 +1741,27 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
|
||||||
}
|
}
|
||||||
break;
|
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: {
|
case DIV_SYSTEM_NAMCO_CUS30: {
|
||||||
bool newNoise=flags.getBool("newNoise",true);
|
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_VBOY:
|
||||||
case DIV_SYSTEM_GA20:
|
case DIV_SYSTEM_GA20:
|
||||||
case DIV_SYSTEM_PV1000:
|
case DIV_SYSTEM_PV1000:
|
||||||
case DIV_SYSTEM_NAMCO:
|
|
||||||
case DIV_SYSTEM_NAMCO_15XX:
|
|
||||||
ImGui::Text("nothing to configure");
|
|
||||||
break;
|
|
||||||
case DIV_SYSTEM_VERA:
|
case DIV_SYSTEM_VERA:
|
||||||
|
break;
|
||||||
case DIV_SYSTEM_YMU759:
|
case DIV_SYSTEM_YMU759:
|
||||||
supportsCustomRate=false;
|
supportsCustomRate=false;
|
||||||
ImGui::Text("nothing to configure");
|
ImGui::Text("nothing to configure");
|
||||||
|
|
Loading…
Reference in a new issue