Merge branch 'master' into centerrate

This commit is contained in:
cam900 2022-08-07 22:56:45 +09:00 committed by GitHub
commit ef157880d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 1330 additions and 182 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

BIN
demos/Bullet_Hell.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/home_wfl_opl3.fur Normal file

Binary file not shown.

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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.

View File

@ -90,6 +90,10 @@ bool DivDispatch::getDCOffRequired() {
return false;
}
bool DivDispatch::getWantPreNote() {
return false;
}
const char* DivDispatch::getEffectName(unsigned char effect) {
return NULL;
}

View File

@ -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();

View File

@ -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);

View File

@ -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;
}

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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() {

View File

@ -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) {}

View File

@ -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);

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -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();
};

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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();

View File

@ -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},

View File

@ -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++) {

View File

@ -80,10 +80,12 @@ const char* aboutLine[]={
"Laggy",
"LovelyA72",
"LunaMoth",
"LVintageNerd",
"Mahbod Karamoozian",
"Miker",
"nicco1690",
"NikonTeen",
"psdominator",
"SuperJet Spade",
"TheDuccinator",
"theloredev",

View File

@ -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),

View File

@ -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];

View File

@ -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);

View File

@ -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;
}

View File

@ -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: {

View File

@ -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);

View File

@ -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) {