mirror of
https://github.com/tildearrow/furnace.git
synced 2024-10-31 18:12:40 +00:00
Merge branch 'master' of https://github.com/tildearrow/furnace into ymf278b
This commit is contained in:
commit
3e1e2fc2a6
73 changed files with 1657 additions and 663 deletions
|
@ -718,8 +718,6 @@ src/engine/wavetable.cpp
|
|||
src/engine/waveSynth.cpp
|
||||
src/engine/wavOps.cpp
|
||||
src/engine/vgmOps.cpp
|
||||
src/engine/zsmOps.cpp
|
||||
src/engine/zsm.cpp
|
||||
|
||||
src/engine/platform/abstract.cpp
|
||||
src/engine/platform/genesis.cpp
|
||||
|
@ -798,6 +796,7 @@ src/engine/platform/dummy.cpp
|
|||
src/engine/export/abstract.cpp
|
||||
src/engine/export/amigaValidation.cpp
|
||||
src/engine/export/tiuna.cpp
|
||||
src/engine/export/zsm.cpp
|
||||
|
||||
src/engine/effect/abstract.cpp
|
||||
src/engine/effect/dummy.cpp
|
||||
|
|
BIN
demos/multichip/pont of noreturn.fur
Normal file
BIN
demos/multichip/pont of noreturn.fur
Normal file
Binary file not shown.
|
@ -61,7 +61,7 @@ if you need to use more samples, you may change the sample bank using effect `EB
|
|||
due to limitations in some of those sound chips, some restrictions exist:
|
||||
|
||||
- Amiga: maximum frequency is 31469Hz, but anything over 28867 will sound glitchy on hardware. sample lengths and loop will be set to an even number, and your sample can't be longer than 131070.
|
||||
- NES: if on DPCM mode, only a limited selection of frequencies is available, and loop position isn't supported (only entire sample).
|
||||
- NES: if on DPCM mode, only a limited selection of frequencies is available.
|
||||
- SegaPCM: your sample can't be longer than 65535, and the maximum frequency is 31.25KHz.
|
||||
- QSound: your sample can't be longer than 65535, and the loop length shall not be greater than 32767.
|
||||
- ADPCM-A: no looping supported. your samples will play at around 18.518KHz.
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
# Sega Genesis/Mega Drive
|
||||
|
||||
a video game console that showed itself as the first true rival to Nintendo's video game market near-monopoly in the US during the '80s.
|
||||
|
||||
this console is powered by two sound chips: the [Yamaha YM2612](ym2612.md) and [a derivative of the SN76489](sms.md).
|
||||
|
||||
## effects
|
||||
|
||||
- `10xy`: **set LFO parameters.**
|
||||
- `x` toggles the LFO.
|
||||
- `y` sets its speed.
|
||||
- `11xx`: **set feedback of channel.**
|
||||
- `12xx`: **set operator 1 level.**
|
||||
- `13xx`: **set operator 2 level.**
|
||||
- `14xx`: **set operator 3 level.**
|
||||
- `15xx`: **set operator 4 level.**
|
||||
- `16xy`: **set multiplier of operator.**
|
||||
- `x` is the operator (1-4).
|
||||
- `y` is the new MULT value..
|
||||
- `17xx`: **enable PCM channel.**
|
||||
- this only works on channel 6.
|
||||
- _this effect is here for compatibility reasons!_ it is otherwise recommended to use Sample type instruments (which automatically enable PCM mode when used).
|
||||
- `18xx`: **toggle extended channel 3 mode.**
|
||||
- `0` disables it and `1` enables it.
|
||||
- only in extended channel 3 chip.
|
||||
- `19xx`: **set attack of all operators.**
|
||||
- `1Axx`: **set attack of operator 1.**
|
||||
- `1Bxx`: **set attack of operator 2.**
|
||||
- `1Cxx`: **set attack of operator 3.**
|
||||
- `1Dxx`: **set attack of operator 4.**
|
||||
- `20xy`: **set PSG noise mode.**
|
||||
- `x` controls whether to inherit frequency from PSG channel 3.
|
||||
- `0`: use one of 3 preset frequencies (`C`: A-2; `C#`: A-3; `D`: A-4).
|
||||
- `1`: use frequency of PSG channel 3.
|
||||
- `y` controls whether to select noise or thin pulse.
|
||||
- `0`: thin pulse.
|
||||
- `1`: noise.
|
||||
|
||||
|
||||
|
||||
## system modes
|
||||
|
||||
## extended channel 3
|
||||
|
||||
in ExtCh mode, channel 3 is split into one column for each of its four operators. feedback and LFO levels are shared. the frequency of each operator may be controlled independently with notes and effects. this can be used for more polyphony or more complex sounds.
|
||||
|
||||
all four operators are still combined according to the algorithm in use. for example, algorithm 7 acts as four independent sine waves. algorithm 4 acts as two independent 2-op sounds. even with algorithm 0, placing a note in any operator triggers that operator alone.
|
||||
|
||||
## CSM
|
||||
|
||||
CSM is short for "Composite Sinusoidal Modeling". CSM works by sending key-on and key-off commands to channel 3 at a specific frequency, controlled by the added "CSM Timer" channel. this can be used to create vocal formants (speech synthesis!) or other complex effects.
|
||||
|
||||
CSM is beyond the scope of this documentation. for more information, see this [brief SSG-EG and CSM video tutorial](https://www.youtube.com/watch?v=IKOR0TUlnWU).
|
||||
|
||||
## DualPCM
|
||||
|
||||
[info here.](ym2612.md)
|
||||
|
||||
## Sega CD
|
||||
|
||||
this isn't a mode so much as a chip configuration. it adds the [Ricoh RF5C68](ricoh.md) found in the Sega CD add-on, providing 8 channels of PCM.
|
||||
|
||||
## chip config
|
||||
|
||||
see [YM2612](ym2612.md) and [SN76489](sms.md) for respective configuration.
|
|
@ -68,6 +68,9 @@ in most arcade boards the chip was used in combination with a PCM chip, like [Se
|
|||
- `5Dxx`: **set D2R/SR of operator 2.**
|
||||
- `5Exx`: **set D2R/SR of operator 3.**
|
||||
- `5Fxx`: **set D2R/SR of operator 4.**
|
||||
- `60xx`: **set operator mask.**
|
||||
- `xx` goes from `0` to `F`. it is a bitfield.
|
||||
- each bit corresponds to an operator.
|
||||
|
||||
## info
|
||||
|
||||
|
|
|
@ -99,6 +99,9 @@ several variants of this chip were released as well, with more features.
|
|||
- `5Dxx`: **set D2R/SR of operator 2.**
|
||||
- `5Exx`: **set D2R/SR of operator 3.**
|
||||
- `5Fxx`: **set D2R/SR of operator 4.**
|
||||
- `60xx`: **set operator mask.**
|
||||
- `xx` goes from `0` to `F`. it is a bitfield.
|
||||
- each bit corresponds to an operator.
|
||||
|
||||
## extended channel 3
|
||||
|
||||
|
|
|
@ -99,6 +99,9 @@ the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built-
|
|||
- `5Dxx`: **set D2R/SR of operator 2.**
|
||||
- `5Exx`: **set D2R/SR of operator 3.**
|
||||
- `5Fxx`: **set D2R/SR of operator 4.**
|
||||
- `60xx`: **set operator mask.**
|
||||
- `xx` goes from `0` to `F`. it is a bitfield.
|
||||
- each bit corresponds to an operator.
|
||||
|
||||
## extended channel 3
|
||||
|
||||
|
|
|
@ -97,6 +97,9 @@ its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and [2 differen
|
|||
- `5Dxx`: **set D2R/SR of operator 2.**
|
||||
- `5Exx`: **set D2R/SR of operator 3.**
|
||||
- `5Fxx`: **set D2R/SR of operator 4.**
|
||||
- `60xx`: **set operator mask.**
|
||||
- `xx` goes from `0` to `F`. it is a bitfield.
|
||||
- each bit corresponds to an operator.
|
||||
|
||||
## extended channel 2
|
||||
|
||||
|
|
|
@ -96,6 +96,9 @@ it is backward compatible with the original chip.
|
|||
- `5Dxx`: **set D2R/SR of operator 2.**
|
||||
- `5Exx`: **set D2R/SR of operator 3.**
|
||||
- `5Fxx`: **set D2R/SR of operator 4.**
|
||||
- `60xx`: **set operator mask.**
|
||||
- `xx` goes from `0` to `F`. it is a bitfield.
|
||||
- each bit corresponds to an operator.
|
||||
|
||||
## extended channel 3
|
||||
|
||||
|
|
|
@ -82,6 +82,9 @@ thanks to the Z80 sound CPU, DualPCM can play two samples at once! this mode spl
|
|||
- `5Dxx`: **set D2R/SR of operator 2.**
|
||||
- `5Exx`: **set D2R/SR of operator 3.**
|
||||
- `5Fxx`: **set D2R/SR of operator 4.**
|
||||
- `60xx`: **set operator mask.**
|
||||
- `xx` goes from `0` to `F`. it is a bitfield.
|
||||
- each bit corresponds to an operator.
|
||||
|
||||
## info
|
||||
|
||||
|
|
16
extern/igfd/dirent/dirent.h
vendored
16
extern/igfd/dirent/dirent.h
vendored
|
@ -1142,13 +1142,6 @@ dirent_mbstowcs_s(
|
|||
{
|
||||
int error;
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1400
|
||||
|
||||
/* Microsoft Visual Studio 2005 or later */
|
||||
error = mbstowcs_s (pReturnValue, wcstr, sizeInWords, mbstr, count);
|
||||
|
||||
#else
|
||||
|
||||
/* Older Visual Studio or non-Microsoft compiler */
|
||||
size_t n;
|
||||
|
||||
|
@ -1179,7 +1172,6 @@ dirent_mbstowcs_s(
|
|||
|
||||
}
|
||||
|
||||
#endif
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -1230,13 +1222,6 @@ dirent_wcstombs_s(
|
|||
{
|
||||
int error;
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1400
|
||||
|
||||
/* Microsoft Visual Studio 2005 or later */
|
||||
error = wcstombs_s (pReturnValue, mbstr, sizeInBytes, wcstr, count);
|
||||
|
||||
#else
|
||||
|
||||
/* Older Visual Studio or non-Microsoft compiler */
|
||||
size_t n;
|
||||
|
||||
|
@ -1268,7 +1253,6 @@ dirent_wcstombs_s(
|
|||
|
||||
}
|
||||
|
||||
#endif
|
||||
return error;
|
||||
}
|
||||
|
||||
|
|
42
po/Reorganize-po-sequence.py
Normal file
42
po/Reorganize-po-sequence.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
def merge_po_files_preserve_format(base_file, new_file, output_file):
|
||||
with open(base_file, 'r', encoding='utf-8') as f1, open(new_file, 'r', encoding='utf-8') as f2:
|
||||
base_lines = f1.readlines()
|
||||
new_lines = f2.readlines()
|
||||
|
||||
# Traverse the new_lines and extract msgid and msgstr
|
||||
new_translations = {}
|
||||
i = 0
|
||||
while i < len(new_lines):
|
||||
if new_lines[i].startswith('msgid '):
|
||||
msgid_start = i
|
||||
while not new_lines[i].startswith('msgstr '):
|
||||
i += 1
|
||||
msgid = ''.join(new_lines[msgid_start:i])
|
||||
msgstr = new_lines[i].split('msgstr ')[1].strip()
|
||||
new_translations[msgid] = msgstr
|
||||
i += 1
|
||||
|
||||
# Open the output_file and write the merged content
|
||||
with open(output_file, 'w', encoding='utf-8') as output:
|
||||
i = 0
|
||||
while i < len(base_lines):
|
||||
if base_lines[i].startswith('msgid '):
|
||||
msgid_start = i
|
||||
while not base_lines[i].startswith('msgstr '):
|
||||
i += 1
|
||||
msgid = ''.join(base_lines[msgid_start:i])
|
||||
if msgid in new_translations:
|
||||
output.write(f'{msgid}')
|
||||
output.write(f'msgstr {new_translations[msgid]}\n')
|
||||
else:
|
||||
output.write(base_lines[i])
|
||||
i += 1
|
||||
else:
|
||||
output.write(base_lines[i])
|
||||
i += 1
|
||||
|
||||
if __name__ == '__main__':
|
||||
base_file = 'base.po'
|
||||
new_file = 'new.po'
|
||||
output_file = 'output.po'
|
||||
merge_po_files_preserve_format(base_file, new_file, output_file)
|
54
scripts/Reorganize-po-sequence.sh
Normal file
54
scripts/Reorganize-po-sequence.sh
Normal file
|
@ -0,0 +1,54 @@
|
|||
#!/bin/bash
|
||||
|
||||
merge_po_files_preserve_format() {
|
||||
base_file="base.po"
|
||||
new_file="new.po"
|
||||
output_file="output.po"
|
||||
|
||||
declare -A new_translations
|
||||
|
||||
# Read new_file and extract msgid and msgstr
|
||||
msgid=""
|
||||
while IFS= read -r line; do
|
||||
if [[ $line == msgid* ]]; then
|
||||
msgid="$line"
|
||||
msgstr=""
|
||||
elif [[ $line == msgstr* ]]; then
|
||||
msgstr="$line"
|
||||
new_translations["$msgid"]="$msgstr"
|
||||
elif [[ $line == "\"\"" ]]; then
|
||||
msgid+="$line"
|
||||
fi
|
||||
done < "$new_file"
|
||||
|
||||
# Open output_file and write merged content
|
||||
msgid=""
|
||||
while IFS= read -r line; do
|
||||
if [[ $line == msgid* ]]; then
|
||||
msgid="$line"
|
||||
msgstr=""
|
||||
elif [[ $line == msgstr* ]]; then
|
||||
msgstr="$line"
|
||||
if [[ -n ${new_translations["$msgid"]} ]]; then
|
||||
echo "$msgid" >> "$output_file"
|
||||
echo "${new_translations["$msgid"]}" >> "$output_file"
|
||||
else
|
||||
echo "$msgid" >> "$output_file"
|
||||
echo "$msgstr" >> "$output_file"
|
||||
fi
|
||||
msgid=""
|
||||
msgstr=""
|
||||
elif [[ $line == "\"\"" ]]; then
|
||||
msgid+="$line"
|
||||
else
|
||||
echo "$line" >> "$output_file"
|
||||
fi
|
||||
done < "$base_file"
|
||||
}
|
||||
|
||||
if [[ $# -ne 3 ]]; then
|
||||
echo "Usage: $0 base_file new_file output_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
merge_po_files_preserve_format "base.po" "new.po" "output.po"
|
|
@ -46,7 +46,9 @@ void* TAAudioSDL::getContext() {
|
|||
bool TAAudioSDL::quit() {
|
||||
if (!initialized) return false;
|
||||
|
||||
SDL_CloseAudioDevice(ai);
|
||||
if (ai!=0) {
|
||||
SDL_CloseAudioDevice(ai);
|
||||
}
|
||||
|
||||
if (running) {
|
||||
running=false;
|
||||
|
|
|
@ -34,5 +34,6 @@ class TAAudioSDL: public TAAudio {
|
|||
std::vector<String> listAudioDevices();
|
||||
bool init(TAAudioDesc& request, TAAudioDesc& response);
|
||||
TAAudioSDL():
|
||||
ai(0),
|
||||
audioSysStarted(false) {}
|
||||
};
|
||||
|
|
|
@ -215,6 +215,10 @@ bool DivCSPlayer::tick() {
|
|||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
arg0=(short)stream.readS();
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE_TARGET:
|
||||
arg0=(short)stream.readS();
|
||||
arg1=(short)stream.readS();
|
||||
break;
|
||||
case DIV_CMD_HINT_LEGATO:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
if (arg0==0xff) {
|
||||
|
@ -321,6 +325,11 @@ bool DivCSPlayer::tick() {
|
|||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
chan[i].volSpeed=arg0;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE_TARGET:
|
||||
chan[i].volSpeed=arg0;
|
||||
chan[i].volSpeedTarget=arg0==0 ? -1 : arg1;
|
||||
break;
|
||||
case DIV_CMD_HINT_PITCH:
|
||||
chan[i].pitch=arg0;
|
||||
|
@ -356,13 +365,29 @@ bool DivCSPlayer::tick() {
|
|||
|
||||
if (sendVolume || chan[i].volSpeed!=0) {
|
||||
chan[i].volume+=chan[i].volSpeed;
|
||||
if (chan[i].volSpeedTarget!=-1) {
|
||||
bool atTarget=false;
|
||||
if (chan[i].volSpeed>0) {
|
||||
atTarget=(chan[i].volume>=chan[i].volSpeedTarget);
|
||||
} else if (chan[i].volSpeed<0) {
|
||||
atTarget=(chan[i].volume<=chan[i].volSpeedTarget);
|
||||
} else {
|
||||
atTarget=true;
|
||||
chan[i].volSpeedTarget=chan[i].volume;
|
||||
}
|
||||
|
||||
if (atTarget) {
|
||||
chan[i].volume=chan[i].volSpeedTarget;
|
||||
chan[i].volSpeed=0;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ struct DivCSChannelState {
|
|||
int lastWaitLen;
|
||||
|
||||
int note, pitch;
|
||||
int volume, volMax, volSpeed;
|
||||
int volume, volMax, volSpeed, volSpeedTarget;
|
||||
int vibratoDepth, vibratoRate, vibratoPos;
|
||||
int portaTarget, portaSpeed;
|
||||
unsigned char arp, arpStage, arpTicks;
|
||||
|
@ -56,6 +56,7 @@ struct DivCSChannelState {
|
|||
volume(0x7f00),
|
||||
volMax(0),
|
||||
volSpeed(0),
|
||||
volSpeedTarget(-1),
|
||||
vibratoDepth(0),
|
||||
vibratoRate(0),
|
||||
vibratoPos(0),
|
||||
|
|
|
@ -59,6 +59,7 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
|
|||
case DIV_CMD_HINT_VOLUME:
|
||||
case DIV_CMD_HINT_PORTA:
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
case DIV_CMD_HINT_VOL_SLIDE_TARGET:
|
||||
case DIV_CMD_HINT_LEGATO:
|
||||
w->writeC((unsigned char)c.cmd+0xb4);
|
||||
break;
|
||||
|
@ -100,6 +101,10 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
|
|||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
w->writeS(c.value);
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE_TARGET:
|
||||
w->writeS(c.value);
|
||||
w->writeS(c.value2);
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
case DIV_CMD_SAMPLE_FREQ:
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
|
|
|
@ -67,6 +67,7 @@ enum DivDispatchCmds {
|
|||
DIV_CMD_HINT_ARPEGGIO, // (note1, note2)
|
||||
DIV_CMD_HINT_VOLUME, // (vol)
|
||||
DIV_CMD_HINT_VOL_SLIDE, // (amount, oneTick)
|
||||
DIV_CMD_HINT_VOL_SLIDE_TARGET, // (amount, target)
|
||||
DIV_CMD_HINT_PORTA, // (target, speed)
|
||||
DIV_CMD_HINT_LEGATO, // (note)
|
||||
|
||||
|
@ -265,6 +266,8 @@ enum DivDispatchCmds {
|
|||
|
||||
DIV_CMD_FDS_MOD_AUTO,
|
||||
|
||||
DIV_CMD_FM_OPMASK, // (mask)
|
||||
|
||||
DIV_CMD_MULTIPCM_MIX_FM, // (value)
|
||||
DIV_CMD_MULTIPCM_MIX_PCM, // (value)
|
||||
DIV_CMD_MULTIPCM_LFO, // (value)
|
||||
|
|
|
@ -98,6 +98,10 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
|
|||
break;
|
||||
case 0xc0: case 0xc1: case 0xc2: case 0xc3:
|
||||
return _("Cxxx: Set tick rate (hz)");
|
||||
case 0xd3:
|
||||
return _("D3xx: Volume portamento");
|
||||
case 0xd4:
|
||||
return _("D4xx: Volume portamento (fast)");
|
||||
case 0xdc:
|
||||
return _("DCxx: Delayed mute");
|
||||
case 0xe0:
|
||||
|
@ -199,6 +203,12 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
|
|||
}
|
||||
}
|
||||
|
||||
void DivEngine::findSongLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders, int& length) {
|
||||
if (curSubSong!=NULL) {
|
||||
curSubSong->findLength(loopOrder,loopRow,fadeoutLen,rowsForFadeout,hasFFxx,orders,song.grooves,length,chans,song.jumpTreatment,song.ignoreJumpAtEnd);
|
||||
}
|
||||
}
|
||||
|
||||
#define EXPORT_BUFSIZE 2048
|
||||
|
||||
double DivEngine::benchmarkPlayback() {
|
||||
|
|
|
@ -133,7 +133,7 @@ struct DivAudioExportOptions {
|
|||
struct DivChannelState {
|
||||
std::vector<DivDelayedCommand> delayed;
|
||||
int note, oldNote, lastIns, pitch, portaSpeed, portaNote;
|
||||
int volume, volSpeed, cut, volCut, legatoDelay, legatoTarget, rowDelay, volMax;
|
||||
int volume, volSpeed, volSpeedTarget, cut, volCut, legatoDelay, legatoTarget, rowDelay, volMax;
|
||||
int delayOrder, delayRow, retrigSpeed, retrigTick;
|
||||
int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoShape, vibratoFine;
|
||||
int tremoloDepth, tremoloRate, tremoloPos;
|
||||
|
@ -157,6 +157,7 @@ struct DivChannelState {
|
|||
portaNote(-1),
|
||||
volume(0x7f00),
|
||||
volSpeed(0),
|
||||
volSpeedTarget(-1),
|
||||
cut(-1),
|
||||
volCut(-1),
|
||||
legatoDelay(-1),
|
||||
|
@ -474,7 +475,7 @@ class DivEngine {
|
|||
int midiOutTimeRate;
|
||||
float midiVolExp;
|
||||
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, curExportChan, nextSpeed, elapsedBars, elapsedBeats, curSpeed;
|
||||
size_t curSubSongIndex;
|
||||
size_t bufferPos;
|
||||
double divider;
|
||||
|
@ -498,6 +499,7 @@ class DivEngine {
|
|||
DivAudioExportModes exportMode;
|
||||
DivAudioExportFormats exportFormat;
|
||||
double exportFadeOut;
|
||||
bool isFadingOut;
|
||||
int exportOutputs;
|
||||
bool exportChannelMask[DIV_MAX_CHANS];
|
||||
DivConfig conf;
|
||||
|
@ -579,7 +581,7 @@ class DivEngine {
|
|||
void processRow(int i, bool afterDelay);
|
||||
void nextOrder();
|
||||
void nextRow();
|
||||
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream);
|
||||
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream, bool* sampleStoppable);
|
||||
// returns true if end of song.
|
||||
bool nextTick(bool noAccum=false, bool inhibitLowLat=false);
|
||||
bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal);
|
||||
|
@ -669,6 +671,7 @@ class DivEngine {
|
|||
friend class DivROMExport;
|
||||
friend class DivExportAmigaValidation;
|
||||
friend class DivExportTiuna;
|
||||
friend class DivExportZSM;
|
||||
|
||||
public:
|
||||
DivSong song;
|
||||
|
@ -721,8 +724,6 @@ class DivEngine {
|
|||
// - -1 to auto-determine trailing
|
||||
// - -2 to add a whole loop of trailing
|
||||
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false, bool directStream=false, int trailingTicks=-1);
|
||||
// dump to ZSM.
|
||||
SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true, bool optimize=true);
|
||||
// dump to TIunA.
|
||||
SafeWriter* saveTiuna(const bool* sysToExport, const char* baseLabel, int firstBankSize, int otherBankSize);
|
||||
// dump command stream.
|
||||
|
@ -817,6 +818,9 @@ class DivEngine {
|
|||
// find song loop position
|
||||
void walkSong(int& loopOrder, int& loopRow, int& loopEnd);
|
||||
|
||||
// find song length in rows (up to specified loop point), and find length of every order
|
||||
void findSongLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders, int& length);
|
||||
|
||||
// play (returns whether successful)
|
||||
bool play();
|
||||
|
||||
|
@ -1008,6 +1012,24 @@ class DivEngine {
|
|||
// is exporting
|
||||
bool isExporting();
|
||||
|
||||
// get how many loops is left
|
||||
void getLoopsLeft(int& loops);
|
||||
|
||||
// get how many loops in total export needs to do
|
||||
void getTotalLoops(int& loops);
|
||||
|
||||
// get current position in song
|
||||
void getCurSongPos(int& row, int& order);
|
||||
|
||||
// get how many files export needs to create
|
||||
void getTotalAudioFiles(int& files);
|
||||
|
||||
// get which file is processed right now (progress for e.g. per-channel export)
|
||||
void getCurFileIndex(int& file);
|
||||
|
||||
// get fadeout state
|
||||
bool getIsFadingOut();
|
||||
|
||||
// add instrument
|
||||
int addInstrument(int refChan=0, DivInstrumentType fallbackType=DIV_INS_STD);
|
||||
|
||||
|
@ -1401,6 +1423,7 @@ class DivEngine {
|
|||
totalLoops(0),
|
||||
lastLoopPos(0),
|
||||
exportLoopCount(0),
|
||||
curExportChan(0),
|
||||
nextSpeed(3),
|
||||
elapsedBars(0),
|
||||
elapsedBeats(0),
|
||||
|
@ -1439,6 +1462,7 @@ class DivEngine {
|
|||
exportMode(DIV_EXPORT_MODE_ONE),
|
||||
exportFormat(DIV_EXPORT_FORMAT_S16),
|
||||
exportFadeOut(0.0),
|
||||
isFadingOut(false),
|
||||
exportOutputs(2),
|
||||
cmdStreamInt(NULL),
|
||||
midiBaseChan(0),
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "export/amigaValidation.h"
|
||||
#include "export/tiuna.h"
|
||||
#include "export/zsm.h"
|
||||
|
||||
DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
|
||||
DivROMExport* exporter=NULL;
|
||||
|
@ -31,6 +32,9 @@ DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
|
|||
case DIV_ROM_TIUNA:
|
||||
exporter=new DivExportTiuna;
|
||||
break;
|
||||
case DIV_ROM_ZSM:
|
||||
exporter=new DivExportZSM;
|
||||
break;
|
||||
default:
|
||||
exporter=new DivROMExport;
|
||||
break;
|
||||
|
|
|
@ -18,9 +18,77 @@
|
|||
*/
|
||||
|
||||
#include "zsm.h"
|
||||
#include "../engine.h"
|
||||
#include "../ta-log.h"
|
||||
#include "../utfutils.h"
|
||||
#include "song.h"
|
||||
#include <fmt/printf.h>
|
||||
|
||||
/// DivZSM definitions
|
||||
|
||||
#define ZSM_HEADER_SIZE 16
|
||||
#define ZSM_VERSION 1
|
||||
#define ZSM_YM_CMD 0x40
|
||||
#define ZSM_DELAY_CMD 0x80
|
||||
#define ZSM_YM_MAX_WRITES 63
|
||||
#define ZSM_SYNC_MAX_WRITES 31
|
||||
#define ZSM_DELAY_MAX 127
|
||||
#define ZSM_EOF ZSM_DELAY_CMD
|
||||
|
||||
#define ZSM_EXT ZSM_YM_CMD
|
||||
#define ZSM_EXT_PCM 0x00
|
||||
#define ZSM_EXT_CHIP 0x40
|
||||
#define ZSM_EXT_SYNC 0x80
|
||||
#define ZSM_EXT_CUSTOM 0xC0
|
||||
|
||||
enum YM_STATE { ym_PREV, ym_NEW, ym_STATES };
|
||||
enum PSG_STATE { psg_PREV, psg_NEW, psg_STATES };
|
||||
|
||||
class DivZSM {
|
||||
private:
|
||||
struct S_pcmInst {
|
||||
int geometry;
|
||||
unsigned int offset, length, loopPoint;
|
||||
bool isLooped;
|
||||
};
|
||||
SafeWriter* w;
|
||||
int ymState[ym_STATES][256];
|
||||
int psgState[psg_STATES][64];
|
||||
int pcmRateCache;
|
||||
int pcmCtrlRVCache;
|
||||
int pcmCtrlDCCache;
|
||||
unsigned int pcmLoopPointCache;
|
||||
bool pcmIsLooped;
|
||||
std::vector<DivRegWrite> ymwrites;
|
||||
std::vector<DivRegWrite> pcmMeta;
|
||||
std::vector<unsigned char> pcmData;
|
||||
std::vector<unsigned char> pcmCache;
|
||||
std::vector<S_pcmInst> pcmInsts;
|
||||
std::vector<DivRegWrite> syncCache;
|
||||
int loopOffset;
|
||||
int numWrites;
|
||||
int ticks;
|
||||
int tickRate;
|
||||
int ymMask;
|
||||
int psgMask;
|
||||
bool optimize;
|
||||
public:
|
||||
DivZSM();
|
||||
~DivZSM();
|
||||
void init(unsigned int rate = 60);
|
||||
int getoffset();
|
||||
void writeYM(unsigned char a, unsigned char v);
|
||||
void writePSG(unsigned char a, unsigned char v);
|
||||
void writePCM(unsigned char a, unsigned char v);
|
||||
void writeSync(unsigned char a, unsigned char v);
|
||||
void setOptimize(bool o);
|
||||
void tick(int numticks = 1);
|
||||
void setLoopPoint();
|
||||
SafeWriter* finish();
|
||||
private:
|
||||
void flushWrites();
|
||||
void flushTicks();
|
||||
};
|
||||
|
||||
/// DivZSM implementation
|
||||
|
||||
DivZSM::DivZSM() {
|
||||
w=NULL;
|
||||
|
@ -447,3 +515,240 @@ void DivZSM::flushTicks() {
|
|||
}
|
||||
ticks=0;
|
||||
}
|
||||
|
||||
/// ZSM export
|
||||
|
||||
constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
|
||||
constexpr int MASTER_CLOCK_MASK=(sizeof(void*)==8)?0xff:0;
|
||||
|
||||
void DivExportZSM::run() {
|
||||
// settings
|
||||
unsigned int zsmrate=conf.getInt("zsmrate",60);
|
||||
bool loop=conf.getBool("loop",true);
|
||||
bool optimize=conf.getBool("optimize",true);
|
||||
|
||||
// system IDs
|
||||
int VERA=-1;
|
||||
int YM=-1;
|
||||
int IGNORED=0;
|
||||
|
||||
// find indexes for YM and VERA. Ignore other systems.
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
switch (e->song.system[i]) {
|
||||
case DIV_SYSTEM_VERA:
|
||||
if (VERA>=0) {
|
||||
IGNORED++;
|
||||
break;
|
||||
}
|
||||
VERA=i;
|
||||
logAppendf("VERA detected as chip id %d",i);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2151:
|
||||
if (YM>=0) {
|
||||
IGNORED++;
|
||||
break;
|
||||
}
|
||||
YM=i;
|
||||
logAppendf("YM detected as chip id %d",i);
|
||||
break;
|
||||
default:
|
||||
IGNORED++;
|
||||
logAppendf("Ignoring chip %d systemID %d",i,(int)e->song.system[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (VERA<0 && YM<0) {
|
||||
logAppend("ERROR: No supported systems for ZSM");
|
||||
failed=true;
|
||||
running=false;
|
||||
return;
|
||||
}
|
||||
if (IGNORED>0) {
|
||||
logAppendf("ZSM export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' ');
|
||||
}
|
||||
|
||||
DivZSM zsm;
|
||||
|
||||
e->stop();
|
||||
e->repeatPattern=false;
|
||||
e->setOrder(0);
|
||||
e->synchronizedSoft([&]() {
|
||||
double origRate=e->got.rate;
|
||||
e->got.rate=zsmrate&0xffff;
|
||||
|
||||
// determine loop point
|
||||
int loopOrder=0;
|
||||
int loopRow=0;
|
||||
int loopEnd=0;
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
logAppendf("loop point: %d %d",loopOrder,loopRow);
|
||||
|
||||
zsm.init(zsmrate);
|
||||
|
||||
// reset the playback state
|
||||
e->curOrder=0;
|
||||
e->freelance=false;
|
||||
e->playing=false;
|
||||
e->extValuePresent=false;
|
||||
e->remainingLoops=-1;
|
||||
|
||||
// Prepare to write song data
|
||||
e->playSub(false);
|
||||
//size_t tickCount=0;
|
||||
bool done=false;
|
||||
bool loopNow=false;
|
||||
int loopPos=-1;
|
||||
int fracWait=0; // accumulates fractional ticks
|
||||
if (VERA>=0) e->disCont[VERA].dispatch->toggleRegisterDump(true);
|
||||
if (YM>=0) {
|
||||
e->disCont[YM].dispatch->toggleRegisterDump(true);
|
||||
// emit LFO initialization commands
|
||||
zsm.writeYM(0x18,0); // freq=0
|
||||
zsm.writeYM(0x19,0x7F); // AMD =7F
|
||||
zsm.writeYM(0x19,0xFF); // PMD =7F
|
||||
// TODO: incorporate the Furnace meta-command for init data and filter
|
||||
// out writes to otherwise-unused channels.
|
||||
}
|
||||
// Indicate the song's tuning as a sync meta-event
|
||||
// specified in terms of how many 1/256th semitones
|
||||
// the song is offset from standard A-440 tuning.
|
||||
// This is mainly to benefit visualizations in players
|
||||
// for non-standard tunings so that they can avoid
|
||||
// displaying the entire song held in pitch bend.
|
||||
// Tunings offsets that exceed a half semitone
|
||||
// will simply be represented in a different key
|
||||
// by nature of overflowing the signed char value
|
||||
signed char tuningoffset=(signed char)(round(3072*(log(e->song.tuning/440.0)/log(2))))&0xff;
|
||||
zsm.writeSync(0x01,tuningoffset);
|
||||
// Set optimize flag, which mainly buffers PSG writes
|
||||
// whenever the channel is silent
|
||||
zsm.setOptimize(optimize);
|
||||
|
||||
while (!done) {
|
||||
if (loopPos==-1) {
|
||||
if (loopOrder==e->curOrder && loopRow==e->curRow && loop)
|
||||
loopNow=true;
|
||||
if (loopNow) {
|
||||
// If Virtual Tempo is in use, our exact loop point
|
||||
// might be skipped due to quantization error.
|
||||
// If this happens, the tick immediately following is our loop point.
|
||||
if (e->ticks==1 || !(loopOrder==e->curOrder && loopRow==e->curRow)) {
|
||||
loopPos=zsm.getoffset();
|
||||
zsm.setLoopPoint();
|
||||
loopNow=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (e->nextTick() || !e->playing) {
|
||||
done=true;
|
||||
if (!loop) {
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
e->disCont[i].dispatch->getRegisterWrites().clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!e->playing) {
|
||||
loopPos=-1;
|
||||
}
|
||||
}
|
||||
// get register dumps
|
||||
for (int j=0; j<2; j++) {
|
||||
int i=0;
|
||||
// dump YM writes first
|
||||
if (j==0) {
|
||||
if (YM<0) {
|
||||
continue;
|
||||
} else {
|
||||
i=YM;
|
||||
}
|
||||
}
|
||||
// dump VERA writes second
|
||||
if (j==1) {
|
||||
if (VERA<0) {
|
||||
continue;
|
||||
} else {
|
||||
i=VERA;
|
||||
}
|
||||
}
|
||||
std::vector<DivRegWrite>& writes=e->disCont[i].dispatch->getRegisterWrites();
|
||||
if (writes.size()>0)
|
||||
logD("zsmOps: Writing %d messages to chip %d",writes.size(),i);
|
||||
for (DivRegWrite& write: writes) {
|
||||
if (i==YM) zsm.writeYM(write.addr&0xff,write.val);
|
||||
if (i==VERA) {
|
||||
if (done && write.addr>=64) continue; // don't process any PCM or sync events on the loop lookahead
|
||||
zsm.writePSG(write.addr&0xff,write.val);
|
||||
}
|
||||
}
|
||||
writes.clear();
|
||||
}
|
||||
|
||||
// write wait
|
||||
int totalWait=e->cycles>>MASTER_CLOCK_PREC;
|
||||
fracWait+=e->cycles&MASTER_CLOCK_MASK;
|
||||
totalWait+=fracWait>>MASTER_CLOCK_PREC;
|
||||
fracWait&=MASTER_CLOCK_MASK;
|
||||
if (totalWait>0 && !done) {
|
||||
zsm.tick(totalWait);
|
||||
//tickCount+=totalWait;
|
||||
}
|
||||
}
|
||||
// end of song
|
||||
|
||||
// done - close out.
|
||||
e->got.rate=origRate;
|
||||
if (VERA>=0) e->disCont[VERA].dispatch->toggleRegisterDump(false);
|
||||
if (YM>=0) e->disCont[YM].dispatch->toggleRegisterDump(false);
|
||||
|
||||
e->remainingLoops=-1;
|
||||
e->playing=false;
|
||||
e->freelance=false;
|
||||
e->extValuePresent=false;
|
||||
});
|
||||
|
||||
progress[0].amount=1.0f;
|
||||
|
||||
logAppend("finished!");
|
||||
|
||||
output.push_back(DivROMExportOutput("out.zsm",zsm.finish()));
|
||||
running=false;
|
||||
}
|
||||
|
||||
/// DivExpottZSM - FRONTEND
|
||||
|
||||
bool DivExportZSM::go(DivEngine* eng) {
|
||||
progress[0].name="Generate";
|
||||
progress[0].amount=0.0f;
|
||||
|
||||
e=eng;
|
||||
running=true;
|
||||
failed=false;
|
||||
mustAbort=false;
|
||||
exportThread=new std::thread(&DivExportZSM::run,this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivExportZSM::wait() {
|
||||
if (exportThread!=NULL) {
|
||||
exportThread->join();
|
||||
delete exportThread;
|
||||
}
|
||||
}
|
||||
|
||||
void DivExportZSM::abort() {
|
||||
mustAbort=true;
|
||||
wait();
|
||||
}
|
||||
|
||||
bool DivExportZSM::isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
bool DivExportZSM::hasFailed() {
|
||||
return failed;
|
||||
}
|
||||
|
||||
DivROMExportProgress DivExportZSM::getProgress(int index) {
|
||||
if (index<0 || index>1) return progress[1];
|
||||
return progress[index];
|
||||
}
|
38
src/engine/export/zsm.h
Normal file
38
src/engine/export/zsm.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 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 <thread>
|
||||
|
||||
class DivExportZSM: public DivROMExport {
|
||||
DivEngine* e;
|
||||
std::thread* exportThread;
|
||||
DivROMExportProgress progress[2];
|
||||
bool running, failed, mustAbort;
|
||||
void run();
|
||||
public:
|
||||
bool go(DivEngine* e);
|
||||
bool isRunning();
|
||||
bool hasFailed();
|
||||
void abort();
|
||||
void wait();
|
||||
DivROMExportProgress getProgress(int index=0);
|
||||
~DivExportZSM() {}
|
||||
};
|
|
@ -401,8 +401,8 @@ std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
|
|||
sample->loop=false;
|
||||
}
|
||||
|
||||
if (sample->centerRate<4000) sample->centerRate=4000;
|
||||
if (sample->centerRate>64000) sample->centerRate=64000;
|
||||
if (sample->centerRate<100) sample->centerRate=100;
|
||||
if (sample->centerRate>384000) sample->centerRate=384000;
|
||||
sfWrap.doClose();
|
||||
BUSY_END;
|
||||
ret.push_back(sample);
|
||||
|
|
|
@ -367,6 +367,141 @@ void DivInstrument::writeFeatureFM(SafeWriter* w, bool fui) {
|
|||
FEATURE_END;
|
||||
}
|
||||
|
||||
bool MemPatch::calcDiff(const void* pre, const void* post, size_t inputSize) {
|
||||
bool diffValid=false;
|
||||
size_t firstDiff=0;
|
||||
size_t lastDiff=0;
|
||||
const unsigned char* preBytes=(const unsigned char*)pre;
|
||||
const unsigned char* postBytes=(const unsigned char*)post;
|
||||
|
||||
// @NOTE: consider/profile using a memcmp==0 check to early-out, if it's potentially faster
|
||||
// for the common case, which is no change
|
||||
for (size_t ii=0; ii<inputSize; ++ii) {
|
||||
if (preBytes[ii] != postBytes[ii]) {
|
||||
lastDiff=ii;
|
||||
firstDiff=diffValid ? firstDiff : ii;
|
||||
diffValid=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (diffValid) {
|
||||
offset=firstDiff;
|
||||
size=lastDiff - firstDiff + 1;
|
||||
data=new unsigned char[size];
|
||||
|
||||
// the diff is to make pre into post (MemPatch is general, not specific to
|
||||
// undo), so copy from postBytes
|
||||
memcpy(data, postBytes+offset, size);
|
||||
}
|
||||
|
||||
return diffValid;
|
||||
}
|
||||
|
||||
void MemPatch::applyAndReverse(void* target, size_t targetSize) {
|
||||
if (size==0) return;
|
||||
if (offset+size>targetSize) {
|
||||
logW("MemPatch (offset %d, size %d) exceeds target size (%d), can't apply!",offset,size,targetSize);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char* targetBytes=(unsigned char*)target;
|
||||
|
||||
// swap this->data and its segment on target
|
||||
for (size_t ii=0; ii<size; ++ii) {
|
||||
unsigned char tmp=targetBytes[offset+ii];
|
||||
targetBytes[offset+ii] = data[ii];
|
||||
data[ii] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
void DivInstrumentUndoStep::applyAndReverse(DivInstrument* target) {
|
||||
if (nameValid) {
|
||||
name.swap(target->name);
|
||||
}
|
||||
podPatch.applyAndReverse((DivInstrumentPOD*)target, sizeof(DivInstrumentPOD));
|
||||
}
|
||||
|
||||
bool DivInstrumentUndoStep::makeUndoPatch(size_t processTime_, const DivInstrument* pre, const DivInstrument* post) {
|
||||
processTime=processTime_;
|
||||
|
||||
// create the patch that will make post into pre
|
||||
podPatch.calcDiff((const DivInstrumentPOD*)post, (const DivInstrumentPOD*)pre, sizeof(DivInstrumentPOD));
|
||||
if (pre->name!=post->name) {
|
||||
nameValid=true;
|
||||
name=pre->name;
|
||||
}
|
||||
|
||||
return nameValid || podPatch.isValid();
|
||||
}
|
||||
|
||||
bool DivInstrument::recordUndoStepIfChanged(size_t processTime, const DivInstrument* old) {
|
||||
DivInstrumentUndoStep step;
|
||||
|
||||
// generate a patch to go back to old
|
||||
if (step.makeUndoPatch(processTime, old, this)) {
|
||||
|
||||
// make room
|
||||
if (undoHist.size()>=undoHist.capacity()) {
|
||||
delete undoHist.front();
|
||||
undoHist.pop_front();
|
||||
}
|
||||
|
||||
// clear redo
|
||||
while (!redoHist.empty()) {
|
||||
delete redoHist.back();
|
||||
redoHist.pop_back();
|
||||
}
|
||||
|
||||
DivInstrumentUndoStep* stepPtr=new DivInstrumentUndoStep;
|
||||
*stepPtr=step;
|
||||
step.podPatch.data=NULL; // don't let it delete the data ptr that's been copied!
|
||||
undoHist.push_back(stepPtr);
|
||||
|
||||
// logI("DivInstrument::undoHist push (%u off, %u size)", stepPtr->podPatch.offset, stepPtr->podPatch.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int DivInstrument::undo() {
|
||||
if (undoHist.empty()) return 0;
|
||||
|
||||
DivInstrumentUndoStep* step=undoHist.back();
|
||||
undoHist.pop_back();
|
||||
// logI("DivInstrument::undo (%u off, %u size)", step->podPatch.offset, step->podPatch.size);
|
||||
step->applyAndReverse(this);
|
||||
|
||||
// make room
|
||||
if (redoHist.size()>=redoHist.capacity()) {
|
||||
DivInstrumentUndoStep* step=redoHist.front();
|
||||
delete step;
|
||||
redoHist.pop_front();
|
||||
}
|
||||
redoHist.push_back(step);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DivInstrument::redo() {
|
||||
if (redoHist.empty()) return 0;
|
||||
|
||||
DivInstrumentUndoStep* step = redoHist.back();
|
||||
redoHist.pop_back();
|
||||
// logI("DivInstrument::redo (%u off, %u size)", step->podPatch.offset, step->podPatch.size);
|
||||
step->applyAndReverse(this);
|
||||
|
||||
// make room
|
||||
if (undoHist.size()>=undoHist.capacity()) {
|
||||
DivInstrumentUndoStep* step=undoHist.front();
|
||||
delete step;
|
||||
undoHist.pop_front();
|
||||
}
|
||||
undoHist.push_back(step);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivInstrument::writeMacro(SafeWriter* w, const DivInstrumentMacro& m) {
|
||||
if (!m.len) return;
|
||||
|
||||
|
@ -3345,3 +3480,28 @@ bool DivInstrument::saveDMP(const char* path) {
|
|||
w->finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
DivInstrument::~DivInstrument() {
|
||||
// free undoHist/redoHist
|
||||
while (!undoHist.empty()) {
|
||||
delete undoHist.back();
|
||||
undoHist.pop_back();
|
||||
}
|
||||
while (!redoHist.empty()) {
|
||||
delete redoHist.back();
|
||||
redoHist.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
DivInstrument::DivInstrument( const DivInstrument& ins ) {
|
||||
// undo/redo history is specifically not copied
|
||||
*(DivInstrumentPOD*)this=ins;
|
||||
name=ins.name;
|
||||
}
|
||||
|
||||
DivInstrument& DivInstrument::operator=( const DivInstrument& ins ) {
|
||||
// undo/redo history is specifically not copied
|
||||
*(DivInstrumentPOD*)this=ins;
|
||||
name=ins.name;
|
||||
return *this;
|
||||
}
|
|
@ -23,8 +23,10 @@
|
|||
#include "dataErrors.h"
|
||||
#include "../ta-utils.h"
|
||||
#include "../pch.h"
|
||||
#include "../fixedQueue.h"
|
||||
|
||||
struct DivSong;
|
||||
struct DivInstrument;
|
||||
|
||||
// NOTICE!
|
||||
// before adding new instrument types to this struct, please ask me first.
|
||||
|
@ -865,8 +867,7 @@ struct DivInstrumentSID2 {
|
|||
noiseMode(0) {}
|
||||
};
|
||||
|
||||
struct DivInstrument {
|
||||
String name;
|
||||
struct DivInstrumentPOD {
|
||||
DivInstrumentType type;
|
||||
DivInstrumentFM fm;
|
||||
DivInstrumentSTD std;
|
||||
|
@ -885,6 +886,77 @@ struct DivInstrument {
|
|||
DivInstrumentPowerNoise powernoise;
|
||||
DivInstrumentSID2 sid2;
|
||||
|
||||
DivInstrumentPOD() :
|
||||
type(DIV_INS_FM) {
|
||||
}
|
||||
};
|
||||
|
||||
struct MemPatch {
|
||||
MemPatch() :
|
||||
data(NULL)
|
||||
, offset(0)
|
||||
, size(0) {
|
||||
}
|
||||
|
||||
~MemPatch() {
|
||||
if (data) {
|
||||
delete[] data;
|
||||
data=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool calcDiff(const void* pre, const void* post, size_t size);
|
||||
void applyAndReverse(void* target, size_t inputSize);
|
||||
bool isValid() const { return size>0; }
|
||||
|
||||
unsigned char* data;
|
||||
size_t offset;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct DivInstrumentUndoStep {
|
||||
DivInstrumentUndoStep() :
|
||||
name(""),
|
||||
nameValid(false),
|
||||
processTime(0) {
|
||||
}
|
||||
|
||||
MemPatch podPatch;
|
||||
String name;
|
||||
bool nameValid;
|
||||
size_t processTime;
|
||||
|
||||
void applyAndReverse(DivInstrument* target);
|
||||
bool makeUndoPatch(size_t processTime_, const DivInstrument* pre, const DivInstrument* post);
|
||||
};
|
||||
|
||||
struct DivInstrument : DivInstrumentPOD {
|
||||
String name;
|
||||
|
||||
DivInstrument() :
|
||||
name("") {
|
||||
// clear and construct DivInstrumentPOD so it doesn't have any garbage in the padding
|
||||
memset((unsigned char*)(DivInstrumentPOD*)this, 0, sizeof(DivInstrumentPOD));
|
||||
new ((DivInstrumentPOD*)this) DivInstrumentPOD;
|
||||
}
|
||||
|
||||
~DivInstrument();
|
||||
|
||||
/**
|
||||
* copy/assignment to specifically avoid leaking or dangling pointers to undo step
|
||||
*/
|
||||
DivInstrument( const DivInstrument& ins );
|
||||
DivInstrument& operator=( const DivInstrument& ins );
|
||||
|
||||
/**
|
||||
* undo stuff
|
||||
*/
|
||||
FixedQueue<DivInstrumentUndoStep*, 128> undoHist;
|
||||
FixedQueue<DivInstrumentUndoStep*, 128> redoHist;
|
||||
bool recordUndoStepIfChanged(size_t processTime, const DivInstrument* old);
|
||||
int undo();
|
||||
int redo();
|
||||
|
||||
/**
|
||||
* these are internal functions.
|
||||
*/
|
||||
|
@ -969,9 +1041,5 @@ struct DivInstrument {
|
|||
* @return whether it was successful.
|
||||
*/
|
||||
bool saveDMP(const char* path);
|
||||
DivInstrument():
|
||||
name(""),
|
||||
type(DIV_INS_FM) {
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -761,6 +761,12 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
|||
immWrite(0x19,0x80|pmDepth);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
@ -423,11 +423,11 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
chan[i].tfx.counter = 0;
|
||||
chan[i].tfx.out = 0;
|
||||
if (chan[i].nextPSGMode.val&8) {
|
||||
if (dumpWrites) addWrite(0xffff0002+(i<<8),0);
|
||||
//if (dumpWrites) addWrite(0xffff0002+(i<<8),0);
|
||||
if (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) {
|
||||
if (dumpWrites) {
|
||||
rWrite(0x08+i,0);
|
||||
addWrite(0xffff0000+(i<<8),chan[i].dac.sample);
|
||||
//addWrite(0xffff0000+(i<<8),chan[i].dac.sample);
|
||||
}
|
||||
if (chan[i].dac.setPos) {
|
||||
chan[i].dac.setPos=false;
|
||||
|
@ -517,7 +517,7 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
chan[i].dac.rate=((double)rate*((sunsoft||clockSel)?8.0:16.0))/(double)(MAX(1,off*chan[i].freq));
|
||||
if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dac.rate);
|
||||
//if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dac.rate);
|
||||
}
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
|
@ -604,12 +604,12 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
}
|
||||
if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) {
|
||||
chan[c.chan].dac.sample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
break;
|
||||
} else {
|
||||
if (dumpWrites) {
|
||||
rWrite(0x08+c.chan,0);
|
||||
addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
|
||||
//addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
|
||||
}
|
||||
}
|
||||
if (chan[c.chan].dac.setPos) {
|
||||
|
@ -637,10 +637,10 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
chan[c.chan].dac.sample=12*sampleBank+chan[c.chan].note%12;
|
||||
if (chan[c.chan].dac.sample>=parent->song.sampleLen) {
|
||||
chan[c.chan].dac.sample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
break;
|
||||
} else {
|
||||
if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
|
||||
//if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
|
||||
}
|
||||
if (chan[c.chan].dac.setPos) {
|
||||
chan[c.chan].dac.setPos=false;
|
||||
|
@ -651,7 +651,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
chan[c.chan].dac.rate=parent->getSample(chan[c.chan].dac.sample)->rate*2048;
|
||||
if (dumpWrites) {
|
||||
rWrite(0x08+c.chan,0);
|
||||
addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dac.rate);
|
||||
//addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dac.rate);
|
||||
}
|
||||
chan[c.chan].dac.furnaceDAC=false;
|
||||
}
|
||||
|
@ -686,7 +686,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].dac.sample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
chan[c.chan].nextPSGMode.val&=~8;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].active=false;
|
||||
|
@ -867,7 +867,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
case DIV_CMD_SAMPLE_POS:
|
||||
chan[c.chan].dac.pos=c.value;
|
||||
chan[c.chan].dac.setPos=true;
|
||||
if (dumpWrites) addWrite(0xffff0005,chan[c.chan].dac.pos);
|
||||
//if (dumpWrites) addWrite(0xffff0005,chan[c.chan].dac.pos);
|
||||
break;
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
chan[c.chan].std.mask(c.value,true);
|
||||
|
|
|
@ -1463,6 +1463,13 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
if (c.chan>=6) break;
|
||||
chan[c.chan].hardReset=c.value;
|
||||
|
|
|
@ -28,7 +28,7 @@ struct _nla_table nla_table;
|
|||
|
||||
#define CHIP_DIVIDER 16
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {doWrite(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite((a),v)); if (dumpWrites) {addWrite((a),v);} }
|
||||
|
||||
const char* regCheatSheetNES[]={
|
||||
"S0Volume", "4000",
|
||||
|
@ -86,10 +86,10 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
|||
unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1; \
|
||||
if (dacAntiClickOn && dacAntiClick<next) { \
|
||||
dacAntiClick+=8; \
|
||||
rWrite(0x4011,dacAntiClick); \
|
||||
doWrite(0x4011,dacAntiClick); \
|
||||
} else { \
|
||||
dacAntiClickOn=false; \
|
||||
rWrite(0x4011,next); \
|
||||
doWrite(0x4011,next); \
|
||||
} \
|
||||
} \
|
||||
dacPos++; \
|
||||
|
@ -108,6 +108,13 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
|||
void DivPlatformNES::acquire_puNES(short** buf, size_t len) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
doPCM;
|
||||
|
||||
if (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
doWrite(w.addr,w.val);
|
||||
regPool[w.addr&0x1f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
apu_tick(nes,NULL);
|
||||
nes->apu.odd_cycle=!nes->apu.odd_cycle;
|
||||
|
@ -134,6 +141,13 @@ void DivPlatformNES::acquire_NSFPlay(short** buf, size_t len) {
|
|||
int out2[2];
|
||||
for (size_t i=0; i<len; i++) {
|
||||
doPCM;
|
||||
|
||||
if (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
doWrite(w.addr,w.val);
|
||||
regPool[w.addr&0x1f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
nes1_NP->Tick(8);
|
||||
nes2_NP->TickFrameSequence(8);
|
||||
|
@ -297,7 +311,7 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
if (chan[i].sweepChanged) {
|
||||
chan[i].sweepChanged=false;
|
||||
if (i==0) {
|
||||
//rWrite(16+i*5,chan[i].sweep);
|
||||
// rWrite(16+i*5,chan[i].sweep);
|
||||
}
|
||||
}
|
||||
if (i<3) if (chan[i].std.phaseReset.had) {
|
||||
|
@ -338,7 +352,7 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
//rWrite(16+i*5+2,8);
|
||||
// rWrite(16+i*5+2,8);
|
||||
if (i==2) { // triangle
|
||||
rWrite(0x4000+i*4,0x00);
|
||||
} else {
|
||||
|
@ -400,6 +414,34 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
logV("switching bank to %d",dpcmBank);
|
||||
if (dumpWrites) addWrite(0xffff0004,dpcmBank);
|
||||
}
|
||||
|
||||
// sample custom loop point...
|
||||
DivSample* lsamp=parent->getSample(dacSample);
|
||||
|
||||
// how it works:
|
||||
// when the initial sample info is written (see above) and playback is launched,
|
||||
// the parameters (start point in memory and length) are locked until sample end
|
||||
// is reached.
|
||||
|
||||
// thus, if we write new data after just several APU clock cycles, it will be used only when
|
||||
// sample finishes one full loop.
|
||||
|
||||
// thus we can write sample's loop point as "start address" and sample's looped part length
|
||||
// as "full sample length".
|
||||
|
||||
// APU will play full sample once and then repeatedly cycle through the looped part.
|
||||
|
||||
// sources:
|
||||
// https://www.nesdev.org/wiki/APU_DMC
|
||||
// https://www.youtube.com/watch?v=vB4P8x2Am6Y
|
||||
|
||||
if (lsamp->loopEnd>lsamp->loopStart && goingToLoop) {
|
||||
int loopStartAddr=(sampleOffDPCM[dacSample]+lsamp->loopStart)>>3;
|
||||
int loopLen=(lsamp->loopEnd-lsamp->loopStart)>>3;
|
||||
|
||||
rWrite(0x4012,(loopStartAddr>>6)&0xff);
|
||||
rWrite(0x4013,(loopLen>>4)&0xff);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (nextDPCMFreq>=0) {
|
||||
|
@ -790,6 +832,7 @@ float DivPlatformNES::getPostAmp() {
|
|||
}
|
||||
|
||||
void DivPlatformNES::reset() {
|
||||
while (!writes.empty()) writes.pop();
|
||||
for (int i=0; i<5; i++) {
|
||||
chan[i]=DivPlatformNES::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include "sound/nes_nsfplay/nes_apu.h"
|
||||
#include "sound/nes_nsfplay/5e01_apu.h"
|
||||
#include "../../fixedQueue.h"
|
||||
|
||||
class DivPlatformNES: public DivDispatch {
|
||||
struct Channel: public SharedChannel<signed char> {
|
||||
|
@ -44,6 +45,13 @@ class DivPlatformNES: public DivDispatch {
|
|||
Channel chan[5];
|
||||
DivDispatchOscBuffer* oscBuf[5];
|
||||
bool isMuted[5];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
QueuedWrite(): addr(0), val(0) {}
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {}
|
||||
};
|
||||
FixedQueue<QueuedWrite,128> writes;
|
||||
int dacPeriod, dacRate, dpcmPos;
|
||||
unsigned int dacPos, dacAntiClick;
|
||||
int dacSample;
|
||||
|
|
|
@ -165,8 +165,12 @@ int DivPlatformSMS::snCalcFreq(int ch) {
|
|||
if (ch==3) CHIP_DIVIDER=noiseDivider;
|
||||
int easyStartingPeriod=16;
|
||||
int easyThreshold=round(128.0*12.0*log((chipClock/(easyStartingPeriod*CHIP_DIVIDER))/(0.0625*parent->song.tuning))/log(2.0))-384+64;
|
||||
if (parent->song.linearPitch==2 && easyNoise && chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2>(easyThreshold)) {
|
||||
int ret=(((easyStartingPeriod<<7))-(chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2-(easyThreshold)))>>7;
|
||||
int curFreq=chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2+(chan[ch].arpOff<<7);
|
||||
if (chan[ch].fixedArp) {
|
||||
curFreq=chan[ch].baseNoteOverride<<7;
|
||||
}
|
||||
if (parent->song.linearPitch==2 && easyNoise && curFreq>easyThreshold) {
|
||||
int ret=(((easyStartingPeriod<<7))-(curFreq-(easyThreshold)))>>7;
|
||||
if (ret<0) ret=0;
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -841,6 +841,8 @@ void DivPlatformSNES::reset() {
|
|||
memcpy(sampleMem,copyOfSampleMem,65536);
|
||||
dsp.init(sampleMem);
|
||||
dsp.set_output(NULL,0);
|
||||
dsp.setupInterpolation(!interpolationOff);
|
||||
|
||||
memset(regPool,0,128);
|
||||
// this can't be 0 or channel 1 won't play
|
||||
// this can't be 0x100 either as that's used by SPC700 page 1 and the stack
|
||||
|
@ -1023,6 +1025,8 @@ void DivPlatformSNES::setFlags(const DivConfig& flags) {
|
|||
initEchoFIR[7]=flags.getInt("echoFilter7",0);
|
||||
|
||||
initEchoMask=flags.getInt("echoMask",0);
|
||||
|
||||
interpolationOff=flags.getBool("interpolationOff",false);
|
||||
}
|
||||
|
||||
int DivPlatformSNES::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
|
|
|
@ -69,6 +69,7 @@ class DivPlatformSNES: public DivDispatch {
|
|||
bool writeEcho;
|
||||
bool writeDryVol;
|
||||
bool echoOn;
|
||||
bool interpolationOff;
|
||||
|
||||
bool initEchoOn;
|
||||
signed char initEchoVolL;
|
||||
|
|
|
@ -135,24 +135,30 @@ static short const gauss [512] =
|
|||
1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305,
|
||||
};
|
||||
|
||||
void SPC_DSP::setupInterpolation(bool interpolate){for(int i=0;i<voice_count;i++){m.voices[i].interpolate=interpolate;}}
|
||||
|
||||
inline int SPC_DSP::interpolate( voice_t const* v )
|
||||
{
|
||||
// Make pointers into gaussian based on fractional position between samples
|
||||
int offset = v->interp_pos >> 4 & 0xFF;
|
||||
short const* fwd = gauss + 255 - offset;
|
||||
short const* rev = gauss + offset; // mirror left half of gaussian
|
||||
if (v->interpolate) {
|
||||
int offset = v->interp_pos >> 4 & 0xFF;
|
||||
short const* fwd = gauss + 255 - offset;
|
||||
short const* rev = gauss + offset; // mirror left half of gaussian
|
||||
|
||||
int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos];
|
||||
int out;
|
||||
out = (fwd [ 0] * in [0]) >> 11;
|
||||
out += (fwd [256] * in [1]) >> 11;
|
||||
out += (rev [256] * in [2]) >> 11;
|
||||
out = (int16_t) out;
|
||||
out += (rev [ 0] * in [3]) >> 11;
|
||||
int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos];
|
||||
int out;
|
||||
out = (fwd [ 0] * in [0]) >> 11;
|
||||
out += (fwd [256] * in [1]) >> 11;
|
||||
out += (rev [256] * in [2]) >> 11;
|
||||
out = (int16_t) out;
|
||||
out += (rev [ 0] * in [3]) >> 11;
|
||||
|
||||
CLAMP16( out );
|
||||
out &= ~1;
|
||||
return out;
|
||||
CLAMP16( out );
|
||||
out &= ~1;
|
||||
return out;
|
||||
} else {
|
||||
return v->buf [(v->interp_pos >> 12) + v->buf_pos]; //Furnace addition -- no interpolation
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,9 @@ public:
|
|||
// output buffer could hold.
|
||||
int sample_count() const;
|
||||
|
||||
// Furnace addition: disable/enable Gaussian interpolation
|
||||
void setupInterpolation(bool interpolate);
|
||||
|
||||
// Emulation
|
||||
|
||||
// Resets DSP to power-on state
|
||||
|
@ -122,6 +125,7 @@ public:
|
|||
int hidden_env; // used by GAIN mode 7, very obscure quirk
|
||||
uint8_t t_envx_out;
|
||||
sample_t out[2]; // Furnace addition, for per-channel oscilloscope
|
||||
bool interpolate; // Furnace addition, to disable interpolation
|
||||
};
|
||||
|
||||
// Furnace addition, gets a voice
|
||||
|
|
|
@ -858,6 +858,9 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
|
|||
immWrite(0x17,0x80|pmDepth);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
// TODO: if OPZ supports op mask
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
@ -1004,6 +1004,13 @@ int DivPlatformYM2203::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
@ -1537,6 +1537,13 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
@ -1507,6 +1507,13 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
@ -1576,6 +1576,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
@ -67,6 +67,7 @@ const char* cmdName[]={
|
|||
"HINT_ARPEGGIO",
|
||||
"HINT_VOLUME",
|
||||
"HINT_VOL_SLIDE",
|
||||
"HINT_VOL_SLIDE_TARGET",
|
||||
"HINT_PORTA",
|
||||
"HINT_LEGATO",
|
||||
|
||||
|
@ -265,6 +266,8 @@ const char* cmdName[]={
|
|||
|
||||
"FDS_MOD_AUTO",
|
||||
|
||||
"FM_OPMASK",
|
||||
|
||||
"MULTIPCM_MIX_FM",
|
||||
"MULTIPCM_MIX_PCM",
|
||||
"MULTIPCM_LFO",
|
||||
|
@ -655,7 +658,23 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
}
|
||||
|
||||
// volume
|
||||
if (pat->data[whatRow][3]!=-1) {
|
||||
int volPortaTarget=-1;
|
||||
bool noApplyVolume=false;
|
||||
for (int j=0; j<curPat[i].effectCols; j++) {
|
||||
short effect=pat->data[whatRow][4+(j<<1)];
|
||||
if (effect==0xd3 || effect==0xd4) { // vol porta
|
||||
volPortaTarget=pat->data[whatRow][3]<<8; // can be -256
|
||||
|
||||
short effectVal=pat->data[whatRow][5+(j<<1)];
|
||||
if (effectVal==-1) effectVal=0;
|
||||
effectVal&=255;
|
||||
|
||||
noApplyVolume=effectVal>0; // "D3.." or "D300" shouldn't stop volume from applying
|
||||
break; // technically you could have both D3 and D4... let's not care
|
||||
}
|
||||
}
|
||||
|
||||
if (pat->data[whatRow][3]!=-1 && !noApplyVolume) {
|
||||
if (!song.oldAlwaysSetVolume || disCont[dispatchOfChan[i]].dispatch->getLegacyAlwaysSetVolume() || (MIN(chan[i].volMax,chan[i].volume)>>8)!=pat->data[whatRow][3]) {
|
||||
if (pat->data[whatRow][0]==0 && pat->data[whatRow][1]==0) {
|
||||
chan[i].midiAftertouch=true;
|
||||
|
@ -844,6 +863,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
} else {
|
||||
chan[i].volSpeed=0;
|
||||
}
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0x06: // vol slide + porta
|
||||
|
@ -885,6 +905,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
} else {
|
||||
chan[i].volSpeed=0;
|
||||
}
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0x07: // tremolo
|
||||
|
@ -895,6 +916,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
chan[i].tremoloRate=effectVal>>4;
|
||||
if (chan[i].tremoloDepth!=0) {
|
||||
chan[i].volSpeed=0;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
} else {
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
|
@ -914,6 +936,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
} else {
|
||||
chan[i].volSpeed=0;
|
||||
}
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0x00: // arpeggio
|
||||
|
@ -956,6 +979,22 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
chan[i].cutType=0;
|
||||
}
|
||||
break;
|
||||
case 0xd3: // volume portamento (vol porta)
|
||||
// tremolo and vol slides are incompatible
|
||||
chan[i].tremoloDepth=0;
|
||||
chan[i].tremoloRate=0;
|
||||
chan[i].volSpeed=volPortaTarget<0 ? 0 : volPortaTarget>chan[i].volume ? effectVal : -effectVal;
|
||||
chan[i].volSpeedTarget=chan[i].volSpeed==0 ? -1 : volPortaTarget;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE_TARGET,i,chan[i].volSpeed,chan[i].volSpeedTarget));
|
||||
break;
|
||||
case 0xd4: // volume portamento fast (vol porta fast)
|
||||
// tremolo and vol slides are incompatible
|
||||
chan[i].tremoloDepth=0;
|
||||
chan[i].tremoloRate=0;
|
||||
chan[i].volSpeed=volPortaTarget<0 ? 0 : volPortaTarget>chan[i].volume ? 256*effectVal : -256*effectVal;
|
||||
chan[i].volSpeedTarget=chan[i].volSpeed==0 ? -1 : volPortaTarget;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE_TARGET,i,chan[i].volSpeed,chan[i].volSpeedTarget));
|
||||
break;
|
||||
case 0xe0: // arp speed
|
||||
if (effectVal>0) {
|
||||
curSubSong->arpLen=effectVal;
|
||||
|
@ -1093,6 +1132,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
chan[i].tremoloDepth=0;
|
||||
chan[i].tremoloRate=0;
|
||||
chan[i].volSpeed=effectVal;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0xf4: // fine volume ramp down
|
||||
|
@ -1100,6 +1140,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
chan[i].tremoloDepth=0;
|
||||
chan[i].tremoloRate=0;
|
||||
chan[i].volSpeed=-effectVal;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0xf5: // disable macro
|
||||
|
@ -1113,12 +1154,14 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
break;
|
||||
case 0xf8: // single volume ramp up
|
||||
chan[i].volSpeed=0; // add compat flag?
|
||||
chan[i].volSpeedTarget=-1;
|
||||
chan[i].volume=MIN(chan[i].volume+effectVal*256,chan[i].volMax);
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
break;
|
||||
case 0xf9: // single volume ramp down
|
||||
chan[i].volSpeed=0; // add compat flag?
|
||||
chan[i].volSpeedTarget=-1;
|
||||
chan[i].volume=MAX(chan[i].volume-effectVal*256,0);
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
|
@ -1136,9 +1179,9 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
} else {
|
||||
chan[i].volSpeed=0;
|
||||
}
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
|
||||
case 0xfc: // delayed note release
|
||||
if (song.delayBehavior==2 || effectVal<nextSpeed) {
|
||||
chan[i].cut=effectVal+1;
|
||||
|
@ -1606,9 +1649,30 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
if (chan[i].volSpeed!=0) {
|
||||
chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8);
|
||||
chan[i].volume+=chan[i].volSpeed;
|
||||
if (chan[i].volSpeedTarget!=-1) {
|
||||
bool atTarget=false;
|
||||
if (chan[i].volSpeed>0) {
|
||||
atTarget=(chan[i].volume>=chan[i].volSpeedTarget);
|
||||
} else if (chan[i].volSpeed<0) {
|
||||
atTarget=(chan[i].volume<=chan[i].volSpeedTarget);
|
||||
} else {
|
||||
atTarget=true;
|
||||
chan[i].volSpeedTarget=chan[i].volume;
|
||||
}
|
||||
|
||||
if (atTarget) {
|
||||
chan[i].volume=chan[i].volSpeedTarget;
|
||||
chan[i].volSpeed=0;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0));
|
||||
}
|
||||
}
|
||||
if (chan[i].volume>chan[i].volMax) {
|
||||
chan[i].volume=chan[i].volMax;
|
||||
chan[i].volSpeed=0;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0));
|
||||
|
@ -1620,6 +1684,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
} else {
|
||||
chan[i].volume=0;
|
||||
}
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
} else {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "sfWrapper.h"
|
||||
#include "../fileutils.h"
|
||||
#include "../ta-log.h"
|
||||
#include "sndfile.h"
|
||||
|
||||
sf_count_t _vioGetSize(void* user) {
|
||||
|
@ -80,6 +81,7 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
vio.seek=_vioSeek;
|
||||
vio.tell=_vioTell;
|
||||
vio.write=_vioWrite;
|
||||
logV("SFWrapper: opening %s",path);
|
||||
|
||||
const char* modeC="rb";
|
||||
if (mode==SFM_WRITE) {
|
||||
|
@ -91,10 +93,12 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
|
||||
f=ps_fopen(path,modeC);
|
||||
if (f==NULL) {
|
||||
logE("SFWrapper: failed to open (%s)",strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_END)==-1) {
|
||||
logE("SFWrapper: failed to seek to end (%s)",strerror(errno));
|
||||
fclose(f);
|
||||
f=NULL;
|
||||
return NULL;
|
||||
|
@ -102,6 +106,7 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
|
||||
len=ftell(f);
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
logE("SFWrapper: failed to tell (%s)",strerror(errno));
|
||||
len=0;
|
||||
fclose(f);
|
||||
f=NULL;
|
||||
|
@ -109,6 +114,7 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
}
|
||||
|
||||
if (fseek(f,0,SEEK_SET)==-1) {
|
||||
logE("SFWrapper: failed to seek to beginning (%s)",strerror(errno));
|
||||
len=0;
|
||||
fclose(f);
|
||||
f=NULL;
|
||||
|
@ -117,5 +123,8 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
|
||||
sf=sf_open_virtual(&vio,mode,sfinfo,this);
|
||||
if (sf!=NULL) fileMode=mode;
|
||||
if (sf==NULL) {
|
||||
logE("SFWrapper: WHY IS IT NULL?!");
|
||||
}
|
||||
return sf;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,6 +102,173 @@ bool DivSubSong::walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int
|
|||
return false;
|
||||
}
|
||||
|
||||
double calcRowLenInSeconds(const DivGroovePattern& speeds, float hz, int vN, int vD, int timeBaseFromSong) {
|
||||
double hl=1; //count for 1 row
|
||||
if (hl<=0.0) hl=4.0;
|
||||
double timeBase=timeBaseFromSong+1;
|
||||
double speedSum=0;
|
||||
for (int i=0; i<MIN(16,speeds.len); i++) {
|
||||
speedSum+=speeds.val[i];
|
||||
}
|
||||
speedSum/=MAX(1,speeds.len);
|
||||
if (timeBase<1.0) timeBase=1.0;
|
||||
if (speedSum<1.0) speedSum=1.0;
|
||||
if (vD<1) vD=1;
|
||||
return 1.0/((60.0*hz/(timeBase*hl*speedSum))*(double)vN/(double)vD/60.0);
|
||||
}
|
||||
|
||||
void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders_vec, std::vector<DivGroovePattern>& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat) {
|
||||
length=0;
|
||||
hasFFxx=false;
|
||||
rowsForFadeout=0;
|
||||
|
||||
float secondsPerThisRow=0.0f;
|
||||
|
||||
DivGroovePattern curSpeeds=speeds; //simulate that we are playing the song, track all speed/BPM/tempo/engine rate changes
|
||||
short curVirtualTempoN=virtualTempoN;
|
||||
short curVirtualTempoD=virtualTempoD;
|
||||
float curHz=hz;
|
||||
double curDivider=(double)timeBase;
|
||||
|
||||
double curLen=0.0; //how many seconds passed since the start of song
|
||||
|
||||
int nextOrder=-1;
|
||||
int nextRow=0;
|
||||
int effectVal=0;
|
||||
int lastSuspectedLoopEnd=-1;
|
||||
DivPattern* subPat[DIV_MAX_CHANS];
|
||||
unsigned char wsWalked[8192];
|
||||
memset(wsWalked,0,8192);
|
||||
if (firstPat>0) {
|
||||
memset(wsWalked,255,32*firstPat);
|
||||
}
|
||||
for (int i=firstPat; i<ordersLen; i++) {
|
||||
bool jumped=false;
|
||||
|
||||
for (int j=0; j<chans; j++) {
|
||||
subPat[j]=pat[j].getPattern(orders.ord[j][i],false);
|
||||
}
|
||||
if (i>lastSuspectedLoopEnd) {
|
||||
lastSuspectedLoopEnd=i;
|
||||
}
|
||||
for (int j=nextRow; j<patLen; j++) {
|
||||
nextRow=0;
|
||||
bool changingOrder=false;
|
||||
bool jumpingOrder=false;
|
||||
if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7))) {
|
||||
return;
|
||||
}
|
||||
for (int k=0; k<chans; k++) {
|
||||
for (int l=0; l<pat[k].effectCols; l++) {
|
||||
effectVal=subPat[k]->data[j][5+(l<<1)];
|
||||
if (effectVal<0) effectVal=0;
|
||||
|
||||
if (subPat[k]->data[j][4+(l<<1)]==0xff) {
|
||||
hasFFxx=true;
|
||||
|
||||
// FFxx makes YOU SHALL NOT PASS!!! move
|
||||
orders_vec.push_back(j+1); // order len
|
||||
length+=j+1; // add length of order to song length
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (subPat[k]->data[j][4+(l<<1)]) {
|
||||
case 0x09: { // select groove pattern/speed 1
|
||||
if (grooves.empty()) {
|
||||
if (effectVal>0) curSpeeds.val[0]=effectVal;
|
||||
} else {
|
||||
if (effectVal<(short)grooves.size()) {
|
||||
curSpeeds=grooves[effectVal];
|
||||
//curSpeed=0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x0f: { // speed 1/speed 2
|
||||
if (curSpeeds.len==2 && grooves.empty()) {
|
||||
if (effectVal>0) curSpeeds.val[1]=effectVal;
|
||||
} else {
|
||||
if (effectVal>0) curSpeeds.val[0]=effectVal;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xfd: { // virtual tempo num
|
||||
if (effectVal>0) curVirtualTempoN=effectVal;
|
||||
break;
|
||||
}
|
||||
case 0xfe: { // virtual tempo den
|
||||
if (effectVal>0) curVirtualTempoD=effectVal;
|
||||
break;
|
||||
}
|
||||
case 0xf0: { // set Hz by tempo (set bpm)
|
||||
curDivider=(double)effectVal*2.0/5.0;
|
||||
if (curDivider<1) curDivider=1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (subPat[k]->data[j][4+(l<<1)]==0x0d) {
|
||||
if (jumpTreatment==2) {
|
||||
if ((i<ordersLen-1 || !ignoreJumpAtEnd)) {
|
||||
nextOrder=i+1;
|
||||
nextRow=effectVal;
|
||||
jumpingOrder=true;
|
||||
}
|
||||
} else if (jumpTreatment==1) {
|
||||
if (nextOrder==-1 && (i<ordersLen-1 || !ignoreJumpAtEnd)) {
|
||||
nextOrder=i+1;
|
||||
nextRow=effectVal;
|
||||
jumpingOrder=true;
|
||||
}
|
||||
} else {
|
||||
if ((i<ordersLen-1 || !ignoreJumpAtEnd)) {
|
||||
if (!changingOrder) {
|
||||
nextOrder=i+1;
|
||||
}
|
||||
jumpingOrder=true;
|
||||
nextRow=effectVal;
|
||||
}
|
||||
}
|
||||
} else if (subPat[k]->data[j][4+(l<<1)]==0x0b) {
|
||||
if (nextOrder==-1 || jumpTreatment==0) {
|
||||
nextOrder=effectVal;
|
||||
if (jumpTreatment==1 || jumpTreatment==2 || !jumpingOrder) {
|
||||
nextRow=0;
|
||||
}
|
||||
changingOrder=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i>loopOrder || (i==loopOrder && j>loopRow)) {
|
||||
// we count each row fadeout lasts. When our time is greater than fadeout length we successfully counted the number of fadeout rows
|
||||
if (curLen<=fadeoutLen && fadeoutLen>0.0) {
|
||||
secondsPerThisRow=calcRowLenInSeconds(speeds,curHz,curVirtualTempoN,curVirtualTempoD,curDivider);
|
||||
curLen+=secondsPerThisRow;
|
||||
rowsForFadeout++;
|
||||
}
|
||||
}
|
||||
|
||||
wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7);
|
||||
|
||||
if (nextOrder!=-1) {
|
||||
i=nextOrder-1;
|
||||
orders_vec.push_back(j+1); // order len
|
||||
length+=j+1; // add length of order to song length
|
||||
jumped=true;
|
||||
nextOrder=-1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!jumped) { // if no jump occured we add full pattern length
|
||||
orders_vec.push_back(patLen); // order len
|
||||
length+=patLen; // add length of order to song length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivSubSong::clearData() {
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
pat[i].wipePatterns();
|
||||
|
|
|
@ -185,6 +185,11 @@ struct DivSubSong {
|
|||
*/
|
||||
bool walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat=0);
|
||||
|
||||
/**
|
||||
* find song length in rows (up to specified loop point).
|
||||
*/
|
||||
void findLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders, std::vector<DivGroovePattern>& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat=0);
|
||||
|
||||
void clearData();
|
||||
void optimizePatterns();
|
||||
void rearrangePatterns();
|
||||
|
|
|
@ -504,6 +504,7 @@ void DivEngine::registerSystems() {
|
|||
{0x5d, {DIV_CMD_FM_D2R, _("5Dxx: Set decay 2 of operator 2 (0 to 1F)"), constVal<1>, effectValAnd<31>}},
|
||||
{0x5e, {DIV_CMD_FM_D2R, _("5Exx: Set decay 2 of operator 3 (0 to 1F)"), constVal<2>, effectValAnd<31>}},
|
||||
{0x5f, {DIV_CMD_FM_D2R, _("5Fxx: Set decay 2 of operator 4 (0 to 1F)"), constVal<3>, effectValAnd<31>}},
|
||||
{0x60, {DIV_CMD_FM_OPMASK, _("60xx: Set operator mask (bits 0-3)")}},
|
||||
};
|
||||
|
||||
EffectHandlerMap fmOPMPostEffectHandlerMap(fmOPNPostEffectHandlerMap);
|
||||
|
@ -514,6 +515,7 @@ void DivEngine::registerSystems() {
|
|||
{0x1e, {DIV_CMD_FM_AM_DEPTH, _("1Exx: Set AM depth (0 to 7F)"), effectValAnd<127>}},
|
||||
{0x1f, {DIV_CMD_FM_PM_DEPTH, _("1Fxx: Set PM depth (0 to 7F)"), effectValAnd<127>}},
|
||||
{0x55, {DIV_CMD_FM_SSG, _("55xy: Set detune 2 (x: operator from 1 to 4 (0 for all ops); y: detune from 0 to 3)"), effectOpVal<4>, effectValAnd<3>}},
|
||||
{0x60, {DIV_CMD_FM_OPMASK, _("60xx: Set operator mask (bits 0-3)")}},
|
||||
});
|
||||
|
||||
EffectHandlerMap fmOPZPostEffectHandlerMap(fmOPMPostEffectHandlerMap);
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
|
||||
constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
|
||||
|
||||
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream) {
|
||||
// this function is so long
|
||||
// may as well make it something else
|
||||
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream, bool* sampleStoppable) {
|
||||
unsigned char baseAddr1=isSecond?0xa0:0x50;
|
||||
unsigned char baseAddr2=isSecond?0x80:0;
|
||||
unsigned short baseAddr2S=isSecond?0x8000:0;
|
||||
|
@ -737,6 +739,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
logD("writing stream command %x:%x with stream ID %d",write.addr,write.val,streamID);
|
||||
switch (write.addr&0xff) {
|
||||
case 0: // play sample
|
||||
sampleStoppable[streamID]=true;
|
||||
if (write.val<(unsigned int)song.sampleLen) {
|
||||
if (playingSample[streamID]!=(int)write.val) {
|
||||
pendingFreq[streamID]=write.val;
|
||||
|
@ -775,6 +778,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
}
|
||||
break;
|
||||
case 1: { // set sample freq
|
||||
sampleStoppable[streamID]=true;
|
||||
int realFreq=write.val;
|
||||
if (realFreq<0) realFreq=0;
|
||||
if (realFreq>44100) realFreq=44100;
|
||||
|
@ -818,11 +822,14 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
break;
|
||||
}
|
||||
case 2: // stop sample
|
||||
w->writeC(0x94);
|
||||
w->writeC(streamID);
|
||||
loopSample[streamID]=-1;
|
||||
playingSample[streamID]=-1;
|
||||
pendingFreq[streamID]=-1;
|
||||
if (sampleStoppable[streamID]) {
|
||||
w->writeC(0x94);
|
||||
w->writeC(streamID);
|
||||
loopSample[streamID]=-1;
|
||||
playingSample[streamID]=-1;
|
||||
pendingFreq[streamID]=-1;
|
||||
sampleStoppable[streamID]=false;
|
||||
}
|
||||
break;
|
||||
case 3: // set sample direction
|
||||
sampleDir[streamID]=write.val;
|
||||
|
@ -1321,6 +1328,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
bool sampleDir[DIV_MAX_CHANS];
|
||||
int pendingFreq[DIV_MAX_CHANS];
|
||||
int playingSample[DIV_MAX_CHANS];
|
||||
bool sampleStoppable[DIV_MAX_CHANS];
|
||||
int setPos[DIV_MAX_CHANS];
|
||||
std::vector<unsigned int> chipVol;
|
||||
std::vector<DivDelayedWrite> delayedWrites[DIV_MAX_CHIPS];
|
||||
|
@ -1343,6 +1351,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
playingSample[i]=-1;
|
||||
setPos[i]=0;
|
||||
sampleDir[i]=false;
|
||||
sampleStoppable[i]=true;
|
||||
}
|
||||
|
||||
bool writeDACSamples=false;
|
||||
|
@ -2274,8 +2283,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
w->writeI(0);
|
||||
w->write(writeADPCM_Y8950[i]->getSampleMem(0),writeADPCM_Y8950[i]->getSampleMemUsage(0));
|
||||
}
|
||||
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) {
|
||||
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff);
|
||||
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage(1)>0) {
|
||||
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage(1)+0xffff)&(~0xffff);
|
||||
if (blockSize > 0x1000000) {
|
||||
blockSize = 0x1000000;
|
||||
}
|
||||
|
@ -2283,7 +2292,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
w->writeC(0x66);
|
||||
w->writeC(0x8F);
|
||||
w->writeI((blockSize+8)|(i*0x80000000));
|
||||
w->writeI(writeQSound[i]->getSampleMemCapacity());
|
||||
w->writeI(writeQSound[i]->getSampleMemCapacity(1));
|
||||
w->writeI(0);
|
||||
w->write(writeQSound[i]->getSampleMem(),blockSize);
|
||||
}
|
||||
|
@ -2638,7 +2647,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
for (int i=0; i<song.systemLen; i++) {
|
||||
std::vector<DivRegWrite>& writes=disCont[i].dispatch->getRegisterWrites();
|
||||
for (DivRegWrite& j: writes) {
|
||||
performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i],directStream);
|
||||
performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i],directStream,sampleStoppable);
|
||||
writeCount++;
|
||||
}
|
||||
writes.clear();
|
||||
|
@ -2678,7 +2687,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
lastOne=i.second.time;
|
||||
}
|
||||
// write write
|
||||
performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i.first],directStream);
|
||||
performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i.first],directStream,sampleStoppable);
|
||||
// handle global Furnace commands
|
||||
|
||||
writeCount++;
|
||||
|
@ -2794,7 +2803,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
w->writeWString(ws,false); // japanese author name
|
||||
w->writeS(0); // date
|
||||
w->writeWString(L"Furnace (chiptune tracker)",false); // ripper
|
||||
w->writeS(0); // notes
|
||||
ws=utf8To16(song.notes.c_str());
|
||||
w->writeWString(ws,false); // notes
|
||||
|
||||
int gd3Len=w->tell()-gd3Off-12;
|
||||
|
||||
|
|
|
@ -33,11 +33,78 @@ bool DivEngine::isExporting() {
|
|||
return exporting;
|
||||
}
|
||||
|
||||
void DivEngine::getLoopsLeft(int &loops) {
|
||||
if (totalLoops<0 || exportLoopCount==0) {
|
||||
loops=0;
|
||||
return;
|
||||
}
|
||||
loops=exportLoopCount-1-totalLoops;
|
||||
}
|
||||
|
||||
void DivEngine::getTotalLoops(int &loops) {
|
||||
loops=exportLoopCount-1;
|
||||
}
|
||||
|
||||
void DivEngine::getCurSongPos(int &row, int &order) {
|
||||
row=curRow;
|
||||
order=curOrder;
|
||||
}
|
||||
|
||||
void DivEngine::getTotalAudioFiles(int &files) {
|
||||
files=0;
|
||||
|
||||
switch (exportMode) {
|
||||
case DIV_EXPORT_MODE_ONE: {
|
||||
files=1;
|
||||
break;
|
||||
}
|
||||
case DIV_EXPORT_MODE_MANY_SYS: {
|
||||
files=1; // there actually are several files but they are processed in the same loop, so to correctly draw progress we think of them as one file
|
||||
break;
|
||||
}
|
||||
case DIV_EXPORT_MODE_MANY_CHAN: {
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (exportChannelMask[i]) {
|
||||
files++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DivEngine::getCurFileIndex(int &file) {
|
||||
file=0;
|
||||
|
||||
switch (exportMode) {
|
||||
case DIV_EXPORT_MODE_ONE: {
|
||||
file=0;
|
||||
break;
|
||||
}
|
||||
case DIV_EXPORT_MODE_MANY_SYS: {
|
||||
file=0; // there actually are several files but they are processed in the same loop, so to correctly draw progress we think of them as one file
|
||||
break;
|
||||
}
|
||||
case DIV_EXPORT_MODE_MANY_CHAN: {
|
||||
file=curExportChan;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool DivEngine::getIsFadingOut() {
|
||||
return isFadingOut;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SNDFILE
|
||||
void DivEngine::runExportThread() {
|
||||
size_t fadeOutSamples=got.rate*exportFadeOut;
|
||||
size_t curFadeOutSample=0;
|
||||
bool isFadingOut=false;
|
||||
isFadingOut=false;
|
||||
|
||||
switch (exportMode) {
|
||||
case DIV_EXPORT_MODE_ONE: {
|
||||
|
@ -140,7 +207,11 @@ void DivEngine::runExportThread() {
|
|||
sf[i]=NULL;
|
||||
si[i].samplerate=got.rate;
|
||||
si[i].channels=disCont[i].dispatch->getOutputCount();
|
||||
si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
|
||||
if (exportFormat==DIV_EXPORT_FORMAT_S16) {
|
||||
si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
|
||||
} else {
|
||||
si[i].format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
|
@ -247,6 +318,8 @@ void DivEngine::runExportThread() {
|
|||
// take control of audio output
|
||||
deinitAudioBackend();
|
||||
|
||||
curExportChan=0;
|
||||
|
||||
float* outBuf[DIV_MAX_OUTPUTS];
|
||||
float* outBufFinal;
|
||||
for (int i=0; i<exportOutputs; i++) {
|
||||
|
@ -258,6 +331,7 @@ void DivEngine::runExportThread() {
|
|||
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (!exportChannelMask[i]) continue;
|
||||
|
||||
SNDFILE* sf;
|
||||
SF_INFO si;
|
||||
SFWrapper sfWrap;
|
||||
|
@ -338,6 +412,8 @@ void DivEngine::runExportThread() {
|
|||
}
|
||||
}
|
||||
|
||||
curExportChan++;
|
||||
|
||||
if (sfWrap.doClose()!=0) {
|
||||
logE("could not close audio file!");
|
||||
}
|
||||
|
@ -378,6 +454,7 @@ void DivEngine::runExportThread() {
|
|||
}
|
||||
logI("done!");
|
||||
exporting=false;
|
||||
curExportChan=0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 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 _ZSM_H
|
||||
#define _ZSM_H
|
||||
|
||||
//#include "engine.h"
|
||||
#include "safeWriter.h"
|
||||
#include "dispatch.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#define ZSM_HEADER_SIZE 16
|
||||
#define ZSM_VERSION 1
|
||||
#define ZSM_YM_CMD 0x40
|
||||
#define ZSM_DELAY_CMD 0x80
|
||||
#define ZSM_YM_MAX_WRITES 63
|
||||
#define ZSM_SYNC_MAX_WRITES 31
|
||||
#define ZSM_DELAY_MAX 127
|
||||
#define ZSM_EOF ZSM_DELAY_CMD
|
||||
|
||||
#define ZSM_EXT ZSM_YM_CMD
|
||||
#define ZSM_EXT_PCM 0x00
|
||||
#define ZSM_EXT_CHIP 0x40
|
||||
#define ZSM_EXT_SYNC 0x80
|
||||
#define ZSM_EXT_CUSTOM 0xC0
|
||||
|
||||
enum YM_STATE { ym_PREV, ym_NEW, ym_STATES };
|
||||
enum PSG_STATE { psg_PREV, psg_NEW, psg_STATES };
|
||||
|
||||
class DivZSM {
|
||||
private:
|
||||
struct S_pcmInst {
|
||||
int geometry;
|
||||
unsigned int offset, length, loopPoint;
|
||||
bool isLooped;
|
||||
};
|
||||
SafeWriter* w;
|
||||
int ymState[ym_STATES][256];
|
||||
int psgState[psg_STATES][64];
|
||||
int pcmRateCache;
|
||||
int pcmCtrlRVCache;
|
||||
int pcmCtrlDCCache;
|
||||
unsigned int pcmLoopPointCache;
|
||||
bool pcmIsLooped;
|
||||
std::vector<DivRegWrite> ymwrites;
|
||||
std::vector<DivRegWrite> pcmMeta;
|
||||
std::vector<unsigned char> pcmData;
|
||||
std::vector<unsigned char> pcmCache;
|
||||
std::vector<S_pcmInst> pcmInsts;
|
||||
std::vector<DivRegWrite> syncCache;
|
||||
int loopOffset;
|
||||
int numWrites;
|
||||
int ticks;
|
||||
int tickRate;
|
||||
int ymMask;
|
||||
int psgMask;
|
||||
bool optimize;
|
||||
public:
|
||||
DivZSM();
|
||||
~DivZSM();
|
||||
void init(unsigned int rate = 60);
|
||||
int getoffset();
|
||||
void writeYM(unsigned char a, unsigned char v);
|
||||
void writePSG(unsigned char a, unsigned char v);
|
||||
void writePCM(unsigned char a, unsigned char v);
|
||||
void writeSync(unsigned char a, unsigned char v);
|
||||
void setOptimize(bool o);
|
||||
void tick(int numticks = 1);
|
||||
void setLoopPoint();
|
||||
SafeWriter* finish();
|
||||
private:
|
||||
void flushWrites();
|
||||
void flushTicks();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,208 +0,0 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 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 "../ta-log.h"
|
||||
#include "../utfutils.h"
|
||||
#include "song.h"
|
||||
#include "zsm.h"
|
||||
|
||||
constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
|
||||
constexpr int MASTER_CLOCK_MASK=(sizeof(void*)==8)?0xff:0;
|
||||
|
||||
SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop, bool optimize) {
|
||||
int VERA=-1;
|
||||
int YM=-1;
|
||||
int IGNORED=0;
|
||||
|
||||
// find indexes for YM and VERA. Ignore other systems.
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
switch (song.system[i]) {
|
||||
case DIV_SYSTEM_VERA:
|
||||
if (VERA>=0) {
|
||||
IGNORED++;
|
||||
break;
|
||||
}
|
||||
VERA=i;
|
||||
logD("VERA detected as chip id %d",i);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2151:
|
||||
if (YM>=0) {
|
||||
IGNORED++;
|
||||
break;
|
||||
}
|
||||
YM=i;
|
||||
logD("YM detected as chip id %d",i);
|
||||
break;
|
||||
default:
|
||||
IGNORED++;
|
||||
logD("Ignoring chip %d systemID %d",i,(int)song.system[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (VERA<0 && YM<0) {
|
||||
logE("No supported systems for ZSM");
|
||||
return NULL;
|
||||
}
|
||||
if (IGNORED>0) {
|
||||
logW("ZSM export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' ');
|
||||
}
|
||||
|
||||
stop();
|
||||
repeatPattern=false;
|
||||
setOrder(0);
|
||||
BUSY_BEGIN_SOFT;
|
||||
|
||||
double origRate=got.rate;
|
||||
got.rate=zsmrate&0xffff;
|
||||
|
||||
// determine loop point
|
||||
int loopOrder=0;
|
||||
int loopRow=0;
|
||||
int loopEnd=0;
|
||||
walkSong(loopOrder,loopRow,loopEnd);
|
||||
logI("loop point: %d %d",loopOrder,loopRow);
|
||||
warnings="";
|
||||
|
||||
DivZSM zsm;
|
||||
zsm.init(zsmrate);
|
||||
|
||||
// reset the playback state
|
||||
curOrder=0;
|
||||
freelance=false;
|
||||
playing=false;
|
||||
extValuePresent=false;
|
||||
remainingLoops=-1;
|
||||
|
||||
// Prepare to write song data
|
||||
playSub(false);
|
||||
//size_t tickCount=0;
|
||||
bool done=false;
|
||||
bool loopNow=false;
|
||||
int loopPos=-1;
|
||||
int fracWait=0; // accumulates fractional ticks
|
||||
if (VERA>=0) disCont[VERA].dispatch->toggleRegisterDump(true);
|
||||
if (YM>=0) {
|
||||
disCont[YM].dispatch->toggleRegisterDump(true);
|
||||
// emit LFO initialization commands
|
||||
zsm.writeYM(0x18,0); // freq=0
|
||||
zsm.writeYM(0x19,0x7F); // AMD =7F
|
||||
zsm.writeYM(0x19,0xFF); // PMD =7F
|
||||
// TODO: incorporate the Furnace meta-command for init data and filter
|
||||
// out writes to otherwise-unused channels.
|
||||
}
|
||||
// Indicate the song's tuning as a sync meta-event
|
||||
// specified in terms of how many 1/256th semitones
|
||||
// the song is offset from standard A-440 tuning.
|
||||
// This is mainly to benefit visualizations in players
|
||||
// for non-standard tunings so that they can avoid
|
||||
// displaying the entire song held in pitch bend.
|
||||
// Tunings offsets that exceed a half semitone
|
||||
// will simply be represented in a different key
|
||||
// by nature of overflowing the signed char value
|
||||
signed char tuningoffset=(signed char)(round(3072*(log(song.tuning/440.0)/log(2))))&0xff;
|
||||
zsm.writeSync(0x01,tuningoffset);
|
||||
// Set optimize flag, which mainly buffers PSG writes
|
||||
// whenever the channel is silent
|
||||
zsm.setOptimize(optimize);
|
||||
|
||||
while (!done) {
|
||||
if (loopPos==-1) {
|
||||
if (loopOrder==curOrder && loopRow==curRow && loop)
|
||||
loopNow=true;
|
||||
if (loopNow) {
|
||||
// If Virtual Tempo is in use, our exact loop point
|
||||
// might be skipped due to quantization error.
|
||||
// If this happens, the tick immediately following is our loop point.
|
||||
if (ticks==1 || !(loopOrder==curOrder && loopRow==curRow)) {
|
||||
loopPos=zsm.getoffset();
|
||||
zsm.setLoopPoint();
|
||||
loopNow=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nextTick() || !playing) {
|
||||
done=true;
|
||||
if (!loop) {
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
disCont[i].dispatch->getRegisterWrites().clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!playing) {
|
||||
loopPos=-1;
|
||||
}
|
||||
}
|
||||
// get register dumps
|
||||
for (int j=0; j<2; j++) {
|
||||
int i=0;
|
||||
// dump YM writes first
|
||||
if (j==0) {
|
||||
if (YM<0) {
|
||||
continue;
|
||||
} else {
|
||||
i=YM;
|
||||
}
|
||||
}
|
||||
// dump VERA writes second
|
||||
if (j==1) {
|
||||
if (VERA<0) {
|
||||
continue;
|
||||
} else {
|
||||
i=VERA;
|
||||
}
|
||||
}
|
||||
std::vector<DivRegWrite>& writes=disCont[i].dispatch->getRegisterWrites();
|
||||
if (writes.size()>0)
|
||||
logD("zsmOps: Writing %d messages to chip %d",writes.size(),i);
|
||||
for (DivRegWrite& write: writes) {
|
||||
if (i==YM) zsm.writeYM(write.addr&0xff,write.val);
|
||||
if (i==VERA) {
|
||||
if (done && write.addr>=64) continue; // don't process any PCM or sync events on the loop lookahead
|
||||
zsm.writePSG(write.addr&0xff,write.val);
|
||||
}
|
||||
}
|
||||
writes.clear();
|
||||
}
|
||||
|
||||
// write wait
|
||||
int totalWait=cycles>>MASTER_CLOCK_PREC;
|
||||
fracWait+=cycles&MASTER_CLOCK_MASK;
|
||||
totalWait+=fracWait>>MASTER_CLOCK_PREC;
|
||||
fracWait&=MASTER_CLOCK_MASK;
|
||||
if (totalWait>0 && !done) {
|
||||
zsm.tick(totalWait);
|
||||
//tickCount+=totalWait;
|
||||
}
|
||||
}
|
||||
// end of song
|
||||
|
||||
// done - close out.
|
||||
got.rate=origRate;
|
||||
if (VERA>=0) disCont[VERA].dispatch->toggleRegisterDump(false);
|
||||
if (YM>=0) disCont[YM].dispatch->toggleRegisterDump(false);
|
||||
|
||||
remainingLoops=-1;
|
||||
playing=false;
|
||||
freelance=false;
|
||||
extValuePresent=false;
|
||||
|
||||
BUSY_END;
|
||||
return zsm.finish();
|
||||
}
|
|
@ -42,6 +42,7 @@ template<typename T, size_t items> struct FixedQueue {
|
|||
void clear();
|
||||
bool empty();
|
||||
size_t size();
|
||||
size_t capacity();
|
||||
FixedQueue():
|
||||
readPos(0),
|
||||
writePos(0) {}
|
||||
|
@ -177,4 +178,8 @@ template <typename T, size_t items> size_t FixedQueue<T,items>::size() {
|
|||
return writePos-readPos;
|
||||
}
|
||||
|
||||
template <typename T, size_t items> size_t FixedQueue<T,items>::capacity() {
|
||||
return items-1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -35,6 +35,7 @@ const char* aboutLine[]={
|
|||
_N("-- program --"),
|
||||
"tildearrow",
|
||||
_N("A M 4 N (intro tune)"),
|
||||
"Adam Lederer",
|
||||
"akumanatt",
|
||||
"cam900",
|
||||
"djtuBIG-MaliceX",
|
||||
|
@ -302,6 +303,7 @@ const char* aboutLine[]={
|
|||
_N("openMSX YMF278 emulator (modified version) by the openMSX developers"),
|
||||
"",
|
||||
_N("greetings to:"),
|
||||
"floxy!",
|
||||
"NEOART Costa Rica",
|
||||
"Xenium Demoparty",
|
||||
"@party",
|
||||
|
|
|
@ -161,6 +161,8 @@ void FurnaceGUI::drawCSPlayer() {
|
|||
ImGui::TableNextColumn();
|
||||
ImGui::Text(_("vols"));
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(_("volst"));
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(_("vib"));
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(_("porta"));
|
||||
|
@ -189,6 +191,8 @@ void FurnaceGUI::drawCSPlayer() {
|
|||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%+d",state->volSpeed);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%+d",state->volSpeedTarget);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d/%d (%d)",state->vibratoDepth,state->vibratoRate,state->vibratoPos);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("-> %d (%d)",state->portaTarget,state->portaSpeed);
|
||||
|
|
|
@ -145,6 +145,7 @@ void FurnaceGUI::drawDebug() {
|
|||
ImGui::Text("- portaNote = %d",ch->portaNote);
|
||||
ImGui::Text("- volume = %.4x",ch->volume);
|
||||
ImGui::Text("- volSpeed = %d",ch->volSpeed);
|
||||
ImGui::Text("- volSpeedTarget = %d",ch->volSpeedTarget);
|
||||
ImGui::Text("- cut = %d",ch->cut);
|
||||
ImGui::Text("- rowDelay = %d",ch->rowDelay);
|
||||
ImGui::Text("- volMax = %.4x",ch->volMax);
|
||||
|
|
|
@ -73,6 +73,8 @@ void FurnaceGUI::doAction(int what) {
|
|||
case GUI_ACTION_UNDO:
|
||||
if (curWindow==GUI_WINDOW_SAMPLE_EDIT) {
|
||||
doUndoSample();
|
||||
} else if (curWindow==GUI_WINDOW_INS_EDIT) {
|
||||
doUndoInstrument();
|
||||
} else {
|
||||
doUndo();
|
||||
}
|
||||
|
@ -80,6 +82,8 @@ void FurnaceGUI::doAction(int what) {
|
|||
case GUI_ACTION_REDO:
|
||||
if (curWindow==GUI_WINDOW_SAMPLE_EDIT) {
|
||||
doRedoSample();
|
||||
} else if (curWindow==GUI_WINDOW_INS_EDIT) {
|
||||
doRedoInstrument();
|
||||
} else {
|
||||
doRedo();
|
||||
}
|
||||
|
@ -123,16 +127,16 @@ void FurnaceGUI::doAction(int what) {
|
|||
pendingStepUpdate=1;
|
||||
break;
|
||||
case GUI_ACTION_OCTAVE_UP:
|
||||
if (++curOctave>7) {
|
||||
curOctave=7;
|
||||
if (++curOctave>GUI_EDIT_OCTAVE_MAX) {
|
||||
curOctave=GUI_EDIT_OCTAVE_MAX;
|
||||
} else {
|
||||
e->autoNoteOffAll();
|
||||
failedNoteOn=false;
|
||||
}
|
||||
break;
|
||||
case GUI_ACTION_OCTAVE_DOWN:
|
||||
if (--curOctave<-5) {
|
||||
curOctave=-5;
|
||||
if (--curOctave<GUI_EDIT_OCTAVE_MIN) {
|
||||
curOctave=GUI_EDIT_OCTAVE_MIN;
|
||||
} else {
|
||||
e->autoNoteOffAll();
|
||||
failedNoteOn=false;
|
||||
|
@ -677,14 +681,7 @@ void FurnaceGUI::doAction(int what) {
|
|||
latchNibble=false;
|
||||
break;
|
||||
case GUI_ACTION_PAT_ABSORB_INSTRUMENT: {
|
||||
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],false);
|
||||
if (!pat) break;
|
||||
for (int i=cursor.y; i>=0; i--) {
|
||||
if (pat->data[i][2] >= 0) {
|
||||
curIns=pat->data[i][2];
|
||||
break;
|
||||
}
|
||||
}
|
||||
doAbsorbInstrument();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -508,7 +508,7 @@ void FurnaceGUI::drawMobileControls() {
|
|||
mobileMenuOpen=false;
|
||||
doAction(GUI_ACTION_SAVE_AS);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(_("Export"))) {
|
||||
doAction(GUI_ACTION_EXPORT);
|
||||
}
|
||||
|
@ -533,6 +533,10 @@ void FurnaceGUI::drawMobileControls() {
|
|||
drawSpeed(true);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem(_("Comments"))) {
|
||||
drawNotes(true);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
break;
|
||||
|
@ -647,8 +651,8 @@ void FurnaceGUI::drawEditControls() {
|
|||
ImGui::TableNextColumn();
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
if (ImGui::InputInt("##Octave",&curOctave,1,1)) {
|
||||
if (curOctave>7) curOctave=7;
|
||||
if (curOctave<-5) curOctave=-5;
|
||||
if (curOctave>GUI_EDIT_OCTAVE_MAX) curOctave=GUI_EDIT_OCTAVE_MAX;
|
||||
if (curOctave<GUI_EDIT_OCTAVE_MIN) curOctave=GUI_EDIT_OCTAVE_MIN;
|
||||
e->autoNoteOffAll();
|
||||
failedNoteOn=false;
|
||||
|
||||
|
@ -808,8 +812,8 @@ void FurnaceGUI::drawEditControls() {
|
|||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(96.0f*dpiScale);
|
||||
if (ImGui::InputInt("##Octave",&curOctave,1,1)) {
|
||||
if (curOctave>7) curOctave=7;
|
||||
if (curOctave<-5) curOctave=-5;
|
||||
if (curOctave>GUI_EDIT_OCTAVE_MAX) curOctave=GUI_EDIT_OCTAVE_MAX;
|
||||
if (curOctave<GUI_EDIT_OCTAVE_MIN) curOctave=GUI_EDIT_OCTAVE_MIN;
|
||||
e->autoNoteOffAll();
|
||||
failedNoteOn=false;
|
||||
|
||||
|
@ -926,8 +930,8 @@ void FurnaceGUI::drawEditControls() {
|
|||
float avail=ImGui::GetContentRegionAvail().x;
|
||||
ImGui::SetNextItemWidth(avail);
|
||||
if (ImGui::InputInt("##Octave",&curOctave,0,0)) {
|
||||
if (curOctave>7) curOctave=7;
|
||||
if (curOctave<-5) curOctave=-5;
|
||||
if (curOctave>GUI_EDIT_OCTAVE_MAX) curOctave=GUI_EDIT_OCTAVE_MAX;
|
||||
if (curOctave<GUI_EDIT_OCTAVE_MIN) curOctave=GUI_EDIT_OCTAVE_MIN;
|
||||
e->autoNoteOffAll();
|
||||
failedNoteOn=false;
|
||||
|
||||
|
@ -1093,8 +1097,8 @@ void FurnaceGUI::drawEditControls() {
|
|||
float avail=ImGui::GetContentRegionAvail().x;
|
||||
ImGui::SetNextItemWidth(avail);
|
||||
if (ImGui::InputInt("##Octave",&curOctave,1,1)) {
|
||||
if (curOctave>7) curOctave=7;
|
||||
if (curOctave<-5) curOctave=-5;
|
||||
if (curOctave>GUI_EDIT_OCTAVE_MAX) curOctave=GUI_EDIT_OCTAVE_MAX;
|
||||
if (curOctave<GUI_EDIT_OCTAVE_MIN) curOctave=GUI_EDIT_OCTAVE_MIN;
|
||||
e->autoNoteOffAll();
|
||||
failedNoteOn=false;
|
||||
|
||||
|
|
|
@ -1823,6 +1823,52 @@ void FurnaceGUI::doExpandSong(int multiplier) {
|
|||
if (e->isPlaying()) e->play();
|
||||
}
|
||||
|
||||
void FurnaceGUI::doAbsorbInstrument() {
|
||||
bool foundIns=false;
|
||||
bool foundOctave=false;
|
||||
auto foundAll = [&]() { return foundIns && foundOctave; };
|
||||
|
||||
// search this order and all prior until we find all the data we need
|
||||
int orderIdx=curOrder;
|
||||
for (; orderIdx>=0 && !foundAll(); orderIdx--) {
|
||||
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][orderIdx],false);
|
||||
if (!pat) continue;
|
||||
|
||||
// start on current row when searching current order, but start from end when searching
|
||||
// prior orders.
|
||||
int searchStartRow=orderIdx==curOrder ? cursor.y : e->curSubSong->patLen-1;
|
||||
for (int i=searchStartRow; i>=0 && !foundAll(); i--) {
|
||||
|
||||
// absorb most recent instrument
|
||||
if (!foundIns && pat->data[i][2] >= 0) {
|
||||
foundIns=true;
|
||||
curIns=pat->data[i][2];
|
||||
}
|
||||
|
||||
// absorb most recent octave (i.e. set curOctave such that the "main row" (QWERTY) of
|
||||
// notes will result in an octave number equal to the previous note).
|
||||
if (!foundOctave && pat->data[i][0] != 0) {
|
||||
foundOctave=true;
|
||||
|
||||
// decode octave data (was signed cast to unsigned char)
|
||||
int octave=pat->data[i][1];
|
||||
if (octave>128) octave-=256;
|
||||
|
||||
// @NOTE the special handling when note==12, which is really an octave above what's
|
||||
// stored in the octave data. without this handling, if you press Q, then
|
||||
// "ABSORB_INSTRUMENT", then Q again, you'd get a different octave!
|
||||
if (pat->data[i][0]==12) octave++;
|
||||
curOctave=CLAMP(octave-1, GUI_EDIT_OCTAVE_MIN, GUI_EDIT_OCTAVE_MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if no instrument has been set at this point, the only way to match it is to use "none"
|
||||
if (!foundIns) curIns=-1;
|
||||
|
||||
logD("doAbsorbInstrument -- searched %d orders", curOrder-orderIdx);
|
||||
}
|
||||
|
||||
void FurnaceGUI::doDrag() {
|
||||
int len=dragEnd.xCoarse-dragStart.xCoarse+1;
|
||||
|
||||
|
|
|
@ -319,6 +319,29 @@ void FurnaceGUI::drawExportROM(bool onWindow) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_ROM_ZSM: {
|
||||
int zsmExportTickRate=romConfig.getInt("zsmrate",60);
|
||||
bool zsmExportLoop=romConfig.getBool("loop",true);
|
||||
bool zsmExportOptimize=romConfig.getBool("optimize",true);
|
||||
|
||||
if (ImGui::InputInt(_("Tick Rate (Hz)"),&zsmExportTickRate,1,2)) {
|
||||
if (zsmExportTickRate<1) zsmExportTickRate=1;
|
||||
if (zsmExportTickRate>44100) zsmExportTickRate=44100;
|
||||
altered=true;
|
||||
}
|
||||
if (ImGui::Checkbox(_("loop"),&zsmExportLoop)) {
|
||||
altered=true;
|
||||
}
|
||||
if (ImGui::Checkbox(_("optimize size"),&zsmExportOptimize)) {
|
||||
altered=true;
|
||||
}
|
||||
if (altered) {
|
||||
romConfig.set("zsmrate",zsmExportTickRate);
|
||||
romConfig.set("loop",zsmExportLoop);
|
||||
romConfig.set("optimize",zsmExportOptimize);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_ROM_ABSTRACT:
|
||||
ImGui::TextWrapped("%s",_("select a target from the menu at the top of this dialog."));
|
||||
break;
|
||||
|
@ -340,28 +363,6 @@ void FurnaceGUI::drawExportROM(bool onWindow) {
|
|||
}
|
||||
}
|
||||
|
||||
void FurnaceGUI::drawExportZSM(bool onWindow) {
|
||||
exitDisabledTimer=1;
|
||||
|
||||
ImGui::Text(_("Commander X16 Zsound Music File"));
|
||||
if (ImGui::InputInt(_("Tick Rate (Hz)"),&zsmExportTickRate,1,2)) {
|
||||
if (zsmExportTickRate<1) zsmExportTickRate=1;
|
||||
if (zsmExportTickRate>44100) zsmExportTickRate=44100;
|
||||
}
|
||||
ImGui::Checkbox(_("loop"),&zsmExportLoop);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox(_("optimize size"),&zsmExportOptimize);
|
||||
if (onWindow) {
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button(_("Cancel"),ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup();
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (ImGui::Button(_("Export"),ImVec2(200.0f*dpiScale,0))) {
|
||||
openFileDialog(GUI_FILE_EXPORT_ZSM);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void FurnaceGUI::drawExportText(bool onWindow) {
|
||||
exitDisabledTimer=1;
|
||||
|
||||
|
@ -444,16 +445,6 @@ void FurnaceGUI::drawExport() {
|
|||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
int numZSMCompat=0;
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++;
|
||||
}
|
||||
if (numZSMCompat>0) {
|
||||
if (ImGui::BeginTabItem(_("ZSM"))) {
|
||||
drawExportZSM(true);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginTabItem(_("Text"))) {
|
||||
drawExportText(true);
|
||||
ImGui::EndTabItem();
|
||||
|
@ -478,9 +469,6 @@ void FurnaceGUI::drawExport() {
|
|||
case GUI_EXPORT_ROM:
|
||||
drawExportROM(true);
|
||||
break;
|
||||
case GUI_EXPORT_ZSM:
|
||||
drawExportZSM(true);
|
||||
break;
|
||||
case GUI_EXPORT_TEXT:
|
||||
drawExportText(true);
|
||||
break;
|
||||
|
|
176
src/gui/gui.cpp
176
src/gui/gui.cpp
|
@ -995,7 +995,7 @@ Collapsed=0\n\
|
|||
\n\
|
||||
[Window][Rendering...]\n\
|
||||
Pos=585,342\n\
|
||||
Size=114,71\n\
|
||||
Size=600,100\n\
|
||||
Collapsed=0\n\
|
||||
\n\
|
||||
[Window][Export VGM##FileDialog]\n\
|
||||
|
@ -2022,16 +2022,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
|||
(settings.autoFillSave)?shortName:""
|
||||
);
|
||||
break;
|
||||
case GUI_FILE_EXPORT_ZSM:
|
||||
if (!dirExists(workingDirZSMExport)) workingDirZSMExport=getHomeDir();
|
||||
hasOpened=fileDialog->openSave(
|
||||
_("Export ZSM"),
|
||||
{_("ZSM file"), "*.zsm"},
|
||||
workingDirZSMExport,
|
||||
dpiScale,
|
||||
(settings.autoFillSave)?shortName:""
|
||||
);
|
||||
break;
|
||||
case GUI_FILE_EXPORT_TEXT:
|
||||
if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir();
|
||||
hasOpened=fileDialog->openSave(
|
||||
|
@ -2592,7 +2582,40 @@ int FurnaceGUI::loadStream(String path) {
|
|||
|
||||
|
||||
void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
|
||||
songOrdersLengths.clear();
|
||||
|
||||
int loopOrder=0;
|
||||
int loopRow=0;
|
||||
int loopEnd=0;
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
|
||||
e->findSongLength(loopOrder,loopRow,audioExportOptions.fadeOut,songFadeoutSectionLength,songHasSongEndCommand,songOrdersLengths,songLength); // for progress estimation
|
||||
|
||||
songLoopedSectionLength=songLength;
|
||||
for (int i=0; i<loopOrder; i++) {
|
||||
songLoopedSectionLength-=songOrdersLengths[i];
|
||||
}
|
||||
songLoopedSectionLength-=loopRow;
|
||||
|
||||
e->saveAudio(path.c_str(),audioExportOptions);
|
||||
|
||||
totalFiles=0;
|
||||
e->getTotalAudioFiles(totalFiles);
|
||||
int totalLoops=0;
|
||||
|
||||
lengthOfOneFile=songLength;
|
||||
|
||||
if (!songHasSongEndCommand) {
|
||||
e->getTotalLoops(totalLoops);
|
||||
|
||||
lengthOfOneFile+=songLoopedSectionLength*totalLoops;
|
||||
lengthOfOneFile+=songFadeoutSectionLength; // account for fadeout
|
||||
}
|
||||
|
||||
totalLength=lengthOfOneFile*totalFiles;
|
||||
|
||||
curProgress=0.0f;
|
||||
|
||||
displayExporting=true;
|
||||
}
|
||||
|
||||
|
@ -3714,6 +3737,7 @@ bool FurnaceGUI::loop() {
|
|||
ImGui::GetIO().AddKeyEvent(ImGuiKey_Backspace,false);
|
||||
injectBackUp=false;
|
||||
}
|
||||
|
||||
while (SDL_PollEvent(&ev)) {
|
||||
WAKE_UP;
|
||||
ImGui_ImplSDL2_ProcessEvent(&ev);
|
||||
|
@ -3730,13 +3754,16 @@ bool FurnaceGUI::loop() {
|
|||
}
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
pointUp(ev.button.x,ev.button.y,ev.button.button);
|
||||
insEditMayBeDirty=true;
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
pointDown(ev.button.x,ev.button.y,ev.button.button);
|
||||
insEditMayBeDirty=true;
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
wheelX+=ev.wheel.x;
|
||||
wheelY+=ev.wheel.y;
|
||||
insEditMayBeDirty=true;
|
||||
break;
|
||||
case SDL_WINDOWEVENT:
|
||||
switch (ev.window.event) {
|
||||
|
@ -3813,12 +3840,14 @@ bool FurnaceGUI::loop() {
|
|||
if (!ImGui::GetIO().WantCaptureKeyboard) {
|
||||
keyDown(ev);
|
||||
}
|
||||
insEditMayBeDirty=true;
|
||||
#ifdef IS_MOBILE
|
||||
injectBackUp=true;
|
||||
#endif
|
||||
break;
|
||||
case SDL_KEYUP:
|
||||
// for now
|
||||
insEditMayBeDirty=true;
|
||||
break;
|
||||
case SDL_DROPFILE:
|
||||
if (ev.drop.file!=NULL) {
|
||||
|
@ -4405,16 +4434,6 @@ bool FurnaceGUI::loop() {
|
|||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
int numZSMCompat=0;
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++;
|
||||
}
|
||||
if (numZSMCompat>0) {
|
||||
if (ImGui::BeginMenu(_("export ZSM..."))) {
|
||||
drawExportZSM();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginMenu(_("export text..."))) {
|
||||
drawExportText();
|
||||
ImGui::EndMenu();
|
||||
|
@ -4442,16 +4461,6 @@ bool FurnaceGUI::loop() {
|
|||
displayExport=true;
|
||||
}
|
||||
}
|
||||
int numZSMCompat=0;
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++;
|
||||
}
|
||||
if (numZSMCompat>0) {
|
||||
if (ImGui::MenuItem(_("export ZSM..."))) {
|
||||
curExportType=GUI_EXPORT_ZSM;
|
||||
displayExport=true;
|
||||
}
|
||||
}
|
||||
if (ImGui::MenuItem(_("export text..."))) {
|
||||
curExportType=GUI_EXPORT_TEXT;
|
||||
displayExport=true;
|
||||
|
@ -4477,7 +4486,7 @@ bool FurnaceGUI::loop() {
|
|||
} else {
|
||||
if (ImGui::BeginMenu(_("add chip..."))) {
|
||||
exitDisabledTimer=1;
|
||||
DivSystem picked=systemPicker();
|
||||
DivSystem picked=systemPicker(false);
|
||||
if (picked!=DIV_SYSTEM_NULL) {
|
||||
if (!e->addSystem(picked)) {
|
||||
showError(fmt::sprintf(_("cannot add chip! (%s)"),e->getLastError()));
|
||||
|
@ -4508,7 +4517,7 @@ bool FurnaceGUI::loop() {
|
|||
ImGui::Checkbox(_("Preserve channel positions"),&preserveChanPos);
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) {
|
||||
DivSystem picked=systemPicker();
|
||||
DivSystem picked=systemPicker(false);
|
||||
if (picked!=DIV_SYSTEM_NULL) {
|
||||
if (e->changeSystem(i,picked,preserveChanPos)) {
|
||||
MARK_MODIFIED;
|
||||
|
@ -4778,7 +4787,7 @@ bool FurnaceGUI::loop() {
|
|||
info=fmt::sprintf(_("Set volume: %d (%.2X, INVALID!)"),p->data[cursor.y][3],p->data[cursor.y][3]);
|
||||
} else {
|
||||
float realVol=e->getGain(cursor.xCoarse,p->data[cursor.y][3]);
|
||||
info=fmt::sprintf(_("Set volume: %d (%.2X, %d%%)"),p->data[cursor.y][3],p->data[cursor.y][3],(int)(realVol*100.0f/(float)maxVol));
|
||||
info=fmt::sprintf(_("Set volume: %d (%.2X, %d%%)"),p->data[cursor.y][3],p->data[cursor.y][3],(int)(realVol*100.0f));
|
||||
}
|
||||
hasInfo=true;
|
||||
}
|
||||
|
@ -5034,9 +5043,6 @@ bool FurnaceGUI::loop() {
|
|||
case GUI_FILE_EXPORT_VGM:
|
||||
workingDirVGMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||
break;
|
||||
case GUI_FILE_EXPORT_ZSM:
|
||||
workingDirZSMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||
break;
|
||||
case GUI_FILE_EXPORT_ROM:
|
||||
case GUI_FILE_EXPORT_TEXT:
|
||||
case GUI_FILE_EXPORT_CMDSTREAM:
|
||||
|
@ -5136,9 +5142,6 @@ bool FurnaceGUI::loop() {
|
|||
if (curFileDialog==GUI_FILE_EXPORT_ROM) {
|
||||
checkExtension(romFilterExt.c_str());
|
||||
}
|
||||
if (curFileDialog==GUI_FILE_EXPORT_ZSM) {
|
||||
checkExtension(".zsm");
|
||||
}
|
||||
if (curFileDialog==GUI_FILE_EXPORT_TEXT) {
|
||||
checkExtension(".txt");
|
||||
}
|
||||
|
@ -5614,27 +5617,6 @@ bool FurnaceGUI::loop() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case GUI_FILE_EXPORT_ZSM: {
|
||||
SafeWriter* w=e->saveZSM(zsmExportTickRate,zsmExportLoop,zsmExportOptimize);
|
||||
if (w!=NULL) {
|
||||
FILE* f=ps_fopen(copyOfName.c_str(),"wb");
|
||||
if (f!=NULL) {
|
||||
fwrite(w->getFinalBuf(),1,w->size(),f);
|
||||
fclose(f);
|
||||
pushRecentSys(copyOfName.c_str());
|
||||
} else {
|
||||
showError(_("could not open file!"));
|
||||
}
|
||||
w->finish();
|
||||
delete w;
|
||||
if (!e->getWarnings().empty()) {
|
||||
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
|
||||
}
|
||||
} else {
|
||||
showError(fmt::sprintf(_("Could not write ZSM! (%s)"),e->getLastError()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GUI_FILE_EXPORT_ROM:
|
||||
romExportPath=copyOfName;
|
||||
pendingExport=e->buildROM(romTarget);
|
||||
|
@ -5877,8 +5859,54 @@ bool FurnaceGUI::loop() {
|
|||
MEASURE_BEGIN(popup);
|
||||
|
||||
centerNextWindow(_("Rendering..."),canvasW,canvasH);
|
||||
if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text(_("Please wait..."));
|
||||
if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove)) {
|
||||
// WHAT the HELL?!
|
||||
WAKE_UP;
|
||||
if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_CHAN) {
|
||||
ImGui::Text(_("Please wait..."));
|
||||
}
|
||||
float* progressLambda=&curProgress;
|
||||
int curPosInRows=0;
|
||||
int* curPosInRowsLambda=&curPosInRows;
|
||||
int loopsLeft=0;
|
||||
int* loopsLeftLambda=&loopsLeft;
|
||||
int totalLoops=0;
|
||||
int* totalLoopsLambda=&totalLoops;
|
||||
int curFile=0;
|
||||
int* curFileLambda=&curFile;
|
||||
if (e->isExporting()) {
|
||||
e->lockEngine([this, progressLambda, curPosInRowsLambda, curFileLambda,
|
||||
loopsLeftLambda, totalLoopsLambda] () {
|
||||
int curRow=0; int curOrder=0;
|
||||
e->getCurSongPos(curRow, curOrder); *curFileLambda=0;
|
||||
e->getCurFileIndex(*curFileLambda);
|
||||
*curPosInRowsLambda=curRow; for (int i=0; i<curOrder;
|
||||
i++) {
|
||||
*curPosInRowsLambda+=songOrdersLengths[i];}
|
||||
|
||||
if (!songHasSongEndCommand) {
|
||||
e->getLoopsLeft(*loopsLeftLambda); e->getTotalLoops(*totalLoopsLambda); if ((*totalLoopsLambda)!=(*loopsLeftLambda)) //we are going 2nd, 3rd, etc. time through the song
|
||||
{
|
||||
*curPosInRowsLambda-=(songLength-songLoopedSectionLength); //a hack so progress bar does not jump?
|
||||
}
|
||||
if (e->getIsFadingOut()) //we are in fadeout??? why it works like that bruh
|
||||
{
|
||||
// LIVE WITH IT damn it
|
||||
*curPosInRowsLambda-=(songLength-songLoopedSectionLength); //a hack so progress bar does not jump?
|
||||
}
|
||||
}
|
||||
// this horrible indentation courtesy of `indent`
|
||||
*progressLambda=(float) ((*curPosInRowsLambda) + ((*totalLoopsLambda)- (*loopsLeftLambda)) * songLength + lengthOfOneFile * (*curFileLambda)) / (float) totalLength;});
|
||||
}
|
||||
|
||||
ImGui::Text(_("Row %d of %d"),curPosInRows+((totalLoops)-(loopsLeft))*songLength,lengthOfOneFile);
|
||||
|
||||
if (audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN) {
|
||||
ImGui::Text(_("Channel %d of %d"),curFile+1,totalFiles);
|
||||
}
|
||||
|
||||
ImGui::ProgressBar(curProgress,ImVec2(320.0f*dpiScale,0),fmt::sprintf("%.2f%%",curProgress*100.0f).c_str());
|
||||
|
||||
if (ImGui::Button(_("Abort"))) {
|
||||
if (e->haltAudioFile()) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
|
@ -7202,6 +7230,11 @@ bool FurnaceGUI::loop() {
|
|||
willCommit=false;
|
||||
}
|
||||
|
||||
// To check for instrument editor modification, we need an up-to-date `insEditMayBeDirty`
|
||||
// (based on incoming user input events), and we want any possible instrument modifications
|
||||
// to already have been made.
|
||||
checkRecordInstrumentUndoStep();
|
||||
|
||||
if (shallDetectScale) {
|
||||
if (--shallDetectScale<1) {
|
||||
if (settings.dpiScale<0.5f) {
|
||||
|
@ -7879,7 +7912,6 @@ void FurnaceGUI::syncState() {
|
|||
workingDirSample=e->getConfString("lastDirSample",workingDir);
|
||||
workingDirAudioExport=e->getConfString("lastDirAudioExport",workingDir);
|
||||
workingDirVGMExport=e->getConfString("lastDirVGMExport",workingDir);
|
||||
workingDirZSMExport=e->getConfString("lastDirZSMExport",workingDir);
|
||||
workingDirROMExport=e->getConfString("lastDirROMExport",workingDir);
|
||||
workingDirFont=e->getConfString("lastDirFont",workingDir);
|
||||
workingDirColors=e->getConfString("lastDirColors",workingDir);
|
||||
|
@ -8038,7 +8070,6 @@ void FurnaceGUI::commitState(DivConfig& conf) {
|
|||
conf.set("lastDirSample",workingDirSample);
|
||||
conf.set("lastDirAudioExport",workingDirAudioExport);
|
||||
conf.set("lastDirVGMExport",workingDirVGMExport);
|
||||
conf.set("lastDirZSMExport",workingDirZSMExport);
|
||||
conf.set("lastDirROMExport",workingDirROMExport);
|
||||
conf.set("lastDirFont",workingDirFont);
|
||||
conf.set("lastDirColors",workingDirColors);
|
||||
|
@ -8256,8 +8287,6 @@ FurnaceGUI::FurnaceGUI():
|
|||
displayError(false),
|
||||
displayExporting(false),
|
||||
vgmExportLoop(true),
|
||||
zsmExportLoop(true),
|
||||
zsmExportOptimize(true),
|
||||
vgmExportPatternHints(false),
|
||||
vgmExportDirectStream(false),
|
||||
displayInsTypeList(false),
|
||||
|
@ -8300,7 +8329,6 @@ FurnaceGUI::FurnaceGUI():
|
|||
vgmExportVersion(0x171),
|
||||
vgmExportTrailingTicks(-1),
|
||||
drawHalt(10),
|
||||
zsmExportTickRate(60),
|
||||
macroPointSize(16),
|
||||
waveEditStyle(0),
|
||||
displayInsTypeListMakeInsSample(-1),
|
||||
|
@ -8367,11 +8395,21 @@ FurnaceGUI::FurnaceGUI():
|
|||
bigFont(NULL),
|
||||
headFont(NULL),
|
||||
fontRange(NULL),
|
||||
songLength(0),
|
||||
songLoopedSectionLength(0),
|
||||
songFadeoutSectionLength(0),
|
||||
songHasSongEndCommand(false),
|
||||
lengthOfOneFile(0),
|
||||
totalLength(0),
|
||||
curProgress(0.0f),
|
||||
totalFiles(0),
|
||||
localeRequiresJapanese(false),
|
||||
localeRequiresChinese(false),
|
||||
localeRequiresChineseTrad(false),
|
||||
localeRequiresKorean(false),
|
||||
prevInsData(NULL),
|
||||
cachedCurInsPtr(NULL),
|
||||
insEditMayBeDirty(false),
|
||||
pendingLayoutImport(NULL),
|
||||
pendingLayoutImportLen(0),
|
||||
pendingLayoutImportStep(0),
|
||||
|
@ -8887,6 +8925,8 @@ FurnaceGUI::FurnaceGUI():
|
|||
|
||||
memset(romExportAvail,0,sizeof(bool)*DIV_ROM_MAX);
|
||||
|
||||
songOrdersLengths.clear();
|
||||
|
||||
strncpy(noteOffLabel,"OFF",32);
|
||||
strncpy(noteRelLabel,"===",32);
|
||||
strncpy(macroRelLabel,"REL",32);
|
||||
|
|
|
@ -137,6 +137,9 @@ enum FurnaceGUIRenderBackend {
|
|||
#define ngettext momo_ngettext
|
||||
#endif
|
||||
|
||||
#define GUI_EDIT_OCTAVE_MIN -5
|
||||
#define GUI_EDIT_OCTAVE_MAX 7
|
||||
|
||||
// TODO:
|
||||
// - add colors for FM envelope and waveform
|
||||
// - maybe add "alternate" color for FM modulators/carriers (a bit difficult)
|
||||
|
@ -598,7 +601,6 @@ enum FurnaceGUIFileDialogs {
|
|||
GUI_FILE_EXPORT_AUDIO_PER_SYS,
|
||||
GUI_FILE_EXPORT_AUDIO_PER_CHANNEL,
|
||||
GUI_FILE_EXPORT_VGM,
|
||||
GUI_FILE_EXPORT_ZSM,
|
||||
GUI_FILE_EXPORT_CMDSTREAM,
|
||||
GUI_FILE_EXPORT_TEXT,
|
||||
GUI_FILE_EXPORT_ROM,
|
||||
|
@ -651,7 +653,6 @@ enum FurnaceGUIExportTypes {
|
|||
GUI_EXPORT_AUDIO=0,
|
||||
GUI_EXPORT_VGM,
|
||||
GUI_EXPORT_ROM,
|
||||
GUI_EXPORT_ZSM,
|
||||
GUI_EXPORT_CMD_STREAM,
|
||||
GUI_EXPORT_TEXT,
|
||||
GUI_EXPORT_DMF
|
||||
|
@ -1594,7 +1595,7 @@ class FurnaceGUI {
|
|||
|
||||
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile, sysSearchQuery, newSongQuery, paletteQuery, sampleBankSearchQuery;
|
||||
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport;
|
||||
String workingDirVGMExport, workingDirZSMExport, workingDirROMExport;
|
||||
String workingDirVGMExport, workingDirROMExport;
|
||||
String workingDirFont, workingDirColors, workingDirKeybinds;
|
||||
String workingDirLayout, workingDirROM, workingDirTest;
|
||||
String workingDirConfig;
|
||||
|
@ -1613,7 +1614,7 @@ class FurnaceGUI {
|
|||
std::vector<String> availRenderDrivers;
|
||||
std::vector<String> availAudioDrivers;
|
||||
|
||||
bool quit, warnQuit, willCommit, edit, editClone, isPatUnique, modified, displayError, displayExporting, vgmExportLoop, zsmExportLoop, zsmExportOptimize, vgmExportPatternHints;
|
||||
bool quit, warnQuit, willCommit, edit, editClone, isPatUnique, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints;
|
||||
bool vgmExportDirectStream, displayInsTypeList, displayWaveSizeList;
|
||||
bool portrait, injectBackUp, mobileMenuOpen, warnColorPushed;
|
||||
bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
|
||||
|
@ -1634,7 +1635,6 @@ class FurnaceGUI {
|
|||
int vgmExportTrailingTicks;
|
||||
int cvHiScore;
|
||||
int drawHalt;
|
||||
int zsmExportTickRate;
|
||||
int macroPointSize;
|
||||
int waveEditStyle;
|
||||
int displayInsTypeListMakeInsSample;
|
||||
|
@ -1723,6 +1723,16 @@ class FurnaceGUI {
|
|||
char emptyLabel[32];
|
||||
char emptyLabel2[32];
|
||||
|
||||
std::vector<int> songOrdersLengths; // lengths of all orders (for drawing song export progress)
|
||||
int songLength; // length of all the song in rows
|
||||
int songLoopedSectionLength; // length of looped part of the song
|
||||
int songFadeoutSectionLength; // length of fading part of the song
|
||||
bool songHasSongEndCommand; // song has "Song end" command (FFxx)
|
||||
int lengthOfOneFile; // length of one rendering pass. song length times num of loops + fadeout
|
||||
int totalLength; // total length of render (lengthOfOneFile times num of files for per-channel export)
|
||||
float curProgress;
|
||||
int totalFiles;
|
||||
|
||||
struct Settings {
|
||||
bool settingsChanged;
|
||||
int mainFontSize, patFontSize, headFontSize, iconSize;
|
||||
|
@ -2263,6 +2273,9 @@ class FurnaceGUI {
|
|||
std::vector<ImWchar> localeExtraRanges;
|
||||
|
||||
DivInstrument* prevInsData;
|
||||
DivInstrument cachedCurIns;
|
||||
DivInstrument* cachedCurInsPtr;
|
||||
bool insEditMayBeDirty;
|
||||
|
||||
unsigned char* pendingLayoutImport;
|
||||
size_t pendingLayoutImportLen;
|
||||
|
@ -2704,7 +2717,6 @@ class FurnaceGUI {
|
|||
void drawExportAudio(bool onWindow=false);
|
||||
void drawExportVGM(bool onWindow=false);
|
||||
void drawExportROM(bool onWindow=false);
|
||||
void drawExportZSM(bool onWindow=false);
|
||||
void drawExportText(bool onWindow=false);
|
||||
void drawExportCommand(bool onWindow=false);
|
||||
void drawExportDMF(bool onWindow=false);
|
||||
|
@ -2824,7 +2836,7 @@ class FurnaceGUI {
|
|||
void drawMemory();
|
||||
void drawCompatFlags();
|
||||
void drawPiano();
|
||||
void drawNotes();
|
||||
void drawNotes(bool asChild=false);
|
||||
void drawChannels();
|
||||
void drawPatManager();
|
||||
void drawSysManager();
|
||||
|
@ -2914,13 +2926,14 @@ class FurnaceGUI {
|
|||
void doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd);
|
||||
void doCollapseSong(int divider);
|
||||
void doExpandSong(int multiplier);
|
||||
void doAbsorbInstrument();
|
||||
void doUndo();
|
||||
void doRedo();
|
||||
void doFind();
|
||||
void doReplace();
|
||||
void doDrag();
|
||||
void editOptions(bool topMenu);
|
||||
DivSystem systemPicker();
|
||||
DivSystem systemPicker(bool fullWidth);
|
||||
void noteInput(int num, int key, int vol=-1);
|
||||
void valueInput(int num, bool direct=false, int target=-1);
|
||||
void orderInput(int num);
|
||||
|
@ -2930,6 +2943,10 @@ class FurnaceGUI {
|
|||
void doUndoSample();
|
||||
void doRedoSample();
|
||||
|
||||
void checkRecordInstrumentUndoStep();
|
||||
void doUndoInstrument();
|
||||
void doRedoInstrument();
|
||||
|
||||
void play(int row=0);
|
||||
void setOrder(unsigned char order, bool forced=false);
|
||||
void stop();
|
||||
|
|
|
@ -473,8 +473,8 @@ const FurnaceGUIColors fxColors[256]={
|
|||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_VOLUME,
|
||||
GUI_COLOR_PATTERN_EFFECT_VOLUME,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
|
@ -687,7 +687,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
|
|||
D("PAT_LATCH", _N("Set note input latch"), 0),
|
||||
D("PAT_SCROLL_MODE", _N("Change mobile scroll mode"), 0),
|
||||
D("PAT_CLEAR_LATCH", _N("Clear note input latch"), 0),
|
||||
D("PAT_ABSORB_INSTRUMENT", _N("Set current instrument to channel's current instrument column"), 0),
|
||||
D("PAT_ABSORB_INSTRUMENT", _N("Absorb instrument/octave from status at cursor"), 0),
|
||||
D("PAT_MAX", "", NOT_AN_ACTION),
|
||||
|
||||
D("INS_LIST_MIN", _N("---Instrument list"), NOT_AN_ACTION),
|
||||
|
|
|
@ -5250,6 +5250,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH));
|
||||
}
|
||||
if (ImGui::Begin("Instrument Editor",&insEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking),_("Instrument Editor"))) {
|
||||
DivInstrument* ins=NULL;
|
||||
if (curIns==-2) {
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY()+(ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().ItemSpacing.y)*0.5f);
|
||||
CENTER_TEXT(_("waiting..."));
|
||||
|
@ -5277,6 +5278,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
curIns=i;
|
||||
wavePreviewInit=true;
|
||||
updateFMPreview=true;
|
||||
ins = e->song.ins[curIns];
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
|
@ -5299,7 +5301,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::EndTable();
|
||||
}
|
||||
} else {
|
||||
DivInstrument* ins=e->song.ins[curIns];
|
||||
ins=e->song.ins[curIns];
|
||||
if (updateFMPreview) {
|
||||
renderFMPreview(ins);
|
||||
updateFMPreview=false;
|
||||
|
@ -7196,6 +7198,8 @@ void FurnaceGUI::drawInsEdit() {
|
|||
macroList.push_back(FurnaceGUIMacroDesc(_("Noise"),&ins->std.dutyMacro,0,8,160,uiColors[GUI_COLOR_MACRO_NOISE]));
|
||||
}
|
||||
macroList.push_back(FurnaceGUIMacroDesc(_("Waveform"),&ins->std.waveMacro,0,waveCount,160,uiColors[GUI_COLOR_MACRO_WAVE],false,NULL,NULL,false,NULL));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(_("Panning (left)"),&ins->std.panLMacro,0,15,46,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(_("Panning (right)"),&ins->std.panRMacro,0,15,46,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(_("Pitch"),&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(_("Phase Reset"),&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
break;
|
||||
|
@ -7755,6 +7759,63 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_EDIT;
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void FurnaceGUI::checkRecordInstrumentUndoStep() {
|
||||
if (insEditOpen && curIns>=0 && curIns<(int)e->song.ins.size()) {
|
||||
DivInstrument* ins=e->song.ins[curIns];
|
||||
|
||||
// invalidate cachedCurIns/any possible changes if the cachedCurIns was referencing a different
|
||||
// instrument altgoether
|
||||
bool insChanged=ins!=cachedCurInsPtr;
|
||||
if (insChanged) {
|
||||
insEditMayBeDirty=false;
|
||||
cachedCurInsPtr=ins;
|
||||
cachedCurIns=*ins;
|
||||
}
|
||||
|
||||
cachedCurInsPtr=ins;
|
||||
|
||||
// check against the last cached to see if diff -- note that modifications to instruments
|
||||
// happen outside drawInsEdit (e.g. cursor inputs are processed and can directly modify
|
||||
// macro data). but don't check until we think the user input is complete.
|
||||
bool delayDiff=ImGui::IsMouseDown(ImGuiMouseButton_Left) || ImGui::IsMouseDown(ImGuiMouseButton_Right) || ImGui::GetIO().WantCaptureKeyboard;
|
||||
if (!delayDiff && insEditMayBeDirty) {
|
||||
bool hasChange=ins->recordUndoStepIfChanged(e->processTime, &cachedCurIns);
|
||||
if (hasChange) {
|
||||
cachedCurIns=*ins;
|
||||
}
|
||||
insEditMayBeDirty=false;
|
||||
}
|
||||
} else {
|
||||
cachedCurInsPtr=NULL;
|
||||
insEditMayBeDirty=false;
|
||||
}
|
||||
}
|
||||
|
||||
void FurnaceGUI::doUndoInstrument() {
|
||||
if (!insEditOpen) return;
|
||||
if (curIns<0 || curIns>=(int)e->song.ins.size()) return;
|
||||
DivInstrument* ins=e->song.ins[curIns];
|
||||
// is locking the engine necessary? copied from doUndoSample
|
||||
e->lockEngine([this,ins]() {
|
||||
ins->undo();
|
||||
cachedCurInsPtr=ins;
|
||||
cachedCurIns=*ins;
|
||||
});
|
||||
}
|
||||
|
||||
void FurnaceGUI::doRedoInstrument() {
|
||||
if (!insEditOpen) return;
|
||||
if (curIns<0 || curIns>=(int)e->song.ins.size()) return;
|
||||
DivInstrument* ins=e->song.ins[curIns];
|
||||
// is locking the engine necessary? copied from doRedoSample
|
||||
e->lockEngine([this,ins]() {
|
||||
ins->redo();
|
||||
cachedCurInsPtr=ins;
|
||||
cachedCurIns=*ins;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1500,6 +1500,7 @@ void FurnaceGUI::drawPattern() {
|
|||
i.cmd==DIV_CMD_HINT_PORTA ||
|
||||
i.cmd==DIV_CMD_HINT_LEGATO ||
|
||||
i.cmd==DIV_CMD_HINT_VOL_SLIDE ||
|
||||
i.cmd==DIV_CMD_HINT_VOL_SLIDE_TARGET ||
|
||||
i.cmd==DIV_CMD_HINT_ARPEGGIO ||
|
||||
i.cmd==DIV_CMD_HINT_PITCH ||
|
||||
i.cmd==DIV_CMD_HINT_VIBRATO ||
|
||||
|
|
|
@ -1216,62 +1216,62 @@ void FurnaceGUI::initSystemPresets() {
|
|||
}
|
||||
);
|
||||
SUB_ENTRY(
|
||||
"Sega TeraDrive", {
|
||||
CH(DIV_SYSTEM_YM2612, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
"Sega TeraDrive", {
|
||||
CH(DIV_SYSTEM_YM2612, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
SUB_SUB_ENTRY(
|
||||
"Sega TeraDrive (extended channel 3)", {
|
||||
CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
"Sega TeraDrive (extended channel 3)", {
|
||||
CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
SUB_SUB_ENTRY(
|
||||
"Sega TeraDrive (CSM)", {
|
||||
CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
"Sega TeraDrive (CSM)", {
|
||||
CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
SUB_SUB_ENTRY(
|
||||
"Sega TeraDrive (DualPCM)", {
|
||||
CH(DIV_SYSTEM_YM2612_DUALPCM, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
"Sega TeraDrive (DualPCM)", {
|
||||
CH(DIV_SYSTEM_YM2612_DUALPCM, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
SUB_SUB_ENTRY(
|
||||
"Sega TeraDrive (DualPCM, extended channel 3)", {
|
||||
CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
"Sega TeraDrive (DualPCM, extended channel 3)", {
|
||||
CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
ENTRY(
|
||||
"Sharp X1", {
|
||||
CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=3")
|
||||
}
|
||||
}
|
||||
);
|
||||
SUB_ENTRY(
|
||||
"Sharp X1 + FM Addon", {
|
||||
CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=3"),
|
||||
CH(DIV_SYSTEM_YM2151, 1.0f, 0, "clockSel=2")
|
||||
}
|
||||
}
|
||||
);
|
||||
ENTRY(
|
||||
"Sharp X68000", {
|
||||
CH(DIV_SYSTEM_YM2151, 1.0f, 0, "clockSel=2"),
|
||||
CH(DIV_SYSTEM_MSM6258, 1.0f, 0, "clockSel=2")
|
||||
}
|
||||
}
|
||||
);
|
||||
ENTRY(
|
||||
"FM-7", {
|
||||
CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=12"),
|
||||
CH(DIV_SYSTEM_YM2203, 1.0f, 0, "clockSel=5")
|
||||
}
|
||||
}
|
||||
);
|
||||
SUB_ENTRY(
|
||||
"FM-7 (extended channel 3)", {
|
||||
|
|
|
@ -253,16 +253,27 @@ void FurnaceGUI::drawSampleEdit() {
|
|||
SAMPLE_WARN(warnLength,"QSound: maximum sample length is 65535");
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_NES:
|
||||
case DIV_SYSTEM_NES: {
|
||||
if (sample->loop) {
|
||||
if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) {
|
||||
SAMPLE_WARN(warnLoopPos,_("NES: loop point ignored on DPCM (may only loop entire sample)"));
|
||||
if (sample->loopStart&511) {
|
||||
int tryWith=(sample->loopStart)&(~511);
|
||||
if (tryWith>(int)sample->samples) tryWith-=512;
|
||||
String alignHint=fmt::sprintf(_("NES: loop start must be a multiple of 512 (try with %d)"),tryWith);
|
||||
SAMPLE_WARN(warnLoopStart,alignHint);
|
||||
}
|
||||
if ((sample->loopEnd)&127) {
|
||||
// +1 bc of how sample length is treated: https://www.nesdev.org/wiki/APU_DMC
|
||||
int tryWith=(sample->loopEnd + 1)&(~127);
|
||||
if (tryWith>(int)sample->samples) tryWith-=128;
|
||||
String alignHint=fmt::sprintf(_("NES: loop end must be a multiple of 128 (try with %d)"),tryWith);
|
||||
SAMPLE_WARN(warnLoopEnd,alignHint);
|
||||
}
|
||||
}
|
||||
if (sample->samples>32648) {
|
||||
SAMPLE_WARN(warnLength,_("NES: maximum DPCM sample length is 32648"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_X1_010:
|
||||
if (sample->loop) {
|
||||
SAMPLE_WARN(warnLoop,_("X1-010: samples can't loop"));
|
||||
|
|
|
@ -1089,15 +1089,19 @@ void FurnaceGUI::drawSettings() {
|
|||
ImGui::PushID(i);
|
||||
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(_("Invert")).x-ImGui::GetFrameHeightWithSpacing()*2.0-ImGui::GetStyle().ItemSpacing.x*2.0);
|
||||
if (ImGui::BeginCombo("##System",getSystemName(sysID))) {
|
||||
for (int j=0; availableSystems[j]; j++) {
|
||||
if (ImGui::Selectable(getSystemName((DivSystem)availableSystems[j]),sysID==availableSystems[j])) {
|
||||
sysID=(DivSystem)availableSystems[j];
|
||||
settings.initialSys.set(fmt::sprintf("id%d",i),(int)e->systemToFileFur(sysID));
|
||||
settings.initialSys.set(fmt::sprintf("flags%d",i),"");
|
||||
settingsChanged=true;
|
||||
}
|
||||
if (ImGui::BeginCombo("##System",getSystemName(sysID),ImGuiComboFlags_HeightLargest)) {
|
||||
|
||||
sysID=systemPicker(true);
|
||||
|
||||
if (sysID!=DIV_SYSTEM_NULL)
|
||||
{
|
||||
settings.initialSys.set(fmt::sprintf("id%d",i),(int)e->systemToFileFur(sysID));
|
||||
settings.initialSys.set(fmt::sprintf("flags%d",i),"");
|
||||
settingsChanged=true;
|
||||
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
|
|
|
@ -22,18 +22,23 @@
|
|||
|
||||
// NOTE: please don't ask me to enable text wrap.
|
||||
// Dear ImGui doesn't have that feature. D:
|
||||
void FurnaceGUI::drawNotes() {
|
||||
void FurnaceGUI::drawNotes(bool asChild) {
|
||||
if (nextWindow==GUI_WINDOW_NOTES) {
|
||||
notesOpen=true;
|
||||
ImGui::SetNextWindowFocus();
|
||||
nextWindow=GUI_WINDOW_NOTHING;
|
||||
}
|
||||
if (!notesOpen) return;
|
||||
if (ImGui::Begin("Song Comments",¬esOpen,globalWinFlags,_("Song Comments"))) {
|
||||
if (!notesOpen && !asChild) return;
|
||||
bool began=asChild?ImGui::BeginChild("Song Info##Song Information"):ImGui::Begin("Song Comments",¬esOpen,globalWinFlags,_("Song Comments"));
|
||||
if (began) {
|
||||
if (ImGui::InputTextMultiline("##SongNotes",&e->song.notes,ImGui::GetContentRegionAvail(),ImGuiInputTextFlags_UndoRedo)) {
|
||||
MARK_MODIFIED;
|
||||
}
|
||||
}
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES;
|
||||
ImGui::End();
|
||||
if (!asChild && ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES;
|
||||
if (asChild) {
|
||||
ImGui::EndChild();
|
||||
} else {
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1969,6 +1969,8 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
|
|||
echoFilter[6]=flags.getInt("echoFilter6",0);
|
||||
echoFilter[7]=flags.getInt("echoFilter7",0);
|
||||
|
||||
bool interpolationOff=flags.getBool("interpolationOff",false);
|
||||
|
||||
ImGui::Text(_("Volume scale:"));
|
||||
if (CWSliderInt(_("Left##VolScaleL"),&vsL,0,127)) {
|
||||
if (vsL<0) vsL=0;
|
||||
|
@ -2084,6 +2086,10 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
|
|||
ImGui::Text(_("sum: %d"),filterSum);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (ImGui::Checkbox(_("Disable Gaussian interpolation"),&interpolationOff)) {
|
||||
altered=true;
|
||||
}
|
||||
|
||||
if (altered) {
|
||||
e->lockSave([&]() {
|
||||
flags.set("volScaleL",127-vsL);
|
||||
|
@ -2102,6 +2108,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
|
|||
flags.set("echoFilter6",echoFilter[6]);
|
||||
flags.set("echoFilter7",echoFilter[7]);
|
||||
flags.set("echoMask",echoMask);
|
||||
flags.set("interpolationOff",interpolationOff);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ void FurnaceGUI::drawSysManager() {
|
|||
ImGui::SameLine();
|
||||
ImGui::Button(_("Change##SysChange"));
|
||||
if (ImGui::BeginPopupContextItem("SysPickerC",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||
DivSystem picked=systemPicker();
|
||||
DivSystem picked=systemPicker(false);
|
||||
if (picked!=DIV_SYSTEM_NULL) {
|
||||
if (e->changeSystem(i,picked,preserveChanPos)) {
|
||||
MARK_MODIFIED;
|
||||
|
@ -138,7 +138,7 @@ void FurnaceGUI::drawSysManager() {
|
|||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_FA_PLUS "##SysAdd");
|
||||
if (ImGui::BeginPopupContextItem("SysPickerA",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||
DivSystem picked=systemPicker();
|
||||
DivSystem picked=systemPicker(false);
|
||||
if (picked!=DIV_SYSTEM_NULL) {
|
||||
if (!e->addSystem(picked)) {
|
||||
showError(fmt::sprintf(_("cannot add chip! (%s)"),e->getLastError()));
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#include "guiConst.h"
|
||||
#include <imgui.h>
|
||||
|
||||
DivSystem FurnaceGUI::systemPicker() {
|
||||
DivSystem FurnaceGUI::systemPicker(bool fullWidth) {
|
||||
DivSystem ret=DIV_SYSTEM_NULL;
|
||||
DivSystem hoveredSys=DIV_SYSTEM_NULL;
|
||||
bool reissueSearch=false;
|
||||
|
@ -61,7 +61,7 @@ DivSystem FurnaceGUI::systemPicker() {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginTable("SysList",1,ImGuiTableFlags_ScrollY,ImVec2(500.0f*dpiScale,200.0*dpiScale))) {
|
||||
if (ImGui::BeginTable("SysList",1,ImGuiTableFlags_ScrollY,ImVec2(fullWidth ? ImGui::GetContentRegionAvail().x : 500.0f*dpiScale,200.0f*dpiScale))) {
|
||||
if (sysSearchQuery.empty()) {
|
||||
// display chip list
|
||||
for (int j=0; curSysSection[j]; j++) {
|
||||
|
|
|
@ -392,7 +392,7 @@ void FurnaceGUI::drawUserPresets() {
|
|||
tempID=fmt::sprintf("%s##USystem",getSystemName(chip.sys));
|
||||
ImGui::Button(tempID.c_str(),ImVec2(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(_("Invert")).x-ImGui::GetFrameHeightWithSpacing()*2.0-ImGui::GetStyle().ItemSpacing.x*2.0,0));
|
||||
if (ImGui::BeginPopupContextItem("SysPickerCU",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||
DivSystem picked=systemPicker();
|
||||
DivSystem picked=systemPicker(false);
|
||||
if (picked!=DIV_SYSTEM_NULL) {
|
||||
chip.sys=picked;
|
||||
mustBake=true;
|
||||
|
@ -456,7 +456,7 @@ void FurnaceGUI::drawUserPresets() {
|
|||
|
||||
ImGui::Button(ICON_FA_PLUS "##SysAddU");
|
||||
if (ImGui::BeginPopupContextItem("SysPickerU",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||
DivSystem picked=systemPicker();
|
||||
DivSystem picked=systemPicker(false);
|
||||
if (picked!=DIV_SYSTEM_NULL) {
|
||||
preset->orig.push_back(FurnaceGUISysDefChip(picked,1.0f,0.0f,""));
|
||||
mustBake=true;
|
||||
|
|
38
src/main.cpp
38
src/main.cpp
|
@ -85,7 +85,6 @@ FurnaceCLI cli;
|
|||
|
||||
String outName;
|
||||
String vgmOutName;
|
||||
String zsmOutName;
|
||||
String cmdOutName;
|
||||
int benchMode=0;
|
||||
int subsong=-1;
|
||||
|
@ -430,12 +429,6 @@ TAParamResult pVGMOut(String val) {
|
|||
return TA_PARAM_SUCCESS;
|
||||
}
|
||||
|
||||
TAParamResult pZSMOut(String val) {
|
||||
zsmOutName=val;
|
||||
e.setAudio(DIV_AUDIO_DUMMY);
|
||||
return TA_PARAM_SUCCESS;
|
||||
}
|
||||
|
||||
TAParamResult pCmdOut(String val) {
|
||||
cmdOutName=val;
|
||||
e.setAudio(DIV_AUDIO_DUMMY);
|
||||
|
@ -458,7 +451,6 @@ void initParams() {
|
|||
params.push_back(TAParam("o","output",true,pOutput,"<filename>","output audio to file"));
|
||||
params.push_back(TAParam("O","vgmout",true,pVGMOut,"<filename>","output .vgm data"));
|
||||
params.push_back(TAParam("D","direct",false,pDirect,"","set VGM export direct stream mode"));
|
||||
params.push_back(TAParam("Z","zsmout",true,pZSMOut,"<filename>","output .zsm data for Commander X16 Zsound"));
|
||||
params.push_back(TAParam("C","cmdout",true,pCmdOut,"<filename>","output command stream"));
|
||||
params.push_back(TAParam("L","loglevel",true,pLogLevel,"debug|info|warning|error","set the log level (info by default)"));
|
||||
params.push_back(TAParam("v","view",true,pView,"pattern|commands|nothing","set visualization (nothing by default)"));
|
||||
|
@ -561,7 +553,6 @@ int main(int argc, char** argv) {
|
|||
#endif
|
||||
outName="";
|
||||
vgmOutName="";
|
||||
zsmOutName="";
|
||||
cmdOutName="";
|
||||
|
||||
// load config for locale
|
||||
|
@ -730,14 +721,14 @@ int main(int argc, char** argv) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (fileName.empty() && (benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="")) {
|
||||
if (fileName.empty() && (benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="")) {
|
||||
logE("provide a file!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_GUI
|
||||
if (e.preInit(consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="")) {
|
||||
if (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="") {
|
||||
if (e.preInit(consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="")) {
|
||||
if (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="") {
|
||||
logW("engine wants safe mode, but Furnace GUI is not going to start.");
|
||||
} else {
|
||||
safeMode=true;
|
||||
|
@ -749,7 +740,7 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
#endif
|
||||
|
||||
if (safeMode && (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="")) {
|
||||
if (safeMode && (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="")) {
|
||||
logE("you can't use safe mode and console/export mode together.");
|
||||
return 1;
|
||||
}
|
||||
|
@ -758,7 +749,7 @@ int main(int argc, char** argv) {
|
|||
e.setAudio(DIV_AUDIO_DUMMY);
|
||||
}
|
||||
|
||||
if (!fileName.empty() && ((!e.getConfBool("tutIntroPlayed",TUT_INTRO_PLAYED)) || e.getConfInt("alwaysPlayIntro",0)!=3 || consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="")) {
|
||||
if (!fileName.empty() && ((!e.getConfBool("tutIntroPlayed",TUT_INTRO_PLAYED)) || e.getConfInt("alwaysPlayIntro",0)!=3 || consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="")) {
|
||||
logI("loading module...");
|
||||
FILE* f=ps_fopen(fileName.c_str(),"rb");
|
||||
if (f==NULL) {
|
||||
|
@ -850,7 +841,7 @@ int main(int argc, char** argv) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="") {
|
||||
if (outName!="" || vgmOutName!="" || cmdOutName!="") {
|
||||
if (cmdOutName!="") {
|
||||
SafeWriter* w=e.saveCommand();
|
||||
if (w!=NULL) {
|
||||
|
@ -883,23 +874,6 @@ int main(int argc, char** argv) {
|
|||
reportError(_("could not write VGM!"));
|
||||
}
|
||||
}
|
||||
if (zsmOutName!="") {
|
||||
// TODO: changing parameters
|
||||
SafeWriter* w=e.saveZSM(60,true,true);
|
||||
if (w!=NULL) {
|
||||
FILE* f=ps_fopen(zsmOutName.c_str(),"wb");
|
||||
if (f!=NULL) {
|
||||
fwrite(w->getFinalBuf(),1,w->size(),f);
|
||||
fclose(f);
|
||||
} else {
|
||||
reportError(fmt::sprintf(_("could not open file! (%s)"),e.getLastError()));
|
||||
}
|
||||
w->finish();
|
||||
delete w;
|
||||
} else {
|
||||
reportError(fmt::sprintf(_("could not write ZSM! (%s)"),e.getLastError()));
|
||||
}
|
||||
}
|
||||
if (outName!="") {
|
||||
e.setConsoleMode(true);
|
||||
e.saveAudio(outName.c_str(),exportOptions);
|
||||
|
|
Loading…
Reference in a new issue