Merge branch 'master' into centerrate
This commit is contained in:
commit
ef157880d2
|
@ -22,8 +22,10 @@ jobs:
|
|||
- { name: 'Windows MSVC x86_64', os: windows-latest, compiler: msvc, arch: x86_64 }
|
||||
- { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 }
|
||||
- { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 }
|
||||
- { name: 'macOS', os: macos-latest }
|
||||
- { name: 'Ubuntu', os: ubuntu-18.04 }
|
||||
- { name: 'macOS x86_64', os: macos-latest, arch: x86_64 }
|
||||
- { name: 'macOS ARM', os: macos-latest, arch: arm64 }
|
||||
- { name: 'Linux x86_64', os: ubuntu-18.04, arch: x86_64 }
|
||||
- { name: 'Linux ARM', os: ubuntu-18.04, arch: armhf }
|
||||
fail-fast: false
|
||||
|
||||
name: ${{ matrix.config.name }}
|
||||
|
@ -76,10 +78,10 @@ jobs:
|
|||
package_name="${package_name}-${{ matrix.config.arch }}"
|
||||
package_ext="" # Directory, uploading will automatically zip it
|
||||
elif [ '${{ runner.os }}' == 'macOS' ]; then
|
||||
package_name="${package_name}-macOS"
|
||||
package_name="${package_name}-macOS-${{ matrix.config.arch }}"
|
||||
package_ext=".dmg"
|
||||
else
|
||||
package_name="${package_name}-Linux"
|
||||
package_name="${package_name}-Linux-${{ matrix.config.arch }}"
|
||||
package_ext=".AppImage"
|
||||
fi
|
||||
|
||||
|
@ -116,8 +118,8 @@ jobs:
|
|||
mingw-w64 \
|
||||
mingw-w64-tools
|
||||
|
||||
- name: Install Dependencies [Ubuntu]
|
||||
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
|
||||
- name: Install Dependencies [Linux x86_64]
|
||||
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install \
|
||||
|
@ -131,8 +133,31 @@ jobs:
|
|||
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod +x appimagetool-x86_64.AppImage
|
||||
|
||||
- name: Install Dependencies [Linux armhf]
|
||||
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'armhf' }}
|
||||
run: |
|
||||
sudo sed -ri "s/^deb /deb [arch=amd64] /" /etc/apt/sources.list
|
||||
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main universe" | sudo tee -a /etc/apt/sources.list
|
||||
sudo dpkg --add-architecture armhf
|
||||
sudo apt update
|
||||
sudo apt install \
|
||||
crossbuild-essential-armhf \
|
||||
appstream
|
||||
sudo apt install \
|
||||
libsdl2-dev:armhf \
|
||||
libfmt-dev:armhf \
|
||||
librtmidi-dev:armhf \
|
||||
libsndfile1-dev:armhf \
|
||||
zlib1g-dev:armhf \
|
||||
libjack-jackd2-dev:armhf
|
||||
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-armhf"
|
||||
chmod +x appimagetool-x86_64.AppImage
|
||||
ls /usr/arm-linux-gnueabihf/lib
|
||||
|
||||
- name: Configure (System Libraries)
|
||||
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
|
||||
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
|
||||
run: |
|
||||
export USE_WAE=ON
|
||||
export CMAKE_EXTRA_ARGS=()
|
||||
|
@ -163,7 +188,7 @@ jobs:
|
|||
"${CMAKE_EXTRA_ARGS[@]}"
|
||||
|
||||
- name: Build (System Libraries)
|
||||
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
|
||||
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
|
||||
run: |
|
||||
cmake \
|
||||
--build ${PWD}/build \
|
||||
|
@ -171,14 +196,14 @@ jobs:
|
|||
--parallel ${{ steps.build-cores.outputs.amount }}
|
||||
|
||||
- name: Install (System Libraries)
|
||||
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
|
||||
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
|
||||
run: |
|
||||
cmake \
|
||||
--install ${PWD}/build \
|
||||
--config ${{ env.BUILD_TYPE }}
|
||||
|
||||
- name: Cleanup (System Libraries)
|
||||
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
|
||||
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
|
||||
run: |
|
||||
rm -rf build/ target/
|
||||
|
||||
|
@ -201,7 +226,13 @@ jobs:
|
|||
elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then
|
||||
CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake')
|
||||
elif [ '${{ runner.os }}' == 'macOS' ]; then
|
||||
CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"')
|
||||
if [ '${{ matrix.config.arch }}' == 'arm64' ]; then
|
||||
CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="11.0"' '-DCMAKE_OSX_ARCHITECTURES=arm64')
|
||||
else
|
||||
CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"')
|
||||
fi
|
||||
elif [ '${{ runner.os }}' == 'Linux' ] && [ '${{ matrix.config.arch }}' == 'armhf' ]; then
|
||||
CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-Linux-armhf.cmake')
|
||||
fi
|
||||
|
||||
cmake \
|
||||
|
@ -255,7 +286,7 @@ jobs:
|
|||
mv Furnace-*-Darwin.dmg ../${{ steps.package-identify.outputs.filename }}
|
||||
popd
|
||||
|
||||
- name: Package [Ubuntu]
|
||||
- name: Package [Linux]
|
||||
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
|
||||
run: |
|
||||
#if [ '${{ env.BUILD_TYPE }}' == 'Release' ]; then
|
||||
|
@ -273,7 +304,11 @@ jobs:
|
|||
cp -v ../../res/AppRun ./
|
||||
popd
|
||||
|
||||
../appimagetool-x86_64.AppImage furnace.AppDir
|
||||
if [ '${{ matrix.config.arch }}' == 'armhf' ]; then
|
||||
../appimagetool-x86_64.AppImage --runtime-file=../runtime-armhf furnace.AppDir
|
||||
else
|
||||
../appimagetool-x86_64.AppImage furnace.AppDir
|
||||
fi
|
||||
mv Furnace-*.AppImage ../${{ steps.package-identify.outputs.filename }}
|
||||
popd
|
||||
|
||||
|
|
|
@ -66,6 +66,8 @@ option(SYSTEM_RTMIDI "Use a system-installed version of RtMidi instead of the ve
|
|||
option(SYSTEM_ZLIB "Use a system-installed version of zlib instead of the vendored one" OFF)
|
||||
option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendored one" ${SYSTEM_SDL2_DEFAULT})
|
||||
option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF)
|
||||
option(WITH_DEMOS "Install demo songs" ON)
|
||||
option(WITH_INSTRUMENTS "Install instruments" ON)
|
||||
|
||||
set(DEPENDENCIES_INCLUDE_DIRS "")
|
||||
|
||||
|
@ -709,8 +711,12 @@ if (NOT ANDROID OR TERMUX)
|
|||
install(FILES res/furnace.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
|
||||
install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace)
|
||||
install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
|
||||
install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
|
||||
if (WITH_DEMOS)
|
||||
install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
|
||||
endif()
|
||||
if (WITH_INSTRUMENTS)
|
||||
install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
|
||||
endif()
|
||||
foreach(num 16 32 64 128 256 512)
|
||||
set(res ${num}x${num})
|
||||
install(FILES res/icon.iconset/icon_${res}.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}/apps)
|
||||
|
|
|
@ -203,6 +203,8 @@ Available options:
|
|||
| `SYSTEM_ZLIB` | `OFF` | Use a system-installed version of zlib instead of the vendored one |
|
||||
| `SYSTEM_SDL2` | `OFF` | Use a system-installed version of SDL2 instead of the vendored one |
|
||||
| `WARNINGS_ARE_ERRORS` | `OFF` (but consider enabling this & reporting any errors that arise from it!) | Whether warnings in furnace's C++ code should be treated as errors |
|
||||
| `WITH_DEMOS` | `ON` | Install demo songs on `make install` |
|
||||
| `WITH_INSTRUMENTS` | `ON` | Install demo instruments on `make install` |
|
||||
|
||||
## console usage
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -11,28 +11,16 @@ if it is a 2-byte macro, read a dummy byte.
|
|||
|
||||
then read data.
|
||||
|
||||
## pattern data
|
||||
## binary command stream
|
||||
|
||||
read sequentially.
|
||||
read channel, command and values.
|
||||
|
||||
first byte determines what to read next:
|
||||
if channel is 80 or higher, then it is a special command:
|
||||
|
||||
```
|
||||
NVI..EEE
|
||||
|
||||
N: note
|
||||
V: volume
|
||||
I: instrument
|
||||
|
||||
EEE: effect count (0-7)
|
||||
fb xx xx xx xx: set tick rate
|
||||
fc xx xx: wait xxxx ticks
|
||||
fd xx: wait xx ticks
|
||||
fe: wait one tick
|
||||
ff: stop
|
||||
```
|
||||
|
||||
if you read 0, end of pattern.
|
||||
otherwise read in following order:
|
||||
|
||||
1. note
|
||||
2. volume
|
||||
3. instrument
|
||||
4. effect and effect value
|
||||
|
||||
then read number of rows until next value, minus 1.
|
||||
|
|
|
@ -32,6 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
|
|||
|
||||
the format versions are:
|
||||
|
||||
- 105: Furance dev105
|
||||
- 104: Furnace dev104
|
||||
- 103: Furnace dev103
|
||||
- 102: Furnace 0.6pre1 (dev102)
|
||||
- 101: Furnace 0.6pre1 (dev101)
|
||||
|
@ -810,6 +812,40 @@ size | description
|
|||
1 | vib depth
|
||||
1 | am depth
|
||||
23 | reserved
|
||||
--- | **Sound Unit data** (>=104)
|
||||
1 | use sample
|
||||
1 | switch roles of phase reset timer and frequency
|
||||
--- | **Game Boy envelope sequence** (>=105)
|
||||
1 | length
|
||||
??? | hardware sequence data
|
||||
| size is length*3:
|
||||
| 1 byte: command
|
||||
| - 0: set envelope
|
||||
| - 1: set sweep
|
||||
| - 2: wait
|
||||
| - 3: wait for release
|
||||
| - 4: loop
|
||||
| - 5: loop until release
|
||||
| 2 bytes: data
|
||||
| - for set envelope:
|
||||
| - 1 byte: parameter
|
||||
| - bit 4-7: volume
|
||||
| - bit 3: direction
|
||||
| - bit 0-2: length
|
||||
| - 1 byte: sound length
|
||||
| - for set sweep:
|
||||
| - 1 byte: parameter
|
||||
| - bit 4-6: length
|
||||
| - bit 3: direction
|
||||
| - bit 0-2: shift
|
||||
| - 1 byte: nothing
|
||||
| - for wait:
|
||||
| - 1 byte: length (in ticks)
|
||||
| - 1 byte: nothing
|
||||
| - for wait for release:
|
||||
| - 2 bytes: nothing
|
||||
| - for loop/loop until release:
|
||||
| - 2 bytes: position
|
||||
```
|
||||
|
||||
# wavetable
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR arm)
|
||||
|
||||
set(TARGET_PREFIX arm-linux-gnueabihf)
|
||||
|
||||
set(CMAKE_C_COMPILER ${TARGET_PREFIX}-gcc)
|
||||
set(CMAKE_CXX_COMPILER ${TARGET_PREFIX}-g++)
|
||||
set(PKG_CONFIG_EXECUTABLE ${TARGET_PREFIX}-pkg-config)
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH /usr/${TARGET_PREFIX} /usr/lib/${TARGET_PREFIX})
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
|
|
@ -55,6 +55,18 @@ enum DivDispatchCmds {
|
|||
DIV_CMD_PRE_PORTA, // (inPorta, isPortaOrSlide)
|
||||
DIV_CMD_PRE_NOTE, // used in C64 (note)
|
||||
|
||||
// these will be used in ROM export.
|
||||
// do NOT implement!
|
||||
DIV_CMD_HINT_VIBRATO, // (speed, depth)
|
||||
DIV_CMD_HINT_VIBRATO_RANGE, // (range)
|
||||
DIV_CMD_HINT_VIBRATO_SHAPE, // (shape)
|
||||
DIV_CMD_HINT_PITCH, // (pitch)
|
||||
DIV_CMD_HINT_ARPEGGIO, // (note1, note2)
|
||||
DIV_CMD_HINT_VOLUME, // (vol)
|
||||
DIV_CMD_HINT_VOL_SLIDE, // (amount, oneTick)
|
||||
DIV_CMD_HINT_PORTA, // (target, speed)
|
||||
DIV_CMD_HINT_LEGATO, // (note)
|
||||
|
||||
DIV_CMD_SAMPLE_MODE, // (enabled)
|
||||
DIV_CMD_SAMPLE_FREQ, // (frequency)
|
||||
DIV_CMD_SAMPLE_BANK, // (bank)
|
||||
|
@ -399,6 +411,12 @@ class DivDispatch {
|
|||
*/
|
||||
virtual bool getDCOffRequired();
|
||||
|
||||
/**
|
||||
* check whether PRE_NOTE command is desired.
|
||||
* @return truth.
|
||||
*/
|
||||
virtual bool getWantPreNote();
|
||||
|
||||
/**
|
||||
* get a description of a dispatch-specific effect.
|
||||
* @param effect the effect.
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "dispatch.h"
|
||||
#define _USE_MATH_DEFINES
|
||||
#include "engine.h"
|
||||
#include "instrument.h"
|
||||
|
@ -234,6 +235,251 @@ double DivEngine::benchmarkSeek() {
|
|||
return tAvg;
|
||||
}
|
||||
|
||||
#define WRITE_TICK \
|
||||
if (!wroteTick) { \
|
||||
wroteTick=true; \
|
||||
if (binary) { \
|
||||
if (tick-lastTick>255) { \
|
||||
w->writeC(0xfc); \
|
||||
w->writeS(tick-lastTick); \
|
||||
} else if (tick-lastTick>1) { \
|
||||
w->writeC(0xfd); \
|
||||
w->writeC(tick-lastTick); \
|
||||
} else { \
|
||||
w->writeC(0xfe); \
|
||||
} \
|
||||
} else { \
|
||||
w->writeText(fmt::sprintf(">> TICK %d\n",tick)); \
|
||||
} \
|
||||
lastTick=tick; \
|
||||
}
|
||||
|
||||
void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
|
||||
w->writeC(c.cmd);
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON:
|
||||
case DIV_CMD_HINT_LEGATO:
|
||||
if (c.value==DIV_NOTE_NULL) {
|
||||
w->writeC(0xff);
|
||||
} else {
|
||||
w->writeC(c.value+60);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
case DIV_CMD_HINT_VIBRATO_RANGE:
|
||||
case DIV_CMD_HINT_VIBRATO_SHAPE:
|
||||
case DIV_CMD_HINT_PITCH:
|
||||
case DIV_CMD_HINT_VOLUME:
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
case DIV_CMD_SAMPLE_FREQ:
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
case DIV_CMD_SAMPLE_DIR:
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
case DIV_CMD_FM_LFO:
|
||||
case DIV_CMD_FM_LFO_WAVE:
|
||||
case DIV_CMD_FM_FB:
|
||||
case DIV_CMD_FM_EXTCH:
|
||||
case DIV_CMD_FM_AM_DEPTH:
|
||||
case DIV_CMD_FM_PM_DEPTH:
|
||||
case DIV_CMD_STD_NOISE_FREQ:
|
||||
case DIV_CMD_STD_NOISE_MODE:
|
||||
case DIV_CMD_WAVE:
|
||||
case DIV_CMD_GB_SWEEP_TIME:
|
||||
case DIV_CMD_GB_SWEEP_DIR:
|
||||
case DIV_CMD_PCE_LFO_MODE:
|
||||
case DIV_CMD_PCE_LFO_SPEED:
|
||||
case DIV_CMD_NES_DMC:
|
||||
case DIV_CMD_C64_CUTOFF:
|
||||
case DIV_CMD_C64_RESONANCE:
|
||||
case DIV_CMD_C64_FILTER_MODE:
|
||||
case DIV_CMD_C64_RESET_TIME:
|
||||
case DIV_CMD_C64_RESET_MASK:
|
||||
case DIV_CMD_C64_FILTER_RESET:
|
||||
case DIV_CMD_C64_DUTY_RESET:
|
||||
case DIV_CMD_C64_EXTENDED:
|
||||
case DIV_CMD_AY_ENVELOPE_SET:
|
||||
case DIV_CMD_AY_ENVELOPE_LOW:
|
||||
case DIV_CMD_AY_ENVELOPE_HIGH:
|
||||
case DIV_CMD_AY_ENVELOPE_SLIDE:
|
||||
case DIV_CMD_AY_NOISE_MASK_AND:
|
||||
case DIV_CMD_AY_NOISE_MASK_OR:
|
||||
case DIV_CMD_AY_AUTO_ENVELOPE:
|
||||
w->writeC(c.value);
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
case DIV_CMD_HINT_VIBRATO:
|
||||
case DIV_CMD_HINT_ARPEGGIO:
|
||||
case DIV_CMD_HINT_PORTA:
|
||||
case DIV_CMD_FM_TL:
|
||||
case DIV_CMD_FM_AM:
|
||||
case DIV_CMD_FM_AR:
|
||||
case DIV_CMD_FM_DR:
|
||||
case DIV_CMD_FM_SL:
|
||||
case DIV_CMD_FM_D2R:
|
||||
case DIV_CMD_FM_RR:
|
||||
case DIV_CMD_FM_DT:
|
||||
case DIV_CMD_FM_DT2:
|
||||
case DIV_CMD_FM_RS:
|
||||
case DIV_CMD_FM_KSR:
|
||||
case DIV_CMD_FM_VIB:
|
||||
case DIV_CMD_FM_SUS:
|
||||
case DIV_CMD_FM_WS:
|
||||
case DIV_CMD_FM_SSG:
|
||||
case DIV_CMD_FM_REV:
|
||||
case DIV_CMD_FM_EG_SHIFT:
|
||||
case DIV_CMD_FM_MULT:
|
||||
case DIV_CMD_FM_FINE:
|
||||
case DIV_CMD_AY_IO_WRITE:
|
||||
case DIV_CMD_AY_AUTO_PWM:
|
||||
w->writeC(c.value);
|
||||
w->writeC(c.value2);
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
w->writeC((c.value?0x80:0)|(c.value2?0x40:0));
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
case DIV_CMD_C64_FINE_DUTY:
|
||||
case DIV_CMD_C64_FINE_CUTOFF:
|
||||
w->writeS(c.value);
|
||||
break;
|
||||
case DIV_CMD_FM_FIXFREQ:
|
||||
w->writeS((c.value<<12)|(c.value2&0x7ff));
|
||||
break;
|
||||
case DIV_CMD_NES_SWEEP:
|
||||
w->writeC((c.value?8:0)|(c.value2&0x77));
|
||||
break;
|
||||
default:
|
||||
logW("unimplemented command %s!",cmdName[c.cmd]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SafeWriter* DivEngine::saveCommand(bool binary) {
|
||||
stop();
|
||||
repeatPattern=false;
|
||||
setOrder(0);
|
||||
BUSY_BEGIN_SOFT;
|
||||
// determine loop point
|
||||
int loopOrder=0;
|
||||
int loopRow=0;
|
||||
int loopEnd=0;
|
||||
walkSong(loopOrder,loopRow,loopEnd);
|
||||
logI("loop point: %d %d",loopOrder,loopRow);
|
||||
|
||||
SafeWriter* w=new SafeWriter;
|
||||
w->init();
|
||||
|
||||
// write header
|
||||
if (binary) {
|
||||
w->write("FCS",4);
|
||||
} else {
|
||||
w->writeText("# Furnace Command Stream\n\n");
|
||||
|
||||
w->writeText("[Information]\n");
|
||||
w->writeText(fmt::sprintf("name: %s\n",song.name));
|
||||
w->writeText(fmt::sprintf("author: %s\n",song.author));
|
||||
w->writeText(fmt::sprintf("category: %s\n",song.category));
|
||||
w->writeText(fmt::sprintf("system: %s\n",song.systemName));
|
||||
|
||||
w->writeText("\n");
|
||||
|
||||
w->writeText("[SubSongInformation]\n");
|
||||
w->writeText(fmt::sprintf("name: %s\n",curSubSong->name));
|
||||
w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz));
|
||||
|
||||
w->writeText("\n");
|
||||
|
||||
w->writeText("[SysDefinition]\n");
|
||||
// TODO
|
||||
|
||||
w->writeText("\n");
|
||||
}
|
||||
|
||||
// play the song ourselves
|
||||
bool done=false;
|
||||
playSub(false);
|
||||
|
||||
if (!binary) {
|
||||
w->writeText("[Stream]\n");
|
||||
}
|
||||
int tick=0;
|
||||
bool oldCmdStreamEnabled=cmdStreamEnabled;
|
||||
cmdStreamEnabled=true;
|
||||
double curDivider=divider;
|
||||
int lastTick=0;
|
||||
while (!done) {
|
||||
if (nextTick(false,true) || !playing) {
|
||||
done=true;
|
||||
}
|
||||
// get command stream
|
||||
bool wroteTick=false;
|
||||
if (curDivider!=divider) {
|
||||
curDivider=divider;
|
||||
WRITE_TICK;
|
||||
if (binary) {
|
||||
w->writeC(0xfb);
|
||||
w->writeI((int)(curDivider*65536));
|
||||
} else {
|
||||
w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider));
|
||||
}
|
||||
}
|
||||
for (DivCommand& i: cmdStream) {
|
||||
switch (i.cmd) {
|
||||
// strip away hinted/useless commands
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA:
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
break;
|
||||
default:
|
||||
WRITE_TICK;
|
||||
if (binary) {
|
||||
w->writeC(i.chan);
|
||||
writePackedCommandValues(w,i);
|
||||
} else {
|
||||
w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
cmdStream.clear();
|
||||
tick++;
|
||||
}
|
||||
cmdStreamEnabled=oldCmdStreamEnabled;
|
||||
|
||||
if (binary) {
|
||||
w->writeC(0xff);
|
||||
} else {
|
||||
if (!playing) {
|
||||
w->writeText(">> END\n");
|
||||
} else {
|
||||
w->writeText(">> LOOP 0\n");
|
||||
}
|
||||
}
|
||||
|
||||
remainingLoops=-1;
|
||||
playing=false;
|
||||
freelance=false;
|
||||
extValuePresent=false;
|
||||
BUSY_END;
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
void _runExportThread(DivEngine* caller) {
|
||||
caller->runExportThread();
|
||||
}
|
||||
|
|
|
@ -45,8 +45,8 @@
|
|||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||
|
||||
#define DIV_VERSION "dev103"
|
||||
#define DIV_ENGINE_VERSION 103
|
||||
#define DIV_VERSION "dev105"
|
||||
#define DIV_ENGINE_VERSION 105
|
||||
|
||||
// for imports
|
||||
#define DIV_VERSION_MOD 0xff01
|
||||
|
@ -280,6 +280,8 @@ enum DivChanTypes {
|
|||
DIV_CH_OP=5
|
||||
};
|
||||
|
||||
extern const char* cmdName[];
|
||||
|
||||
class DivEngine {
|
||||
DivDispatchContainer disCont[32];
|
||||
TAAudio* output;
|
||||
|
@ -472,7 +474,9 @@ class DivEngine {
|
|||
// specify system to build ROM for.
|
||||
SafeWriter* buildROM(int sys);
|
||||
// dump to VGM.
|
||||
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171);
|
||||
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false);
|
||||
// dump command stream.
|
||||
SafeWriter* saveCommand(bool binary=false);
|
||||
// export to an audio file
|
||||
bool saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime=0.0);
|
||||
// wait for audio export to finish
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "../ta-log.h"
|
||||
#include "../fileutils.h"
|
||||
#include <fmt/printf.h>
|
||||
#include <limits.h>
|
||||
|
||||
enum DivInsFormats {
|
||||
DIV_INSFORMAT_DMP,
|
||||
|
@ -1262,7 +1263,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
patchNameRead = lfoRead = characteristicRead = m1Read = c1Read = m2Read = c2Read = false;
|
||||
newPatch = NULL;
|
||||
};
|
||||
auto readIntStrWithinRange = [](String&& input, int limitLow, int limitHigh) -> int {
|
||||
auto readIntStrWithinRange = [](String&& input, int limitLow = INT_MIN, int limitHigh = INT_MAX) -> int {
|
||||
int x = std::stoi(input.c_str());
|
||||
if (x > limitHigh || x < limitLow) {
|
||||
throw std::invalid_argument(fmt::sprintf("%s is out of bounds of range [%d..%d]", input, limitLow, limitHigh));
|
||||
|
@ -1280,7 +1281,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
|||
op.mult = readIntStrWithinRange(reader.readStringToken(), 0, 15);
|
||||
op.dt = fmDtRegisterToFurnace(readIntStrWithinRange(reader.readStringToken(), 0, 7));
|
||||
op.dt2 = readIntStrWithinRange(reader.readStringToken(), 0, 3);
|
||||
op.am = readIntStrWithinRange(reader.readStringToken(), 0, 1);
|
||||
op.am = readIntStrWithinRange(reader.readStringToken(), 0) > 0 ? 1 : 0;
|
||||
};
|
||||
auto seekGroupValStart = [](SafeReader& reader, int pos) {
|
||||
// Seek to position then move to next ':' character
|
||||
|
|
|
@ -528,6 +528,17 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
|||
w->writeC(0);
|
||||
}
|
||||
|
||||
// Sound Unit
|
||||
w->writeC(su.useSample);
|
||||
w->writeC(su.switchRoles);
|
||||
|
||||
// GB hardware sequence
|
||||
w->writeC(gb.hwSeqLen);
|
||||
for (int i=0; i<gb.hwSeqLen; i++) {
|
||||
w->writeC(gb.hwSeq[i].cmd);
|
||||
w->writeS(gb.hwSeq[i].data);
|
||||
}
|
||||
|
||||
blockEndSeek=w->tell();
|
||||
w->seek(blockStartSeek,SEEK_SET);
|
||||
w->writeI(blockEndSeek-blockStartSeek-4);
|
||||
|
@ -1075,6 +1086,21 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
for (int k=0; k<23; k++) reader.readC();
|
||||
}
|
||||
|
||||
// Sound Unit
|
||||
if (version>=104) {
|
||||
su.useSample=reader.readC();
|
||||
su.switchRoles=reader.readC();
|
||||
}
|
||||
|
||||
// GB hardware sequence
|
||||
if (version>=105) {
|
||||
gb.hwSeqLen=reader.readC();
|
||||
for (int i=0; i<gb.hwSeqLen; i++) {
|
||||
gb.hwSeq[i].cmd=reader.readC();
|
||||
gb.hwSeq[i].data=reader.readS();
|
||||
}
|
||||
}
|
||||
|
||||
return DIV_DATA_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
|
@ -263,12 +263,29 @@ struct DivInstrumentSTD {
|
|||
};
|
||||
|
||||
struct DivInstrumentGB {
|
||||
unsigned char envVol, envDir, envLen, soundLen;
|
||||
unsigned char envVol, envDir, envLen, soundLen, hwSeqLen;
|
||||
enum HWSeqCommands: unsigned char {
|
||||
DIV_GB_HWCMD_ENVELOPE=0,
|
||||
DIV_GB_HWCMD_SWEEP,
|
||||
DIV_GB_HWCMD_WAIT,
|
||||
DIV_GB_HWCMD_WAIT_REL,
|
||||
DIV_GB_HWCMD_LOOP,
|
||||
DIV_GB_HWCMD_LOOP_REL,
|
||||
|
||||
DIV_GB_HWCMD_MAX
|
||||
};
|
||||
struct HWSeqCommand {
|
||||
unsigned char cmd;
|
||||
unsigned short data;
|
||||
} hwSeq[256];
|
||||
DivInstrumentGB():
|
||||
envVol(15),
|
||||
envDir(0),
|
||||
envLen(2),
|
||||
soundLen(64) {}
|
||||
soundLen(64),
|
||||
hwSeqLen(0) {
|
||||
memset(hwSeq,0,256*sizeof(int));
|
||||
}
|
||||
};
|
||||
|
||||
struct DivInstrumentC64 {
|
||||
|
@ -439,6 +456,14 @@ struct DivInstrumentWaveSynth {
|
|||
param4(0) {}
|
||||
};
|
||||
|
||||
struct DivInstrumentSoundUnit {
|
||||
bool useSample;
|
||||
bool switchRoles;
|
||||
DivInstrumentSoundUnit():
|
||||
useSample(false),
|
||||
switchRoles(false) {}
|
||||
};
|
||||
|
||||
struct DivInstrument {
|
||||
String name;
|
||||
bool mode;
|
||||
|
@ -452,6 +477,7 @@ struct DivInstrument {
|
|||
DivInstrumentFDS fds;
|
||||
DivInstrumentMultiPCM multipcm;
|
||||
DivInstrumentWaveSynth ws;
|
||||
DivInstrumentSoundUnit su;
|
||||
|
||||
/**
|
||||
* save the instrument to a SafeWriter.
|
||||
|
|
|
@ -90,6 +90,10 @@ bool DivDispatch::getDCOffRequired() {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool DivDispatch::getWantPreNote() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* DivDispatch::getEffectName(unsigned char effect) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -513,6 +513,10 @@ bool DivPlatformC64::getDCOffRequired() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DivPlatformC64::getWantPreNote() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformC64::reset() {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i]=DivPlatformC64::Channel();
|
||||
|
|
|
@ -97,6 +97,7 @@ class DivPlatformC64: public DivDispatch {
|
|||
void setFlags(unsigned int flags);
|
||||
void notifyInsChange(int ins);
|
||||
bool getDCOffRequired();
|
||||
bool getWantPreNote();
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
|
|
|
@ -97,10 +97,11 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
void DivPlatformGB::updateWave() {
|
||||
rWrite(0x1a,0);
|
||||
for (int i=0; i<16; i++) {
|
||||
int nibble1=15-ws.output[i<<1];
|
||||
int nibble2=15-ws.output[1+(i<<1)];
|
||||
int nibble1=15-ws.output[((i<<1)+antiClickWavePos)&31];
|
||||
int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos)&31];
|
||||
rWrite(0x30+i,(nibble1<<4)|nibble2);
|
||||
}
|
||||
antiClickWavePos&=31;
|
||||
}
|
||||
|
||||
static unsigned char chanMuteMask[4]={
|
||||
|
@ -151,6 +152,12 @@ static unsigned char noiseTable[256]={
|
|||
};
|
||||
|
||||
void DivPlatformGB::tick(bool sysTick) {
|
||||
if (antiClickEnabled && sysTick && chan[2].freq>0) {
|
||||
antiClickPeriodCount+=((chipClock>>1)/MAX(parent->getCurHz(),1.0f));
|
||||
antiClickWavePos+=antiClickPeriodCount/chan[2].freq;
|
||||
antiClickPeriodCount%=chan[2].freq;
|
||||
}
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.arp.had) {
|
||||
|
@ -180,9 +187,8 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB);
|
||||
if (i!=2) {
|
||||
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
||||
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63)));
|
||||
} else {
|
||||
if (parent->song.waveDutyIsVol) {
|
||||
rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty.val&3)<<2]);
|
||||
|
@ -213,6 +219,10 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].keyOn=true;
|
||||
if (i==2) {
|
||||
antiClickWavePos=0;
|
||||
antiClickPeriodCount=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i==2) {
|
||||
|
@ -230,7 +240,6 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB);
|
||||
if (i==3) { // noise
|
||||
int ntPos=chan[i].baseFreq;
|
||||
if (ntPos<0) ntPos=0;
|
||||
|
@ -246,8 +255,8 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
rWrite(16+i*5,0x80);
|
||||
rWrite(16+i*5+2,gbVolMap[chan[i].vol]);
|
||||
} else {
|
||||
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
||||
rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3));
|
||||
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63)));
|
||||
rWrite(16+i*5+2,((chan[i].vol<<4))|(chan[i].envLen&7)|((chan[i].envDir&1)<<3));
|
||||
}
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
|
@ -259,10 +268,10 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
}
|
||||
if (i==3) { // noise
|
||||
rWrite(16+i*5+3,(chan[i].freq&0xff)|(chan[i].duty?8:0));
|
||||
rWrite(16+i*5+4,((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((ins->gb.soundLen<64)<<6));
|
||||
rWrite(16+i*5+4,((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<64)<<6));
|
||||
} else {
|
||||
rWrite(16+i*5+3,(2048-chan[i].freq)&0xff);
|
||||
rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((ins->gb.soundLen<63)<<6));
|
||||
rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<63)<<6));
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
|
@ -299,6 +308,12 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
}
|
||||
ws.init(ins,32,15,chan[c.chan].insChanged);
|
||||
}
|
||||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].envVol=ins->gb.envVol;
|
||||
chan[c.chan].envLen=ins->gb.envLen;
|
||||
chan[c.chan].envDir=ins->gb.envDir;
|
||||
chan[c.chan].soundLen=ins->gb.soundLen;
|
||||
}
|
||||
chan[c.chan].insChanged=false;
|
||||
break;
|
||||
}
|
||||
|
@ -317,9 +332,13 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
chan[c.chan].insChanged=true;
|
||||
if (c.chan!=2) {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB);
|
||||
chan[c.chan].vol=ins->gb.envVol;
|
||||
chan[c.chan].envVol=ins->gb.envVol;
|
||||
chan[c.chan].envLen=ins->gb.envLen;
|
||||
chan[c.chan].envDir=ins->gb.envDir;
|
||||
chan[c.chan].soundLen=ins->gb.soundLen;
|
||||
chan[c.chan].vol=chan[c.chan].envVol;
|
||||
if (parent->song.gbInsAffectsEnvelope) {
|
||||
rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3));
|
||||
rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(chan[c.chan].envLen&7)|((chan[c.chan].envDir&1)<<3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -471,6 +490,9 @@ void DivPlatformGB::reset() {
|
|||
lastPan=0xff;
|
||||
immWrite(0x25,procMute());
|
||||
immWrite(0x24,0x77);
|
||||
|
||||
antiClickPeriodCount=0;
|
||||
antiClickWavePos=0;
|
||||
}
|
||||
|
||||
bool DivPlatformGB::isStereo() {
|
||||
|
@ -507,6 +529,10 @@ void DivPlatformGB::poke(std::vector<DivRegWrite>& wlist) {
|
|||
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformGB::setFlags(unsigned int flags) {
|
||||
antiClickEnabled=!(flags&8);
|
||||
}
|
||||
|
||||
int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
chipClock=4194304;
|
||||
rate=chipClock/16;
|
||||
|
@ -519,6 +545,7 @@ int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int fl
|
|||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
gb=new GB_gameboy_t;
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 4;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ class DivPlatformGB: public DivDispatch {
|
|||
unsigned char duty, sweep;
|
||||
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta;
|
||||
signed char vol, outVol, wave;
|
||||
unsigned char envVol, envDir, envLen, soundLen;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
|
@ -54,14 +55,21 @@ class DivPlatformGB: public DivDispatch {
|
|||
inPorta(false),
|
||||
vol(15),
|
||||
outVol(15),
|
||||
wave(-1) {}
|
||||
wave(-1),
|
||||
envVol(0),
|
||||
envDir(0),
|
||||
envLen(0),
|
||||
soundLen(0) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
bool isMuted[4];
|
||||
bool antiClickEnabled;
|
||||
unsigned char lastPan;
|
||||
DivWaveSynth ws;
|
||||
|
||||
int antiClickPeriodCount, antiClickWavePos;
|
||||
|
||||
GB_gameboy_t* gb;
|
||||
unsigned char regPool[128];
|
||||
|
||||
|
@ -88,6 +96,7 @@ class DivPlatformGB: public DivDispatch {
|
|||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
void setFlags(unsigned int flags);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformGB();
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
#define CHIP_FREQBASE fmFreqBase
|
||||
#define CHIP_DIVIDER fmDivBase
|
||||
|
||||
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
|
||||
|
||||
int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
||||
if (c.chan<2) {
|
||||
return DivPlatformGenesis::dispatch(c);
|
||||
|
@ -539,7 +541,7 @@ void DivPlatformGenesisExt::forceIns() {
|
|||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
if (chan[i].active) {
|
||||
chan[i].keyOn=true;
|
||||
chan[i].freqChanged=true;
|
||||
|
|
|
@ -171,7 +171,7 @@ void DivPlatformMMC5::tick(bool sysTick) {
|
|||
|
||||
// PCM
|
||||
if (chan[2].freqChanged) {
|
||||
chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false);
|
||||
chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false,0,chan[2].pitch2,1,1);
|
||||
if (chan[2].furnaceDac) {
|
||||
double off=1.0;
|
||||
if (getSampleVaild(parent,dacSample)) {
|
||||
|
@ -200,7 +200,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
}
|
||||
dacPos=0;
|
||||
dacPeriod=0;
|
||||
chan[c.chan].baseFreq=parent->song.tuning*pow(2.0f,((float)(c.value+3)/12.0f));
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
|
@ -281,7 +281,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_PERIODIC(c.value2);
|
||||
int destFreq=(c.chan==2)?(parent->calcBaseFreq(1,1,c.value2,false)):(NOTE_PERIODIC(c.value2));
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
|
@ -311,7 +311,11 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
setSampleBank(parent,c.value);
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
if (c.chan==2) {
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)),false);
|
||||
} else {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
|
|
|
@ -293,7 +293,7 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
if (!isMuted[adpcmChan]) {
|
||||
os[0]-=aOut.data[0]>>3;
|
||||
os[1]-=aOut.data[0]>>3;
|
||||
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0];
|
||||
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=aOut.data[0];
|
||||
} else {
|
||||
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0;
|
||||
}
|
||||
|
|
|
@ -133,6 +133,10 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
}
|
||||
|
||||
void DivPlatformPCE::updateWave(int ch) {
|
||||
if (chan[ch].pcm) {
|
||||
chan[ch].deferredWaveUpdate=true;
|
||||
return;
|
||||
}
|
||||
chWrite(ch,0x04,0x5f);
|
||||
chWrite(ch,0x04,0x1f);
|
||||
for (int i=0; i<32; i++) {
|
||||
|
@ -142,6 +146,9 @@ void DivPlatformPCE::updateWave(int ch) {
|
|||
if (chan[ch].active) {
|
||||
chWrite(ch,0x04,0x80|chan[ch].outVol);
|
||||
}
|
||||
if (chan[ch].deferredWaveUpdate) {
|
||||
chan[ch].deferredWaveUpdate=false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: in octave 6 the noise table changes to a tonal one
|
||||
|
@ -226,8 +233,12 @@ void DivPlatformPCE::tick(bool sysTick) {
|
|||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
|
||||
chan[i].antiClickWavePos=0;
|
||||
chan[i].antiClickPeriodCount=0;
|
||||
}
|
||||
if (chan[i].active) {
|
||||
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) {
|
||||
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) || chan[i].deferredWaveUpdate) {
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
|
@ -561,6 +572,12 @@ void DivPlatformPCE::setFlags(unsigned int flags) {
|
|||
for (int i=0; i<6; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
||||
if (pce!=NULL) {
|
||||
delete pce;
|
||||
pce=NULL;
|
||||
}
|
||||
pce=new PCE_PSG(tempL,tempR,(flags&4)?PCE_PSG::REVISION_HUC6280A:PCE_PSG::REVISION_HUC6280);
|
||||
}
|
||||
|
||||
void DivPlatformPCE::poke(unsigned int addr, unsigned short val) {
|
||||
|
@ -579,8 +596,8 @@ int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
pce=NULL;
|
||||
setFlags(flags);
|
||||
pce=new PCE_PSG(tempL,tempR,PCE_PSG::REVISION_HUC6280A);
|
||||
reset();
|
||||
return 6;
|
||||
}
|
||||
|
@ -589,7 +606,10 @@ void DivPlatformPCE::quit() {
|
|||
for (int i=0; i<6; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
delete pce;
|
||||
if (pce!=NULL) {
|
||||
delete pce;
|
||||
pce=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformPCE::~DivPlatformPCE() {
|
||||
|
|
|
@ -34,7 +34,7 @@ class DivPlatformPCE: public DivDispatch, public DivPlatformSample {
|
|||
unsigned int dacPos;
|
||||
int dacSample, ins;
|
||||
unsigned char pan;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac, deferredWaveUpdate;
|
||||
signed char vol, outVol, wave;
|
||||
DivMacroInt std;
|
||||
DivWaveSynth ws;
|
||||
|
@ -65,6 +65,7 @@ class DivPlatformPCE: public DivDispatch, public DivPlatformSample {
|
|||
noise(false),
|
||||
pcm(false),
|
||||
furnaceDac(false),
|
||||
deferredWaveUpdate(false),
|
||||
vol(31),
|
||||
outVol(31),
|
||||
wave(-1) {}
|
||||
|
|
|
@ -362,7 +362,7 @@ void DivPlatformQSound::tick(bool sysTick) {
|
|||
off=10223616.0*getCenterRate(parent->getIns(chan[i].ins,DIV_INS_AMIGA),parent->getSample(chan[i].sample),chan[i].note,false);
|
||||
}
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,off);
|
||||
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
|
||||
if (chan[i].freq>0xefff) chan[i].freq=0xefff;
|
||||
if (chan[i].keyOn) {
|
||||
rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank);
|
||||
rWrite(q1_reg_map[Q1V_END][i], qsound_end);
|
||||
|
|
|
@ -1180,11 +1180,11 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
if ((value & 0x80)) {
|
||||
/* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU
|
||||
reads from it. */
|
||||
/*if (!CGB &&
|
||||
if (!CGB &&
|
||||
gb->apu.is_active[GB_WAVE] &&
|
||||
gb->apu.wave_channel.sample_countdown == 0 &&
|
||||
gb->apu.wave_channel.enable) {
|
||||
unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;*/
|
||||
unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;
|
||||
|
||||
/* This glitch varies between models and even specific instances:
|
||||
DMG-B: Most of them behave as emulated. A few behave differently.
|
||||
|
@ -1193,7 +1193,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
|
||||
Additionally, I believe DMGs, including those we behave differently than emulated,
|
||||
are all deterministic. */
|
||||
/*if (offset < 4) {
|
||||
if (offset < 4) {
|
||||
gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset];
|
||||
gb->apu.wave_channel.wave_form[0] = gb->apu.wave_channel.wave_form[offset / 2];
|
||||
gb->apu.wave_channel.wave_form[1] = gb->apu.wave_channel.wave_form[offset / 2 + 1];
|
||||
|
@ -1206,7 +1206,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
gb->apu.wave_channel.wave_form + (offset & ~3) * 2,
|
||||
8);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
if (!gb->apu.is_active[GB_WAVE]) {
|
||||
gb->apu.is_active[GB_WAVE] = true;
|
||||
update_sample(gb, GB_WAVE,
|
||||
|
|
|
@ -16,7 +16,7 @@ extern "C" {
|
|||
|
||||
#define GB_STRUCT_VERSION 13
|
||||
|
||||
#define CGB 0
|
||||
#define CGB (gb->model&GB_MODEL_CGB_FAMILY)
|
||||
|
||||
#define GB_MODEL_FAMILY_MASK 0xF00
|
||||
#define GB_MODEL_DMG_FAMILY 0x000
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900
|
||||
|
|
|
@ -26,37 +26,11 @@
|
|||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace Lynx
|
||||
{
|
||||
#if defined( _MSC_VER )
|
||||
|
||||
namespace
|
||||
{
|
||||
#include <intrin.h>
|
||||
|
||||
static constexpr int64_t CNT_MAX = std::numeric_limits<int64_t>::max() & ~15;
|
||||
|
||||
#if defined ( __cpp_lib_bitops )
|
||||
|
||||
#define popcnt(X) std::popcount(X)
|
||||
|
||||
#elif defined( _MSC_VER )
|
||||
|
||||
# include <intrin.h>
|
||||
|
||||
uint32_t popcnt( uint32_t x )
|
||||
{
|
||||
return __popcnt( x );
|
||||
}
|
||||
|
||||
#elif defined( __GNUC__ )
|
||||
|
||||
uint32_t popcnt( uint32_t x )
|
||||
{
|
||||
return __builtin_popcount( x );
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
uint32_t popcnt( uint32_t x )
|
||||
static uint32_t popcnt_generic( uint32_t x )
|
||||
{
|
||||
int v = 0;
|
||||
while ( x != 0 )
|
||||
|
@ -67,8 +41,61 @@ uint32_t popcnt( uint32_t x )
|
|||
return v;
|
||||
}
|
||||
|
||||
#if defined( _M_IX86 ) || defined( _M_X64 )
|
||||
|
||||
static uint32_t popcnt_intrinsic( uint32_t x )
|
||||
{
|
||||
return __popcnt( x );
|
||||
}
|
||||
|
||||
static uint32_t( *popcnt )( uint32_t );
|
||||
|
||||
//detecting popcnt availability on msvc intel
|
||||
static void selectPOPCNT()
|
||||
{
|
||||
int info[4];
|
||||
__cpuid( info, 1 );
|
||||
if ( ( info[2] & ( (int)1 << 23 ) ) != 0 )
|
||||
{
|
||||
popcnt = &popcnt_intrinsic;
|
||||
}
|
||||
else
|
||||
{
|
||||
popcnt = &popcnt_generic;
|
||||
}
|
||||
}
|
||||
|
||||
#else //defined( _M_IX86 ) || defined( _M_X64 )
|
||||
|
||||
//MSVC non INTEL should use generic implementation
|
||||
inline void selectPOPCNT()
|
||||
{
|
||||
}
|
||||
|
||||
#define popcnt popcnt_generic
|
||||
|
||||
#endif
|
||||
|
||||
#else //defined( _MSC_VER )
|
||||
|
||||
//non MVSC should use builtin implementation
|
||||
|
||||
inline void selectPOPCNT()
|
||||
{
|
||||
}
|
||||
|
||||
#define popcnt __builtin_popcount
|
||||
|
||||
#endif
|
||||
|
||||
namespace Lynx
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
static constexpr int64_t CNT_MAX = std::numeric_limits<int64_t>::max() & ~15;
|
||||
|
||||
int32_t clamp( int32_t v, int32_t lo, int32_t hi )
|
||||
{
|
||||
return v < lo ? lo : ( v > hi ? hi : v );
|
||||
|
@ -513,6 +540,7 @@ private:
|
|||
|
||||
Mikey::Mikey( uint32_t sampleRate ) : mMikey{ std::make_unique<MikeyPimpl>() }, mQueue{ std::make_unique<ActionQueue>() }, mTick{}, mNextTick{}, mSampleRate{ sampleRate }, mSamplesRemainder{}, mTicksPerSample{ 16000000 / mSampleRate, 16000000 % mSampleRate }
|
||||
{
|
||||
selectPOPCNT();
|
||||
enqueueSampling();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900, tildearrow
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900, tildearrow
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: tildearrow
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: tildearrow
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: tildearrow
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: tildearrow
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst
|
||||
|
|
|
@ -5,9 +5,22 @@
|
|||
#define minval(a,b) (((a)<(b))?(a):(b))
|
||||
#define maxval(a,b) (((a)>(b))?(a):(b))
|
||||
|
||||
#define FILVOL chan[4].special1C
|
||||
#define ILCTRL chan[4].special1D
|
||||
#define ILSIZE chan[5].special1C
|
||||
#define FIL1 chan[5].special1D
|
||||
#define IL1 chan[6].special1C
|
||||
#define IL2 chan[6].special1D
|
||||
#define IL0 chan[7].special1C
|
||||
#define MVOL chan[7].special1D
|
||||
|
||||
void SoundUnit::NextSample(short* l, short* r) {
|
||||
// run channels
|
||||
for (int i=0; i<8; i++) {
|
||||
if (chan[i].vol==0 && !chan[i].flags.swvol) {fns[i]=0; continue;}
|
||||
if (chan[i].vol==0 && !chan[i].flags.swvol) {
|
||||
fns[i]=0;
|
||||
continue;
|
||||
}
|
||||
if (chan[i].flags.pcm) {
|
||||
ns[i]=pcm[chan[i].pcmpos];
|
||||
} else switch (chan[i].flags.shape) {
|
||||
|
@ -48,13 +61,12 @@ void SoundUnit::NextSample(short* l, short* r) {
|
|||
pcmdec[i]-=32768;
|
||||
if (chan[i].pcmpos<chan[i].pcmbnd) {
|
||||
chan[i].pcmpos++;
|
||||
chan[i].wc++;
|
||||
if (chan[i].pcmpos==chan[i].pcmbnd) {
|
||||
if (chan[i].flags.pcmloop) {
|
||||
chan[i].pcmpos=chan[i].pcmrst;
|
||||
}
|
||||
}
|
||||
chan[i].pcmpos&=(SOUNDCHIP_PCM_SIZE-1);
|
||||
chan[i].pcmpos&=(pcmSize-1);
|
||||
} else if (chan[i].flags.pcmloop) {
|
||||
chan[i].pcmpos=chan[i].pcmrst;
|
||||
}
|
||||
|
@ -221,16 +233,81 @@ void SoundUnit::NextSample(short* l, short* r) {
|
|||
nsR[i]=0;
|
||||
}
|
||||
}
|
||||
|
||||
// mix
|
||||
tnsL=(nsL[0]+nsL[1]+nsL[2]+nsL[3]+nsL[4]+nsL[5]+nsL[6]+nsL[7])>>2;
|
||||
tnsR=(nsR[0]+nsR[1]+nsR[2]+nsR[3]+nsR[4]+nsR[5]+nsR[6]+nsR[7])>>2;
|
||||
|
||||
|
||||
IL1=minval(32767,maxval(-32767,tnsL))>>8;
|
||||
IL2=minval(32767,maxval(-32767,tnsR))>>8;
|
||||
|
||||
// write input lines to sample memory
|
||||
if (ILSIZE&64) {
|
||||
if (++ilBufPeriod>=((1+(FIL1>>4))<<2)) {
|
||||
ilBufPeriod=0;
|
||||
unsigned short ilLowerBound=pcmSize-((1+(ILSIZE&63))<<7);
|
||||
short next;
|
||||
if (ilBufPos<ilLowerBound) ilBufPos=ilLowerBound;
|
||||
switch (ILCTRL&3) {
|
||||
case 0:
|
||||
ilFeedback0=ilFeedback1=pcm[ilBufPos];
|
||||
next=((signed char)IL0)+((pcm[ilBufPos]*(FIL1&15))>>4);
|
||||
if (next<-128) next=-128;
|
||||
if (next>127) next=127;
|
||||
pcm[ilBufPos]=next;
|
||||
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
|
||||
break;
|
||||
case 1:
|
||||
ilFeedback0=ilFeedback1=pcm[ilBufPos];
|
||||
next=((signed char)IL1)+((pcm[ilBufPos]*(FIL1&15))>>4);
|
||||
if (next<-128) next=-128;
|
||||
if (next>127) next=127;
|
||||
pcm[ilBufPos]=next;
|
||||
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
|
||||
break;
|
||||
case 2:
|
||||
ilFeedback0=ilFeedback1=pcm[ilBufPos];
|
||||
next=((signed char)IL2)+((pcm[ilBufPos]*(FIL1&15))>>4);
|
||||
if (next<-128) next=-128;
|
||||
if (next>127) next=127;
|
||||
pcm[ilBufPos]=next;
|
||||
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
|
||||
break;
|
||||
case 3:
|
||||
ilFeedback0=pcm[ilBufPos];
|
||||
next=((signed char)IL1)+((pcm[ilBufPos]*(FIL1&15))>>4);
|
||||
if (next<-128) next=-128;
|
||||
if (next>127) next=127;
|
||||
pcm[ilBufPos]=next;
|
||||
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
|
||||
ilFeedback1=pcm[ilBufPos];
|
||||
next=((signed char)IL2)+((pcm[ilBufPos]*(FIL1&15))>>4);
|
||||
if (next<-128) next=-128;
|
||||
if (next>127) next=127;
|
||||
pcm[ilBufPos]=next;
|
||||
if (++ilBufPos>=pcmSize) ilBufPos=ilLowerBound;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ILCTRL&4) {
|
||||
if (ILSIZE&128) {
|
||||
tnsL+=ilFeedback1*(signed char)FILVOL;
|
||||
tnsR+=ilFeedback0*(signed char)FILVOL;
|
||||
} else {
|
||||
tnsL+=ilFeedback0*(signed char)FILVOL;
|
||||
tnsR+=ilFeedback1*(signed char)FILVOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*l=minval(32767,maxval(-32767,tnsL));
|
||||
*r=minval(32767,maxval(-32767,tnsR));
|
||||
}
|
||||
|
||||
void SoundUnit::Init() {
|
||||
void SoundUnit::Init(int sampleMemSize) {
|
||||
pcmSize=sampleMemSize;
|
||||
Reset();
|
||||
memset(pcm,0,SOUNDCHIP_PCM_SIZE);
|
||||
memset(pcm,0,pcmSize);
|
||||
for (int i=0; i<256; i++) {
|
||||
SCsine[i]=sin((i/128.0f)*M_PI)*127;
|
||||
SCtriangle[i]=(i>127)?(255-i):(i);
|
||||
|
@ -242,9 +319,6 @@ void SoundUnit::Init() {
|
|||
SCpantabR[128+i]=i-1;
|
||||
}
|
||||
SCpantabR[128]=0;
|
||||
for (int i=0; i<8; i++) {
|
||||
muted[i]=false;
|
||||
}
|
||||
}
|
||||
|
||||
void SoundUnit::Reset() {
|
||||
|
@ -274,6 +348,10 @@ void SoundUnit::Reset() {
|
|||
}
|
||||
tnsL=0;
|
||||
tnsR=0;
|
||||
ilBufPos=0;
|
||||
ilBufPeriod=0;
|
||||
ilFeedback0=0;
|
||||
ilFeedback1=0;
|
||||
memset(chan,0,sizeof(SUChannel)*8);
|
||||
}
|
||||
|
||||
|
@ -282,6 +360,8 @@ void SoundUnit::Write(unsigned char addr, unsigned char data) {
|
|||
}
|
||||
|
||||
SoundUnit::SoundUnit() {
|
||||
Init();
|
||||
memset(pcm,0,SOUNDCHIP_PCM_SIZE);
|
||||
Init(65536); // default
|
||||
for (int i=0; i<8; i++) {
|
||||
muted[i]=false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define SOUNDCHIP_PCM_SIZE 8192
|
||||
|
||||
class SoundUnit {
|
||||
signed char SCsine[256];
|
||||
signed char SCtriangle[256];
|
||||
|
@ -22,8 +20,13 @@ class SoundUnit {
|
|||
int nshigh[8];
|
||||
int nsband[8];
|
||||
int tnsL, tnsR;
|
||||
unsigned char ilBufPeriod;
|
||||
unsigned short ilBufPos;
|
||||
signed char ilFeedback0;
|
||||
signed char ilFeedback1;
|
||||
unsigned short oldfreq[8];
|
||||
unsigned short oldflags[8];
|
||||
unsigned int pcmSize;
|
||||
public:
|
||||
unsigned short resetfreq[8];
|
||||
unsigned short voldcycles[8];
|
||||
|
@ -81,11 +84,13 @@ class SoundUnit {
|
|||
unsigned char dir: 1;
|
||||
unsigned char bound;
|
||||
} swcut;
|
||||
unsigned short wc;
|
||||
unsigned char special1C;
|
||||
unsigned char special1D;
|
||||
unsigned short restimer;
|
||||
} chan[8];
|
||||
signed char pcm[SOUNDCHIP_PCM_SIZE];
|
||||
signed char pcm[65536];
|
||||
bool muted[8];
|
||||
void SetIL0(unsigned char addr);
|
||||
void Write(unsigned char addr, unsigned char data);
|
||||
void NextSample(short* l, short* r);
|
||||
inline int GetSample(int ch) {
|
||||
|
@ -94,7 +99,7 @@ class SoundUnit {
|
|||
if (ret>32767) ret=32767;
|
||||
return ret;
|
||||
}
|
||||
void Init();
|
||||
void Init(int sampleMemSize=8192);
|
||||
void Reset();
|
||||
SoundUnit();
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900, tildearrow
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900, tildearrow
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900, tildearrow
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/blob/vgsound_emu_v1/LICENSE for more details
|
||||
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
|
||||
|
||||
Copyright holder(s): cam900
|
||||
Modifiers and Contributors for Furnace: cam900, tildearrow
|
||||
|
|
|
@ -251,12 +251,12 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
|||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SU);
|
||||
if (chan[c.chan].pcm && ins->type!=DIV_INS_AMIGA) {
|
||||
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA);
|
||||
if (chan[c.chan].pcm && !(ins->type==DIV_INS_AMIGA || ins->su.useSample)) {
|
||||
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample);
|
||||
writeControl(c.chan);
|
||||
writeControlUpper(c.chan);
|
||||
}
|
||||
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA);
|
||||
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->su.useSample);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
@ -524,6 +524,16 @@ void DivPlatformSoundUnit::reset() {
|
|||
lfoMode=0;
|
||||
lfoSpeed=255;
|
||||
delay=500;
|
||||
|
||||
// set initial IL status
|
||||
ilCtrl=initIlCtrl;
|
||||
ilSize=initIlSize;
|
||||
fil1=initFil1;
|
||||
echoVol=initEchoVol;
|
||||
rWrite(0x9c,echoVol);
|
||||
rWrite(0x9d,ilCtrl);
|
||||
rWrite(0xbc,ilSize);
|
||||
rWrite(0xbd,fil1);
|
||||
}
|
||||
|
||||
bool DivPlatformSoundUnit::isStereo() {
|
||||
|
@ -550,6 +560,15 @@ void DivPlatformSoundUnit::setFlags(unsigned int flags) {
|
|||
for (int i=0; i<8; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
initIlCtrl=3|(flags&4);
|
||||
initIlSize=((flags>>8)&63)|((flags&4)?0x40:0)|((flags&8)?0x80:0);
|
||||
initFil1=flags>>16;
|
||||
initEchoVol=flags>>24;
|
||||
|
||||
sampleMemSize=flags&16;
|
||||
|
||||
su->Init(sampleMemSize?65536:8192);
|
||||
renderSamples();
|
||||
}
|
||||
|
||||
void DivPlatformSoundUnit::poke(unsigned int addr, unsigned short val) {
|
||||
|
@ -565,7 +584,7 @@ const void* DivPlatformSoundUnit::getSampleMem(int index) {
|
|||
}
|
||||
|
||||
size_t DivPlatformSoundUnit::getSampleMemCapacity(int index) {
|
||||
return (index==0)?8192:0;
|
||||
return (index==0)?((sampleMemSize?65536:8192)-((initIlSize&64)?((1+(initIlSize&63))<<7):0)):0;
|
||||
}
|
||||
|
||||
size_t DivPlatformSoundUnit::getSampleMemUsage(int index) {
|
||||
|
@ -578,6 +597,7 @@ void DivPlatformSoundUnit::renderSamples() {
|
|||
size_t memPos=0;
|
||||
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||
DivSample* s=parent->song.sample[i];
|
||||
if (s->data8==NULL) continue;
|
||||
int paddedLen=s->samples;
|
||||
if (memPos>=getSampleMemCapacity(0)) {
|
||||
logW("out of PCM memory for sample %d!",i);
|
||||
|
@ -604,9 +624,8 @@ int DivPlatformSoundUnit::init(DivEngine* p, int channels, int sugRate, unsigned
|
|||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
su=new SoundUnit();
|
||||
su->Init();
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 6;
|
||||
}
|
||||
|
|
|
@ -97,6 +97,10 @@ class DivPlatformSoundUnit: public DivDispatch, public DivPlatformSample {
|
|||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
unsigned char lastPan;
|
||||
bool sampleMemSize;
|
||||
unsigned char ilCtrl, ilSize, fil1;
|
||||
unsigned char initIlCtrl, initIlSize, initFil1;
|
||||
signed char echoVol, initEchoVol;
|
||||
|
||||
int cycles, curChan, delay;
|
||||
short tempL;
|
||||
|
|
|
@ -57,6 +57,16 @@ const char* cmdName[]={
|
|||
"PRE_PORTA",
|
||||
"PRE_NOTE",
|
||||
|
||||
"HINT_VIBRATO",
|
||||
"HINT_VIBRATO_RANGE",
|
||||
"HINT_VIBRATO_SHAPE",
|
||||
"HINT_PITCH",
|
||||
"HINT_ARPEGGIO",
|
||||
"HINT_VOLUME",
|
||||
"HINT_VOL_SLIDE",
|
||||
"HINT_PORTA",
|
||||
"HINT_LEGATO",
|
||||
|
||||
"SAMPLE_MODE",
|
||||
"SAMPLE_FREQ",
|
||||
"SAMPLE_BANK",
|
||||
|
@ -330,14 +340,15 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
// instrument
|
||||
bool insChanged=false;
|
||||
if (pat->data[whatRow][2]!=-1) {
|
||||
dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2]));
|
||||
if (chan[i].lastIns!=pat->data[whatRow][2]) {
|
||||
dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2]));
|
||||
chan[i].lastIns=pat->data[whatRow][2];
|
||||
insChanged=true;
|
||||
if (song.legacyVolumeSlides && chan[i].volume==chan[i].volMax+1) {
|
||||
logV("forcing volume");
|
||||
chan[i].volume=chan[i].volMax;
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -350,11 +361,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (chan[i].stopOnOff) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].stopOnOff=false;
|
||||
}
|
||||
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
/*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) {
|
||||
chan[i+1].portaNote=-1;
|
||||
chan[i+1].portaSpeed=-1;
|
||||
|
@ -371,11 +384,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (chan[i].stopOnOff) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].stopOnOff=false;
|
||||
}
|
||||
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
/*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) {
|
||||
chan[i+1].portaNote=-1;
|
||||
chan[i+1].portaSpeed=-1;
|
||||
|
@ -392,6 +407,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (!chan[i].keyOn) {
|
||||
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsArp(dispatchChanOfChan[i])) {
|
||||
chan[i].arp=0;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_ARPEGGIO,i,chan[i].arp));
|
||||
}
|
||||
}
|
||||
chan[i].doNote=true;
|
||||
|
@ -408,6 +424,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
}
|
||||
chan[i].volume=pat->data[whatRow][3]<<8;
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -452,11 +469,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (effectVal==0) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].inPorta=false;
|
||||
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
|
||||
} else {
|
||||
chan[i].portaNote=song.limitSlides?0x60:255;
|
||||
chan[i].portaSpeed=effectVal;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].portaStop=true;
|
||||
chan[i].nowYouCanStop=false;
|
||||
chan[i].stopOnOff=false;
|
||||
|
@ -472,11 +491,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (effectVal==0) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].inPorta=false;
|
||||
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
|
||||
} else {
|
||||
chan[i].portaNote=song.limitSlides?disCont[dispatchOfChan[i]].dispatch->getPortaFloor(dispatchChanOfChan[i]):-60;
|
||||
chan[i].portaSpeed=effectVal;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].portaStop=true;
|
||||
chan[i].nowYouCanStop=false;
|
||||
chan[i].stopOnOff=false;
|
||||
|
@ -490,6 +511,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (effectVal==0) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].inPorta=false;
|
||||
dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
|
||||
} else {
|
||||
|
@ -503,6 +525,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
chan[i].inPorta=true;
|
||||
chan[i].wasShorthandPorta=false;
|
||||
}
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].portaStop=true;
|
||||
if (chan[i].keyOn) chan[i].doNote=false;
|
||||
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
|
||||
|
@ -514,6 +537,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
case 0x04: // vibrato
|
||||
chan[i].vibratoDepth=effectVal&15;
|
||||
chan[i].vibratoRate=effectVal>>4;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate));
|
||||
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
|
||||
break;
|
||||
case 0x07: // tremolo
|
||||
|
@ -537,12 +561,14 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
} else {
|
||||
chan[i].volSpeed=0;
|
||||
}
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0x00: // arpeggio
|
||||
chan[i].arp=effectVal;
|
||||
if (chan[i].arp==0 && song.arp0Reset) {
|
||||
chan[i].resetArp=true;
|
||||
}
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_ARPEGGIO,i,chan[i].arp));
|
||||
break;
|
||||
case 0x0c: // retrigger
|
||||
if (effectVal!=0) {
|
||||
|
@ -574,6 +600,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
case 0xe1: // portamento up
|
||||
chan[i].portaNote=chan[i].note+(effectVal&15);
|
||||
chan[i].portaSpeed=(effectVal>>4)*4;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].portaStop=true;
|
||||
chan[i].nowYouCanStop=false;
|
||||
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
|
||||
|
@ -592,6 +619,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
case 0xe2: // portamento down
|
||||
chan[i].portaNote=chan[i].note-(effectVal&15);
|
||||
chan[i].portaSpeed=(effectVal>>4)*4;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].portaStop=true;
|
||||
chan[i].nowYouCanStop=false;
|
||||
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
|
||||
|
@ -609,9 +637,11 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
break;
|
||||
case 0xe3: // vibrato direction
|
||||
chan[i].vibratoDir=effectVal;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO_SHAPE,i,chan[i].vibratoDir));
|
||||
break;
|
||||
case 0xe4: // vibrato fine
|
||||
chan[i].vibratoFine=effectVal;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO_RANGE,i,chan[i].vibratoFine));
|
||||
break;
|
||||
case 0xe5: // pitch
|
||||
chan[i].pitch=effectVal-0x80;
|
||||
|
@ -622,6 +652,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
}
|
||||
//chan[i].pitch+=globalPitch;
|
||||
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PITCH,i,chan[i].pitch));
|
||||
break;
|
||||
case 0xea: // legato mode
|
||||
chan[i].legato=effectVal;
|
||||
|
@ -671,17 +702,21 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
break;
|
||||
case 0xf3: // fine volume ramp up
|
||||
chan[i].volSpeed=effectVal;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0xf4: // fine volume ramp down
|
||||
chan[i].volSpeed=-effectVal;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0xf8: // single volume ramp up
|
||||
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].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));
|
||||
break;
|
||||
case 0xfa: // fast volume ramp
|
||||
if (effectVal!=0) {
|
||||
|
@ -693,6 +728,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
} else {
|
||||
chan[i].volSpeed=0;
|
||||
}
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
|
||||
case 0xff: // stop song
|
||||
|
@ -723,15 +759,18 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
|
||||
if (chan[i].legato) {
|
||||
dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note));
|
||||
} else {
|
||||
if (chan[i].inPorta && chan[i].keyOn && !chan[i].shorthandPorta) {
|
||||
if (song.e1e2StopOnSameNote && chan[i].wasShorthandPorta) {
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
|
||||
chan[i].wasShorthandPorta=false;
|
||||
chan[i].inPorta=false;
|
||||
} else {
|
||||
chan[i].portaNote=chan[i].note;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
}
|
||||
} else if (!chan[i].noteOnInhibit) {
|
||||
dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,chan[i].note,chan[i].volume>>8));
|
||||
|
@ -742,12 +781,14 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
if (!chan[i].keyOn && chan[i].scheduledSlideReset) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].scheduledSlideReset=false;
|
||||
chan[i].inPorta=false;
|
||||
}
|
||||
if (!chan[i].keyOn && chan[i].volume>chan[i].volMax) {
|
||||
chan[i].volume=chan[i].volMax;
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
}
|
||||
chan[i].keyOn=true;
|
||||
chan[i].keyOff=false;
|
||||
|
@ -866,7 +907,9 @@ void DivEngine::nextRow() {
|
|||
if (!(pat->data[curRow][0]==0 && pat->data[curRow][1]==0)) {
|
||||
if (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) {
|
||||
if (!chan[i].legato) {
|
||||
dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks));
|
||||
if (disCont[dispatchOfChan[i]].dispatch!=NULL) {
|
||||
if (disCont[dispatchOfChan[i]].dispatch->getWantPreNote()) dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks));
|
||||
}
|
||||
|
||||
if (song.oneTickCut) {
|
||||
bool doPrepareCut=true;
|
||||
|
@ -914,7 +957,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
|
||||
// MIDI clock
|
||||
if (output) if (!skipping && output->midiOut!=NULL) {
|
||||
output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0));
|
||||
//output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0));
|
||||
}
|
||||
|
||||
while (!pendingNotes.empty()) {
|
||||
|
@ -985,15 +1028,19 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
if (chan[i].volume>chan[i].volMax) {
|
||||
chan[i].volume=chan[i].volMax;
|
||||
chan[i].volSpeed=0;
|
||||
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));
|
||||
} else if (chan[i].volume<0) {
|
||||
chan[i].volSpeed=0;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0));
|
||||
if (song.legacyVolumeSlides) {
|
||||
chan[i].volume=chan[i].volMax+1;
|
||||
} else {
|
||||
chan[i].volume=0;
|
||||
}
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
} else {
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
}
|
||||
|
@ -1022,10 +1069,12 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) {
|
||||
if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) {
|
||||
chan[i].portaSpeed=0;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].oldNote=chan[i].note;
|
||||
chan[i].note=chan[i].portaNote;
|
||||
chan[i].inPorta=false;
|
||||
dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1039,11 +1088,13 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
if (chan[i].stopOnOff) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
chan[i].stopOnOff=false;
|
||||
}
|
||||
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) {
|
||||
chan[i].portaNote=-1;
|
||||
chan[i].portaSpeed=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
|
||||
/*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) {
|
||||
chan[i+1].portaNote=-1;
|
||||
chan[i+1].portaSpeed=-1;
|
||||
|
@ -1057,6 +1108,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
}
|
||||
if (chan[i].resetArp) {
|
||||
dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_LEGATO,i,chan[i].note));
|
||||
chan[i].resetArp=false;
|
||||
}
|
||||
if (song.rowResetsArpPos && firstTick) {
|
||||
|
|
|
@ -120,6 +120,9 @@ int SafeWriter::writeWString(WString val, bool pascal) {
|
|||
return 2+val.size()*2;
|
||||
}
|
||||
}
|
||||
int SafeWriter::writeText(String val) {
|
||||
return write(val.c_str(),val.size());
|
||||
}
|
||||
|
||||
void SafeWriter::init() {
|
||||
if (operative) return;
|
||||
|
|
|
@ -57,6 +57,7 @@ class SafeWriter {
|
|||
int writeD_BE(double val);
|
||||
int writeWString(WString val, bool pascal);
|
||||
int writeString(String val, bool pascal);
|
||||
int writeText(String val);
|
||||
|
||||
void init();
|
||||
SafeReader* toReader();
|
||||
|
|
|
@ -1850,8 +1850,8 @@ void DivEngine::registerSystems() {
|
|||
sysDefs[DIV_SYSTEM_Y8950]=new DivSysDef(
|
||||
"Yamaha Y8950", NULL, 0xb2, 0, 10, true, false, 0x151, false,
|
||||
"like OPL but with an ADPCM channel.",
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "PCM"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "PCM"},
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "ADPCM"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "P"},
|
||||
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM},
|
||||
{DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA},
|
||||
{},
|
||||
|
@ -1862,8 +1862,8 @@ void DivEngine::registerSystems() {
|
|||
sysDefs[DIV_SYSTEM_Y8950_DRUMS]=new DivSysDef(
|
||||
"Yamaha Y8950 with drums", NULL, 0xb3, 0, 12, true, false, 0x151, false,
|
||||
"the Y8950 chip, in drums mode.",
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick/FM 7", "Snare", "Tom", "Top", "HiHat", "PCM"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "PCM"},
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick/FM 7", "Snare", "Tom", "Top", "HiHat", "ADPCM"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "P"},
|
||||
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM},
|
||||
{DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_OPL_DRUMS, DIV_INS_AMIGA},
|
||||
{DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_NULL},
|
||||
|
|
|
@ -802,7 +802,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
}
|
||||
}
|
||||
|
||||
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints) {
|
||||
if (version<0x150) {
|
||||
lastError="VGM version is too low";
|
||||
return NULL;
|
||||
|
@ -1795,6 +1795,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
playSub(false);
|
||||
size_t tickCount=0;
|
||||
bool writeLoop=false;
|
||||
int ord=-1;
|
||||
int exportChans=0;
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (!willExport[dispatchOfChan[i]]) continue;
|
||||
exportChans++;
|
||||
}
|
||||
while (!done) {
|
||||
if (loopPos==-1) {
|
||||
if (loopOrder==curOrder && loopRow==curRow && ticks==1) {
|
||||
|
@ -1820,6 +1826,26 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
writeLoop=false;
|
||||
loopPos=-1;
|
||||
}
|
||||
} else {
|
||||
// check for pattern change
|
||||
if (prevOrder!=ord) {
|
||||
logI("registering order change %d on %d",prevOrder, prevRow);
|
||||
ord=prevOrder;
|
||||
|
||||
if (patternHints) {
|
||||
w->writeC(0x67);
|
||||
w->writeC(0x66);
|
||||
w->writeC(0xfe);
|
||||
w->writeI(3+exportChans);
|
||||
w->writeC(0x01);
|
||||
w->writeC(prevOrder);
|
||||
w->writeC(prevRow);
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (!willExport[dispatchOfChan[i]]) continue;
|
||||
w->writeC(curSubSong->orders.ord[i][prevOrder]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// get register dumps
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
|
|
|
@ -80,10 +80,12 @@ const char* aboutLine[]={
|
|||
"Laggy",
|
||||
"LovelyA72",
|
||||
"LunaMoth",
|
||||
"LVintageNerd",
|
||||
"Mahbod Karamoozian",
|
||||
"Miker",
|
||||
"nicco1690",
|
||||
"NikonTeen",
|
||||
"psdominator",
|
||||
"SuperJet Spade",
|
||||
"TheDuccinator",
|
||||
"theloredev",
|
||||
|
|
|
@ -1401,6 +1401,17 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
|||
dpiScale
|
||||
);
|
||||
break;
|
||||
case GUI_FILE_EXPORT_CMDSTREAM:
|
||||
if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir();
|
||||
hasOpened=fileDialog->openSave(
|
||||
"Export Command Stream",
|
||||
{"text file", "*.txt",
|
||||
"binary file", "*.bin"},
|
||||
"text file{.txt},binary file{.bin}",
|
||||
workingDirROMExport,
|
||||
dpiScale
|
||||
);
|
||||
break;
|
||||
case GUI_FILE_EXPORT_ROM:
|
||||
showError("Coming soon!");
|
||||
break;
|
||||
|
@ -2901,6 +2912,22 @@ bool FurnaceGUI::loop() {
|
|||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::Checkbox("loop",&vgmExportLoop);
|
||||
ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"inserts data blocks on pattern changes.\n"
|
||||
"useful if you are writing a playback routine.\n\n"
|
||||
|
||||
"the format of a pattern change data block is:\n"
|
||||
"67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n"
|
||||
"- ll: length, a 32-bit little-endian number\n"
|
||||
"- oo: order\n"
|
||||
"- rr: initial row (a 0Dxx effect is able to select a different row)\n"
|
||||
"- pp: pattern index (one per channel)\n\n"
|
||||
|
||||
"pattern indexes are ordered as they appear in the song."
|
||||
);
|
||||
}
|
||||
ImGui::Text("systems to export:");
|
||||
bool hasOneAtLeast=false;
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
|
@ -2931,6 +2958,19 @@ bool FurnaceGUI::loop() {
|
|||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("export command stream...")) {
|
||||
ImGui::Text(
|
||||
"this option exports a text or binary file which\n"
|
||||
"contains a dump of the internal command stream\n"
|
||||
"produced when playing the song.\n\n"
|
||||
|
||||
"technical/development use only!"
|
||||
);
|
||||
if (ImGui::Button("export")) {
|
||||
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginMenu("add system...")) {
|
||||
for (int j=0; availableSystems[j]; j++) {
|
||||
|
@ -3242,9 +3282,12 @@ bool FurnaceGUI::loop() {
|
|||
workingDirAudioExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||
break;
|
||||
case GUI_FILE_EXPORT_VGM:
|
||||
case GUI_FILE_EXPORT_ROM:
|
||||
workingDirVGMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||
break;
|
||||
case GUI_FILE_EXPORT_ROM:
|
||||
case GUI_FILE_EXPORT_CMDSTREAM:
|
||||
workingDirROMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||
break;
|
||||
case GUI_FILE_LOAD_MAIN_FONT:
|
||||
case GUI_FILE_LOAD_PAT_FONT:
|
||||
workingDirFont=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||
|
@ -3309,6 +3352,11 @@ bool FurnaceGUI::loop() {
|
|||
if (curFileDialog==GUI_FILE_EXPORT_VGM) {
|
||||
checkExtension(".vgm");
|
||||
}
|
||||
if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM) {
|
||||
// we can't tell whether the user chose .txt or .bin in the system file picker
|
||||
const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="text file")?".txt":".bin";
|
||||
checkExtensionDual(".txt",".bin",fallbackExt);
|
||||
}
|
||||
if (curFileDialog==GUI_FILE_EXPORT_COLORS) {
|
||||
checkExtension(".cfgc");
|
||||
}
|
||||
|
@ -3468,7 +3516,7 @@ bool FurnaceGUI::loop() {
|
|||
}
|
||||
break;
|
||||
case GUI_FILE_EXPORT_VGM: {
|
||||
SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion);
|
||||
SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion,vgmExportPatternHints);
|
||||
if (w!=NULL) {
|
||||
FILE* f=ps_fopen(copyOfName.c_str(),"wb");
|
||||
if (f!=NULL) {
|
||||
|
@ -3490,6 +3538,35 @@ bool FurnaceGUI::loop() {
|
|||
case GUI_FILE_EXPORT_ROM:
|
||||
showError("Coming soon!");
|
||||
break;
|
||||
case GUI_FILE_EXPORT_CMDSTREAM: {
|
||||
String lowerCase=fileName;
|
||||
for (char& i: lowerCase) {
|
||||
if (i>='A' && i<='Z') i+='a'-'A';
|
||||
}
|
||||
bool isBinary=true;
|
||||
if ((lowerCase.size()<4 || lowerCase.rfind(".bin")!=lowerCase.size()-4)) {
|
||||
isBinary=false;
|
||||
}
|
||||
|
||||
SafeWriter* w=e->saveCommand(isBinary);
|
||||
if (w!=NULL) {
|
||||
FILE* f=ps_fopen(copyOfName.c_str(),"wb");
|
||||
if (f!=NULL) {
|
||||
fwrite(w->getFinalBuf(),1,w->size(),f);
|
||||
fclose(f);
|
||||
} 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 command stream! (%s)",e->getLastError()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GUI_FILE_LOAD_MAIN_FONT:
|
||||
settings.mainFontPath=copyOfName;
|
||||
break;
|
||||
|
@ -4083,6 +4160,7 @@ bool FurnaceGUI::init() {
|
|||
workingDirSample=e->getConfString("lastDirSample",workingDir);
|
||||
workingDirAudioExport=e->getConfString("lastDirAudioExport",workingDir);
|
||||
workingDirVGMExport=e->getConfString("lastDirVGMExport",workingDir);
|
||||
workingDirROMExport=e->getConfString("lastDirROMExport",workingDir);
|
||||
workingDirFont=e->getConfString("lastDirFont",workingDir);
|
||||
workingDirColors=e->getConfString("lastDirColors",workingDir);
|
||||
workingDirKeybinds=e->getConfString("lastDirKeybinds",workingDir);
|
||||
|
@ -4323,6 +4401,7 @@ bool FurnaceGUI::finish() {
|
|||
e->setConf("lastDirSample",workingDirSample);
|
||||
e->setConf("lastDirAudioExport",workingDirAudioExport);
|
||||
e->setConf("lastDirVGMExport",workingDirVGMExport);
|
||||
e->setConf("lastDirROMExport",workingDirROMExport);
|
||||
e->setConf("lastDirFont",workingDirFont);
|
||||
e->setConf("lastDirColors",workingDirColors);
|
||||
e->setConf("lastDirKeybinds",workingDirKeybinds);
|
||||
|
@ -4431,6 +4510,7 @@ FurnaceGUI::FurnaceGUI():
|
|||
displayError(false),
|
||||
displayExporting(false),
|
||||
vgmExportLoop(true),
|
||||
vgmExportPatternHints(false),
|
||||
wantCaptureKeyboard(false),
|
||||
oldWantCaptureKeyboard(false),
|
||||
displayMacroMenu(false),
|
||||
|
|
|
@ -266,6 +266,7 @@ enum FurnaceGUIFileDialogs {
|
|||
GUI_FILE_EXPORT_AUDIO_PER_SYS,
|
||||
GUI_FILE_EXPORT_AUDIO_PER_CHANNEL,
|
||||
GUI_FILE_EXPORT_VGM,
|
||||
GUI_FILE_EXPORT_CMDSTREAM,
|
||||
GUI_FILE_EXPORT_ROM,
|
||||
GUI_FILE_LOAD_MAIN_FONT,
|
||||
GUI_FILE_LOAD_PAT_FONT,
|
||||
|
@ -948,11 +949,14 @@ class FurnaceGUI {
|
|||
bool updateSampleTex;
|
||||
|
||||
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile;
|
||||
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirROM, workingDirTest;
|
||||
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport;
|
||||
String workingDirVGMExport, workingDirROMExport, workingDirFont, workingDirColors, workingDirKeybinds;
|
||||
String workingDirLayout, workingDirROM, workingDirTest;
|
||||
String mmlString[32];
|
||||
String mmlStringW;
|
||||
|
||||
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
|
||||
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints;
|
||||
bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
|
||||
bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly;
|
||||
bool displayPendingIns, pendingInsSingle;
|
||||
bool willExport[32];
|
||||
|
|
|
@ -43,24 +43,91 @@ const char* fmParamShortNames[3][32]={
|
|||
{"ALG", "FB", "FMS", "AMS", "A", "D", "D2", "R", "S", "TL", "RS", "ML", "DT", "DT2", "SSG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM", "EGS", "REV", "Fine", "FMS2", "AMS2"}
|
||||
};
|
||||
|
||||
const char* opllInsNames[17]={
|
||||
"User",
|
||||
"Violin",
|
||||
"Guitar",
|
||||
"Piano",
|
||||
"Flute",
|
||||
"Clarinet",
|
||||
"Oboe",
|
||||
"Trumpet",
|
||||
"Organ",
|
||||
"Horn",
|
||||
"Synth",
|
||||
"Harpsichord",
|
||||
"Vibraphone",
|
||||
"Synth Bass",
|
||||
"Acoustic Bass",
|
||||
"Electric Guitar",
|
||||
"Drums"
|
||||
const char* opllVariants[4]={
|
||||
"OPLL",
|
||||
"YMF281",
|
||||
"YM2423",
|
||||
"VRC7"
|
||||
};
|
||||
|
||||
const char* opllInsNames[4][17]={
|
||||
/* YM2413 */ {
|
||||
"User",
|
||||
"Violin",
|
||||
"Guitar",
|
||||
"Piano",
|
||||
"Flute",
|
||||
"Clarinet",
|
||||
"Oboe",
|
||||
"Trumpet",
|
||||
"Organ",
|
||||
"Horn",
|
||||
"Synth",
|
||||
"Harpsichord",
|
||||
"Vibraphone",
|
||||
"Synth Bass",
|
||||
"Acoustic Bass",
|
||||
"Electric Guitar",
|
||||
"Drums"
|
||||
},
|
||||
/* YMF281 */ {
|
||||
"User",
|
||||
"Electric String",
|
||||
"Bow wow",
|
||||
"Electric Guitar",
|
||||
"Organ",
|
||||
"Clarinet",
|
||||
"Saxophone",
|
||||
"Trumpet",
|
||||
"Street Organ",
|
||||
"Synth Brass",
|
||||
"Electric Piano",
|
||||
"Bass",
|
||||
"Vibraphone",
|
||||
"Chime",
|
||||
"Tom Tom II",
|
||||
"Noise",
|
||||
"Drums"
|
||||
},
|
||||
/* YM2423 */ {
|
||||
"User",
|
||||
"Strings",
|
||||
"Guitar",
|
||||
"Electric Guitar",
|
||||
"Electric Piano",
|
||||
"Flute",
|
||||
"Marimba",
|
||||
"Trumpet",
|
||||
"Harmonica",
|
||||
"Tuba",
|
||||
"Synth Brass",
|
||||
"Short Saw",
|
||||
"Vibraphone",
|
||||
"Electric Guitar 2",
|
||||
"Synth Bass",
|
||||
"Sitar",
|
||||
"Drums"
|
||||
},
|
||||
// stolen from FamiTracker
|
||||
/* VRC7 */ {
|
||||
"User",
|
||||
"Bell",
|
||||
"Guitar",
|
||||
"Piano",
|
||||
"Flute",
|
||||
"Clarinet",
|
||||
"Rattling Bell",
|
||||
"Trumpet",
|
||||
"Reed Organ",
|
||||
"Soft Bell",
|
||||
"Xylophone",
|
||||
"Vibraphone",
|
||||
"Brass",
|
||||
"Bass Guitar",
|
||||
"Synth",
|
||||
"Chorus",
|
||||
"Drums"
|
||||
}
|
||||
};
|
||||
|
||||
const char* oplWaveforms[8]={
|
||||
|
@ -217,6 +284,15 @@ const char* dualWSEffects[9]={
|
|||
"Phase Modulation"
|
||||
};
|
||||
|
||||
const char* gbHWSeqCmdTypes[6]={
|
||||
"Envelope",
|
||||
"Sweep",
|
||||
"Wait",
|
||||
"Wait for Release",
|
||||
"Loop",
|
||||
"Loop until Release"
|
||||
};
|
||||
|
||||
const char* macroAbsoluteMode="Fixed";
|
||||
const char* macroRelativeMode="Relative";
|
||||
const char* macroQSoundMode="QSound";
|
||||
|
@ -1572,10 +1648,59 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::TableNextColumn();
|
||||
drawAlgorithm(0,FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,24.0*dpiScale));
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
if (ImGui::BeginCombo("##LLPreset",opllInsNames[ins->fm.opllPreset])) {
|
||||
for (int i=0; i<17; i++) {
|
||||
if (ImGui::Selectable(opllInsNames[i])) {
|
||||
ins->fm.opllPreset=i;
|
||||
|
||||
bool isPresent[4];
|
||||
int isPresentCount=0;
|
||||
memset(isPresent,0,4*sizeof(bool));
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
if (e->song.system[i]==DIV_SYSTEM_VRC7) {
|
||||
isPresent[3]=true;
|
||||
} else if (e->song.system[i]==DIV_SYSTEM_OPLL || e->song.system[i]==DIV_SYSTEM_OPLL_DRUMS) {
|
||||
isPresent[(e->song.systemFlags[i]>>4)&3]=true;
|
||||
}
|
||||
}
|
||||
if (!isPresent[0] && !isPresent[1] && !isPresent[2] && !isPresent[3]) {
|
||||
isPresent[0]=true;
|
||||
}
|
||||
for (int i=0; i<4; i++) {
|
||||
if (isPresent[i]) isPresentCount++;
|
||||
}
|
||||
int presentWhich=0;
|
||||
for (int i=0; i<4; i++) {
|
||||
if (isPresent[i]) {
|
||||
presentWhich=i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::BeginCombo("##LLPreset",opllInsNames[presentWhich][ins->fm.opllPreset])) {
|
||||
if (isPresentCount>1) {
|
||||
if (ImGui::BeginTable("LLPresetList",isPresentCount)) {
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
|
||||
for (int i=0; i<4; i++) {
|
||||
if (!isPresent[i]) continue;
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s name",opllVariants[i]);
|
||||
}
|
||||
for (int i=0; i<17; i++) {
|
||||
ImGui::TableNextRow();
|
||||
for (int j=0; j<4; j++) {
|
||||
if (!isPresent[j]) continue;
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID(j*17+i);
|
||||
if (ImGui::Selectable(opllInsNames[j][i])) {
|
||||
ins->fm.opllPreset=i;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
} else {
|
||||
for (int i=0; i<17; i++) {
|
||||
if (ImGui::Selectable(opllInsNames[presentWhich][i])) {
|
||||
ins->fm.opllPreset=i;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
|
@ -2858,6 +2983,152 @@ void FurnaceGUI::drawInsEdit() {
|
|||
}
|
||||
|
||||
drawGBEnv(ins->gb.envVol,ins->gb.envLen,ins->gb.soundLen,ins->gb.envDir,ImVec2(ImGui::GetContentRegionAvail().x,100.0f*dpiScale));
|
||||
|
||||
if (ImGui::BeginChild("HWSeq",ImGui::GetContentRegionAvail(),true,ImGuiWindowFlags_MenuBar)) {
|
||||
ImGui::BeginMenuBar();
|
||||
ImGui::Text("Hardware Sequence");
|
||||
ImGui::EndMenuBar();
|
||||
|
||||
if (ins->gb.hwSeqLen>0) if (ImGui::BeginTable("HWSeqList",2)) {
|
||||
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
|
||||
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch);
|
||||
int curFrame=0;
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Tick");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Command");
|
||||
for (int i=0; i<ins->gb.hwSeqLen; i++) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d (#%d)",curFrame,i);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID(i);
|
||||
if (ins->gb.hwSeq[i].cmd>=DivInstrumentGB::DIV_GB_HWCMD_MAX) {
|
||||
ins->gb.hwSeq[i].cmd=0;
|
||||
}
|
||||
int cmd=ins->gb.hwSeq[i].cmd;
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
if (ImGui::Combo("##HWSeqCmd",&cmd,gbHWSeqCmdTypes,DivInstrumentGB::DIV_GB_HWCMD_MAX)) {
|
||||
if (ins->gb.hwSeq[i].cmd!=cmd) {
|
||||
ins->gb.hwSeq[i].cmd=cmd;
|
||||
ins->gb.hwSeq[i].data=0;
|
||||
}
|
||||
}
|
||||
bool somethingChanged=false;
|
||||
switch (ins->gb.hwSeq[i].cmd) {
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_ENVELOPE: {
|
||||
int hwsVol=(ins->gb.hwSeq[i].data&0xf0)>>4;
|
||||
bool hwsDir=ins->gb.hwSeq[i].data&8;
|
||||
int hwsLen=ins->gb.hwSeq[i].data&7;
|
||||
int hwsSoundLen=ins->gb.hwSeq[i].data>>8;
|
||||
|
||||
if (CWSliderInt("Volume",&hwsVol,0,15)) {
|
||||
somethingChanged=true;
|
||||
}
|
||||
if (CWSliderInt("Env Length",&hwsLen,0,7)) {
|
||||
somethingChanged=true;
|
||||
}
|
||||
if (CWSliderInt("Sound Length",&hwsSoundLen,0,64,hwsSoundLen>63?"Infinity":"%d")) {
|
||||
somethingChanged=true;
|
||||
}
|
||||
if (ImGui::RadioButton("Up",hwsDir)) { PARAMETER
|
||||
hwsDir=true;
|
||||
somethingChanged=true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("Down",!hwsDir)) { PARAMETER
|
||||
hwsDir=false;
|
||||
somethingChanged=true;
|
||||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
ins->gb.hwSeq[i].data=(hwsLen&7)|(hwsDir?8:0)|(hwsVol<<4)|(hwsSoundLen<<8);
|
||||
PARAMETER;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_SWEEP: {
|
||||
int hwsShift=ins->gb.hwSeq[i].data&7;
|
||||
int hwsSpeed=(ins->gb.hwSeq[i].data&0x70)>>4;
|
||||
bool hwsDir=ins->gb.hwSeq[i].data&8;
|
||||
|
||||
if (CWSliderInt("Shift",&hwsShift,0,7)) {
|
||||
somethingChanged=true;
|
||||
}
|
||||
if (CWSliderInt("Speed",&hwsSpeed,0,7)) {
|
||||
somethingChanged=true;
|
||||
}
|
||||
|
||||
if (ImGui::RadioButton("Up",hwsDir)) { PARAMETER
|
||||
hwsDir=true;
|
||||
somethingChanged=true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("Down",!hwsDir)) { PARAMETER
|
||||
hwsDir=false;
|
||||
somethingChanged=true;
|
||||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
ins->gb.hwSeq[i].data=(hwsShift&7)|(hwsDir?8:0)|(hwsSpeed<<4);
|
||||
PARAMETER;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_WAIT: {
|
||||
int len=ins->gb.hwSeq[i].data+1;
|
||||
curFrame+=ins->gb.hwSeq[i].data+1;
|
||||
|
||||
if (ImGui::InputInt("Ticks",&len)) {
|
||||
if (len<1) len=1;
|
||||
if (len>255) len=256;
|
||||
somethingChanged=true;
|
||||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
ins->gb.hwSeq[i].data=len-1;
|
||||
PARAMETER;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_WAIT_REL:
|
||||
curFrame++;
|
||||
break;
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_LOOP:
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_LOOP_REL: {
|
||||
int pos=ins->gb.hwSeq[i].data;
|
||||
|
||||
if (ImGui::InputInt("Position",&pos)) {
|
||||
if (pos<0) pos=0;
|
||||
if (pos>(ins->gb.hwSeqLen-1)) pos=(ins->gb.hwSeqLen-1);
|
||||
somethingChanged=true;
|
||||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
ins->gb.hwSeq[i].data=pos;
|
||||
PARAMETER;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
if (ImGui::Button(ICON_FA_PLUS "##HWCmdAdd")) {
|
||||
if (ins->gb.hwSeqLen<255) {
|
||||
ins->gb.hwSeq[ins->gb.hwSeqLen].cmd=0;
|
||||
ins->gb.hwSeq[ins->gb.hwSeqLen].data=0;
|
||||
ins->gb.hwSeqLen++;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ins->type==DIV_INS_C64) if (ImGui::BeginTabItem("C64")) {
|
||||
|
@ -2977,13 +3248,17 @@ void FurnaceGUI::drawInsEdit() {
|
|||
P(ImGui::Checkbox("Don't test/gate before new note",&ins->c64.noTest));
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ins->type==DIV_INS_AMIGA) if (ImGui::BeginTabItem("Sample")) {
|
||||
if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SU) if (ImGui::BeginTabItem((ins->type==DIV_INS_SU)?"Sound Unit":"Sample")) {
|
||||
String sName;
|
||||
if (ins->amiga.initSample<0 || ins->amiga.initSample>=e->song.sampleLen) {
|
||||
sName="none selected";
|
||||
} else {
|
||||
sName=e->song.sample[ins->amiga.initSample]->name;
|
||||
}
|
||||
if (ins->type==DIV_INS_SU) {
|
||||
P(ImGui::Checkbox("Use sample",&ins->su.useSample));
|
||||
P(ImGui::Checkbox("Switch roles of frequency and phase reset timer",&ins->su.switchRoles));
|
||||
}
|
||||
if (ImGui::BeginCombo("Initial Sample",sName.c_str())) {
|
||||
String id;
|
||||
for (int i=0; i<e->song.sampleLen; i++) {
|
||||
|
@ -2994,14 +3269,16 @@ void FurnaceGUI::drawInsEdit() {
|
|||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
P(ImGui::Checkbox("Use wavetable (Amiga only)",&ins->amiga.useWave));
|
||||
if (ins->amiga.useWave) {
|
||||
int len=ins->amiga.waveLen+1;
|
||||
if (ImGui::InputInt("Width",&len,2,16)) {
|
||||
if (len<2) len=2;
|
||||
if (len>256) len=256;
|
||||
ins->amiga.waveLen=(len&(~1))-1;
|
||||
PARAMETER
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
P(ImGui::Checkbox("Use wavetable (Amiga only)",&ins->amiga.useWave));
|
||||
if (ins->amiga.useWave) {
|
||||
int len=ins->amiga.waveLen+1;
|
||||
if (ImGui::InputInt("Width",&len,2,16)) {
|
||||
if (len<2) len=2;
|
||||
if (len>256) len=256;
|
||||
ins->amiga.waveLen=(len&(~1))-1;
|
||||
PARAMETER
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::BeginDisabled(ins->amiga.useWave);
|
||||
|
|
|
@ -1712,7 +1712,7 @@ void FurnaceGUI::drawSettings() {
|
|||
ImGui::Text("%s",SDL_GetScancodeName((SDL_Scancode)i.scan));
|
||||
ImGui::TableNextColumn();
|
||||
if (i.val==102) {
|
||||
snprintf(id,4095,"Envelope release##SNType_%d",i.scan);
|
||||
snprintf(id,4095,"Macro release##SNType_%d",i.scan);
|
||||
if (ImGui::Button(id)) {
|
||||
noteKeys[i.scan]=0;
|
||||
}
|
||||
|
|
|
@ -118,6 +118,66 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
|
|||
if (ImGui::Checkbox("Disable anti-click",&antiClick)) {
|
||||
copyOfFlags=(flags&(~8))|(antiClick<<3);
|
||||
}
|
||||
ImGui::Text("Chip revision:");
|
||||
if (ImGui::RadioButton("HuC6280 (original)",(flags&4)==0)) {
|
||||
copyOfFlags=(flags&(~4))|0;
|
||||
}
|
||||
if (ImGui::RadioButton("HuC6280A (SuperGrafx)",(flags&4)==4)) {
|
||||
copyOfFlags=(flags&(~4))|4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_SOUND_UNIT: {
|
||||
ImGui::Text("CPU rate:");
|
||||
if (ImGui::RadioButton("6.18MHz (NTSC)",(flags&3)==0)) {
|
||||
copyOfFlags=(flags&(~3))|0;
|
||||
}
|
||||
if (ImGui::RadioButton("5.95MHz (PAL)",(flags&3)==1)) {
|
||||
copyOfFlags=(flags&(~3))|1;
|
||||
}
|
||||
ImGui::Text("Chip revision (sample memory):");
|
||||
if (ImGui::RadioButton("A/B/E (8K)",(flags&16)==0)) {
|
||||
copyOfFlags=(flags&(~16))|0;
|
||||
}
|
||||
if (ImGui::RadioButton("D/F (64K)",(flags&16)==16)) {
|
||||
copyOfFlags=(flags&(~16))|16;
|
||||
}
|
||||
bool echo=flags&4;
|
||||
if (ImGui::Checkbox("Enable echo",&echo)) {
|
||||
copyOfFlags=(flags&(~4))|(echo<<2);
|
||||
}
|
||||
bool flipEcho=flags&8;
|
||||
if (ImGui::Checkbox("Swap echo channels",&flipEcho)) {
|
||||
copyOfFlags=(flags&(~8))|(flipEcho<<3);
|
||||
}
|
||||
ImGui::Text("Echo delay:");
|
||||
int echoBufSize=(flags&0x3f00)>>8;
|
||||
if (CWSliderInt("##EchoBufSize",&echoBufSize,0,63)) {
|
||||
if (echoBufSize<0) echoBufSize=0;
|
||||
if (echoBufSize>63) echoBufSize=63;
|
||||
copyOfFlags=(flags&~0x3f00)|(echoBufSize<<8);
|
||||
} rightClickable
|
||||
ImGui::Text("Echo resolution:");
|
||||
int echoResolution=(flags&0xf00000)>>20;
|
||||
if (CWSliderInt("##EchoResolution",&echoResolution,0,15)) {
|
||||
if (echoResolution<0) echoResolution=0;
|
||||
if (echoResolution>15) echoResolution=15;
|
||||
copyOfFlags=(flags&(~0xf00000))|(echoResolution<<20);
|
||||
} rightClickable
|
||||
ImGui::Text("Echo feedback:");
|
||||
int echoFeedback=(flags&0xf0000)>>16;
|
||||
if (CWSliderInt("##EchoFeedback",&echoFeedback,0,15)) {
|
||||
if (echoFeedback<0) echoFeedback=0;
|
||||
if (echoFeedback>15) echoFeedback=15;
|
||||
copyOfFlags=(flags&(~0xf0000))|(echoFeedback<<16);
|
||||
} rightClickable
|
||||
ImGui::Text("Echo volume:");
|
||||
int echoVolume=(signed char)((flags&0xff000000)>>24);
|
||||
if (CWSliderInt("##EchoVolume",&echoVolume,-128,127)) {
|
||||
if (echoVolume<-128) echoVolume=-128;
|
||||
if (echoVolume>127) echoVolume=127;
|
||||
copyOfFlags=(flags&(~0xff000000))|(((unsigned char)echoVolume)<<24);
|
||||
} rightClickable
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_GB: {
|
||||
|
|
|
@ -152,7 +152,7 @@ void FurnaceGUI::drawWaveEdit() {
|
|||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Width");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback.");
|
||||
ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine, SCC, Konami Bubble System, Namco WSG and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback.");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(96.0f*dpiScale);
|
||||
|
@ -166,7 +166,7 @@ void FurnaceGUI::drawWaveEdit() {
|
|||
ImGui::SameLine();
|
||||
ImGui::Text("Height");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010\nany other heights will be scaled during playback.");
|
||||
ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, Namco WSG, Konami Bubble System, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010 and SCC\nany other heights will be scaled during playback.");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(96.0f*dpiScale);
|
||||
|
|
34
src/main.cpp
34
src/main.cpp
|
@ -52,6 +52,7 @@ FurnaceCLI cli;
|
|||
|
||||
String outName;
|
||||
String vgmOutName;
|
||||
String cmdOutName;
|
||||
int loops=1;
|
||||
int benchMode=0;
|
||||
DivAudioExportModes outMode=DIV_EXPORT_MODE_ONE;
|
||||
|
@ -63,6 +64,7 @@ bool consoleMode=true;
|
|||
#endif
|
||||
|
||||
bool displayEngineFailError=false;
|
||||
bool cmdOutBinary=false;
|
||||
|
||||
std::vector<TAParam> params;
|
||||
|
||||
|
@ -114,6 +116,11 @@ TAParamResult pConsole(String val) {
|
|||
return TA_PARAM_SUCCESS;
|
||||
}
|
||||
|
||||
TAParamResult pBinary(String val) {
|
||||
cmdOutBinary=true;
|
||||
return TA_PARAM_SUCCESS;
|
||||
}
|
||||
|
||||
TAParamResult pLogLevel(String val) {
|
||||
if (val=="trace") {
|
||||
logLevel=LOGLEVEL_TRACE;
|
||||
|
@ -250,6 +257,12 @@ TAParamResult pVGMOut(String val) {
|
|||
return TA_PARAM_SUCCESS;
|
||||
}
|
||||
|
||||
TAParamResult pCmdOut(String val) {
|
||||
cmdOutName=val;
|
||||
e.setAudio(DIV_AUDIO_DUMMY);
|
||||
return TA_PARAM_SUCCESS;
|
||||
}
|
||||
|
||||
bool needsValue(String param) {
|
||||
for (size_t i=0; i<params.size(); i++) {
|
||||
if (params[i].name==param) {
|
||||
|
@ -265,6 +278,8 @@ void initParams() {
|
|||
params.push_back(TAParam("a","audio",true,pAudio,"jack|sdl","set audio engine (SDL by default)"));
|
||||
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("C","cmdout",true,pCmdOut,"<filename>","output command stream"));
|
||||
params.push_back(TAParam("b","binary",false,pBinary,"","set command stream output format to binary"));
|
||||
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 (pattern by default)"));
|
||||
params.push_back(TAParam("c","console",false,pConsole,"","enable console mode"));
|
||||
|
@ -307,6 +322,7 @@ int main(int argc, char** argv) {
|
|||
#endif
|
||||
outName="";
|
||||
vgmOutName="";
|
||||
cmdOutName="";
|
||||
|
||||
initParams();
|
||||
|
||||
|
@ -443,7 +459,23 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
if (outName!="" || vgmOutName!="") {
|
||||
if (outName!="" || vgmOutName!="" || cmdOutName!="") {
|
||||
if (cmdOutName!="") {
|
||||
SafeWriter* w=e.saveCommand(cmdOutBinary);
|
||||
if (w!=NULL) {
|
||||
FILE* f=fopen(cmdOutName.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("could not write command stream!");
|
||||
}
|
||||
}
|
||||
if (vgmOutName!="") {
|
||||
SafeWriter* w=e.saveVGM();
|
||||
if (w!=NULL) {
|
||||
|
|
Loading…
Reference in New Issue