Merge branch 'master' into doc-general

This commit is contained in:
Electric Keet 2023-08-21 12:34:51 -07:00 committed by GitHub
commit f4726190b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 274 additions and 60 deletions

View File

@ -757,15 +757,11 @@ endif()
if (WITH_RENDER_DX11)
if (WIN32)
if (SUPPORT_XP)
message(FATAL_ERROR "SUPPORT_XP is on. cannot enable DirectX 11 backend.")
else()
list(APPEND GUI_SOURCES src/gui/render/renderDX11.cpp)
list(APPEND GUI_SOURCES extern/imgui_patched/backends/imgui_impl_dx11.cpp)
list(APPEND DEPENDENCIES_DEFINES HAVE_RENDER_DX11)
list(APPEND DEPENDENCIES_LIBRARIES d3d11)
message(STATUS "UI render backend: DirectX 11")
endif()
list(APPEND GUI_SOURCES src/gui/render/renderDX11.cpp)
list(APPEND GUI_SOURCES extern/imgui_patched/backends/imgui_impl_dx11.cpp)
list(APPEND DEPENDENCIES_DEFINES HAVE_RENDER_DX11)
list(APPEND DEPENDENCIES_LIBRARIES d3d11)
message(STATUS "UI render backend: DirectX 11")
else()
message(FATAL_ERROR "DirectX 11 render backend only for Windows!")
endif()

View File

@ -48,8 +48,10 @@ for other operating systems, you may [build the source](#developer-info).
- Ricoh RF5C68 used in Sega CD and FM Towns
- OKI MSM6258 and MSM6295
- Konami K007232
- Konami K053260
- Irem GA20
- Ensoniq ES5506
- Namco C140
- wavetable chips:
- HuC6280 used in PC Engine
- Konami Bubble System WSG
@ -73,6 +75,7 @@ for other operating systems, you may [build the source](#developer-info).
- QuadTone engine
- Pokémon Mini
- Commodore PET
- TED used in Commodore Plus/4
- Casio PV-1000
- TIA used in Atari 2600
- POKEY used in Atari 8-bit computers
@ -124,7 +127,7 @@ for other operating systems, you may [build the source](#developer-info).
# quick references
- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, the [official Revolt](https://rvlt.gg/GRPS6tmc) or the [official Discord server](https://discord.gg/EfrwT2wq7z).
- **help**: check out the [documentation](doc/README.md). it's about 80% complete.
- **help**: check out the [documentation](doc/README.md). it's about 90% complete.
## packages

View File

@ -4,8 +4,9 @@ here is a small collection of useful tricks and techniques to really make Furnac
- [using samples with limited playback rates](limited-samples.md)
- [choosing emulation cores](emulation-cores.md)
- [guide on using OPLL patch macro](opllswitching.md)
- [using OPLL patch macro](opllswitching.md)
- [using AY/SAA hardware envelope](envelope.md)
# links
- [FM Synthesis of Real Instruments](http://www.javelinart.com/FM_Synthesis_of_Real_Instruments.pdf): an in-depth tutorial on creating FM patches from scratch.
- [FM Synthesis of Real Instruments](http://www.javelinart.com/FM_Synthesis_of_Real_Instruments.pdf): an in-depth tutorial on creating FM patches from scratch.

27
doc/9-guides/envelope.md Normal file
View File

@ -0,0 +1,27 @@
# AY-3-8910/8930/SAA1099 envelope guide
AY-3-8910 programmable sound generator, aside of normal 4-bit volume control, has an hardware volume envelope - a feature that allows you for defining shape of volume envelope at arbibrary speed, according to 8 preset envelope shapes. One may think, what is any upside of hardware envelope? Well, it's somewhat independent of tone/noise generators, and it goes so high in frequency, it can be used melodically! This guide explains how to abuse AY/SAA envelope.
## AY-3-8910/AY8930
going into instrument editor, first set the waveform macro value to `envelope`. This will disable any output, but don't worry. Then, go to `Envelope` macro and select `enable`. You will hear a very high-pitched squeak. This is because you must set envelope period - the frequency at which hardware envelope runs. You can do it in two ways:
- either via 23xx and 24xx effects (envelope coarse and fine period) or...
- 29xx auto-envelope period effect and macros
Auto-envelope works via numerator and denominator. In general, the higher the numerator, the higher the envelope pitch. The higher the denominator, the lower the envelope pitch. Why there are both of these? Because, envelope generator might be used to mask the tone output (i.e. affect the square wave as well). To do it, set the waveform macro values to both square and envelope. Then, the higher the denominator value, then the lower the envelope pitch relative to the square wave output, analogously the numerator. With square + envelope setting, a lot of wild, detuned, synth instruments can do made.
Back to the hardware envelope itself. Depending of the `Envelope` macro value, different envelope shapes can be obtained. The most basic one, at 8 is a sawtooth wave. The `direction` value will invert the envelope, producing the reverse sawtooth. The `alternate` value produces an interesting pseudo-triangular wave, similiar to halved sine. That one can also be reversed. `Hold` option disables the envelope.
WARNING: the envelope pitch resolution is fairly low, at high pitched it will be detuned. Hence, it was used mostly for bass.
WARNING: there is only one hardware envelope generator. So, you cant use two pitches/two waveforms at once.
## SAA1099
SAA envelope works a bit differently, It doesn't have its own pitch, it reles on a channel 2/5 pitch. It also has much more parameters than AY envelope. To use it: go to waveform macro, and set it to 0 (unless you want to have sqaure wave mask). Then, set up an envelope macro: tuen on enabled, loop and, depending on a desired shape, cut and direction. Resolution will give you higher pitch range than on AY.
Then lay two notes in pattern editor: the one in channel 2 will control the envelope pitch, the one in channel 3 can be any note you wish, its just to enable the envelope output.
## examples
- [Demoscene-type Beat by Duccinator](https://www.youtube.com/watch?v=qcBgmpPrlUA)
- [Philips SAA1099 Test by Duccinator](https://www.youtube.com/watch?v=IBh2gr09zjs)
- [Touhou Kaikidan: Mystic Square title theme by ZUN](https://www.youtube.com/watch?v=tUKei7Pz0Fw) /rare instance of AY envelope used for drums, it can be used to mask the noise generator output too

2
extern/fmt vendored

@ -1 +1 @@
Subproject commit afbcf1e8eafc5d7f27e29c7397f22521eaa33fac
Subproject commit e57ca2e3685b160617d3d95fcd9e789c4e06ca88

View File

@ -15,7 +15,7 @@ fi
cd win32build
# TODO: potential Arch-ism?
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON -DWITH_RENDER_DX11=OFF .. || exit 1
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON -DWITH_RENDER_DX11=ON .. || exit 1
make -j8 || exit 1
cd ..

View File

@ -822,9 +822,6 @@ void DivEngine::runExportThread() {
size_t curFadeOutSample=0;
bool isFadingOut=false;
quitDispatch();
initDispatch(true);
switch (exportMode) {
case DIV_EXPORT_MODE_ONE: {
SNDFILE* sf;
@ -1149,8 +1146,6 @@ void DivEngine::runExportThread() {
}
}
quitDispatch();
initDispatch(false);
stopExport=false;
}
#else
@ -1158,6 +1153,11 @@ void DivEngine::runExportThread() {
}
#endif
bool DivEngine::shallSwitchCores() {
// TODO: detect whether we should
return true;
}
bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime) {
#ifndef HAVE_SNDFILE
logE("Furnace was not compiled with libsndfile. cannot export!");
@ -1187,6 +1187,20 @@ bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode,
} else {
remainingLoops=-1;
}
if (shallSwitchCores()) {
bool isMutedBefore[DIV_MAX_CHANS];
memcpy(isMutedBefore,isMuted,DIV_MAX_CHANS*sizeof(bool));
quitDispatch();
initDispatch(true);
renderSamplesP();
for (int i=0; i<chans; i++) {
if (isMutedBefore[i]) {
muteChannel(i,true);
}
}
}
exportLoopCount=loops;
exportThread=new std::thread(_runExportThread,this);
return true;
@ -1202,9 +1216,26 @@ void DivEngine::waitAudioFile() {
bool DivEngine::haltAudioFile() {
stopExport=true;
stop();
waitAudioFile();
finishAudioFile();
return true;
}
void DivEngine::finishAudioFile() {
if (shallSwitchCores()) {
bool isMutedBefore[DIV_MAX_CHANS];
memcpy(isMutedBefore,isMuted,DIV_MAX_CHANS*sizeof(bool));
quitDispatch();
initDispatch(false);
renderSamplesP();
for (int i=0; i<chans; i++) {
if (isMutedBefore[i]) {
muteChannel(i,true);
}
}
}
}
void DivEngine::notifyInsChange(int ins) {
BUSY_BEGIN;
for (int i=0; i<song.systemLen; i++) {

View File

@ -497,6 +497,7 @@ class DivEngine {
void playSub(bool preserveDrift, int goalRow=0);
void runMidiClock(int totalCycles=1);
void runMidiTime(int totalCycles=1);
bool shallSwitchCores();
void testFunction();
@ -614,6 +615,8 @@ class DivEngine {
void waitAudioFile();
// stop audio file export
bool haltAudioFile();
// return back to playback cores if necessary
void finishAudioFile();
// notify instrument parameter change
void notifyInsChange(int ins);
// notify wavetable change

View File

@ -449,6 +449,7 @@ void DivPlatformC140::renderSamples(int sysID) {
if (memPos+length>=(getSampleMemCapacity())) {
if (s->depth==DIV_SAMPLE_DEPTH_MULAW) {
for (unsigned int i=0; i<(getSampleMemCapacity())-memPos; i++) {
if (i>=s->lengthMuLaw) break;
unsigned char x=s->dataMuLaw[i]^0xff;
if (x&0x80) x^=15;
unsigned char c140Mu=(x&0x80)|((x&15)<<3)|((x&0x70)>>4);
@ -461,6 +462,7 @@ void DivPlatformC140::renderSamples(int sysID) {
} else {
if (s->depth==DIV_SAMPLE_DEPTH_MULAW) {
for (unsigned int i=0; i<length; i++) {
if (i>=s->lengthMuLaw) break;
unsigned char x=s->dataMuLaw[i]^0xff;
if (x&0x80) x^=15;
unsigned char c140Mu=(x&0x80)|((x&15)<<3)|((x&0x70)>>4);

View File

@ -559,6 +559,17 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
rWrite(0x22,lfoValue);
}
if (opChan[i].std.panL.had) {
opChan[i].pan=opChan[i].std.panL.val&3;
if (parent->song.sharedExtStat) {
for (int j=0; j<4; j++) {
if (i==j) continue;
opChan[j].pan=opChan[i].pan;
}
}
rWrite(chanOffs[extChanOffs]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[i].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));
}
// param macros
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[i]];
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[i]];

View File

@ -229,7 +229,7 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
} else {
output=output*chan[0].vol*chan[0].envVol/16384;
}
oscBuf->data[oscBuf->needle++]=output>>1;
oscBuf->data[oscBuf->needle++]=((output>>depthScale)<<depthScale)>>1;
if (outStereo) {
buf[0][h]=((output*chan[0].panL)>>(depthScale+8))<<depthScale;
buf[1][h]=((output*chan[0].panR)>>(depthScale+8))<<depthScale;

View File

@ -5,7 +5,7 @@
MODIFIED Namco C140 sound emulator - MODIFIED VERSION
by cam900
MODIFICATION by tildearrow - adds muting function
MODIFICATION by tildearrow - adds muting function and fixes overflow
THIS IS NOT THE ORIGINAL VERSION - you can find the original one in
commit 72d04777c013988ed8cf6da27c62a9d784a59dff
@ -99,8 +99,8 @@ void c140_voice_tick(struct c140_t *c140, const unsigned char v, const int cycle
s1 = c140->mulaw[(s1 >> 8) & 0xff];
s2 = c140->mulaw[(s2 >> 8) & 0xff];
}
// interpolate
signed int sample = s1 + (((voice->frac) * (s2 - s1)) >> 16);
// interpolate (originally was >>16, but I had to reduce it to 15 to prevent overflow)
signed int sample = s1 + (((voice->frac >> 1) * (s2 - s1)) >> 15);
voice->lout = sample * voice->lvol;
voice->rout = sample * voice->rvol;
}

View File

@ -508,6 +508,17 @@ void DivPlatformYM2608Ext::tick(bool sysTick) {
rWrite(0x22,lfoValue);
}
if (opChan[i].std.panL.had) {
opChan[i].pan=opChan[i].std.panL.val&3;
if (parent->song.sharedExtStat) {
for (int j=0; j<4; j++) {
if (i==j) continue;
opChan[j].pan=opChan[i].pan;
}
}
rWrite(chanOffs[extChanOffs]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[i].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));
}
// param macros
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[i]];

View File

@ -504,6 +504,17 @@ void DivPlatformYM2610BExt::tick(bool sysTick) {
rWrite(0x22,lfoValue);
}
if (opChan[i].std.panL.had) {
opChan[i].pan=opChan[i].std.panL.val&3;
if (parent->song.sharedExtStat) {
for (int j=0; j<4; j++) {
if (i==j) continue;
opChan[j].pan=opChan[i].pan;
}
}
rWrite(chanOffs[extChanOffs]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[i].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));
}
// param macros
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[i]];
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[i]];

View File

@ -504,6 +504,17 @@ void DivPlatformYM2610Ext::tick(bool sysTick) {
rWrite(0x22,lfoValue);
}
if (opChan[i].std.panL.had) {
opChan[i].pan=opChan[i].std.panL.val&3;
if (parent->song.sharedExtStat) {
for (int j=0; j<4; j++) {
if (i==j) continue;
opChan[j].pan=opChan[i].pan;
}
}
rWrite(chanOffs[extChanOffs]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[i].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));
}
// param macros
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[i]];
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[i]];

View File

@ -449,7 +449,12 @@ void DivEngine::registerSystems() {
{0x30, {DIV_CMD_FM_HARD_RESET, "30xx: Toggle hard envelope reset on new notes"}},
};
EffectHandlerMap fmOPN2EffectHandlerMap(fmEffectHandlerMap);
EffectHandlerMap fmExtChEffectHandlerMap(fmEffectHandlerMap);
fmExtChEffectHandlerMap.insert({
{0x18, {DIV_CMD_FM_EXTCH, "18xx: Toggle extended channel 3 mode"}},
});
EffectHandlerMap fmOPN2EffectHandlerMap(fmExtChEffectHandlerMap);
fmOPN2EffectHandlerMap.insert({
{0x17, {DIV_CMD_SAMPLE_MODE, "17xx: Toggle PCM mode (LEGACY)"}},
{0xdf, {DIV_CMD_SAMPLE_DIR, "DFxx: Set sample playback direction (0: normal; 1: reverse)"}},
@ -522,7 +527,6 @@ void DivEngine::registerSystems() {
fmOPNPostEffectHandlerMap.insert({
{0x10, {DIV_CMD_FM_LFO, "10xy: Setup LFO (x: enable; y: speed)"}},
{0x18, {DIV_CMD_FM_EXTCH, "18xx: Toggle extended channel 3 mode"}},
{0x55, {DIV_CMD_FM_SSG, "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)", effectOpVal<4>, effectValAnd<15>}},
});
EffectHandlerMap fmOPN2PostEffectHandlerMap(fmOPNPostEffectHandlerMap);
@ -769,7 +773,7 @@ void DivEngine::registerSystems() {
{DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA},
{DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
fmEffectHandlerMap,
fmExtChEffectHandlerMap,
fmOPNAPostEffectHandlerMap
);
@ -1028,7 +1032,7 @@ void DivEngine::registerSystems() {
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY},
{},
fmEffectHandlerMap,
fmExtChEffectHandlerMap,
fmOPNPostEffectHandlerMap
);
@ -1040,7 +1044,7 @@ void DivEngine::registerSystems() {
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_NOISE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY},
{},
fmEffectHandlerMap,
fmExtChEffectHandlerMap,
fmOPNPostEffectHandlerMap
);
@ -1064,7 +1068,7 @@ void DivEngine::registerSystems() {
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB},
{DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA},
fmEffectHandlerMap,
fmExtChEffectHandlerMap,
fmOPNAPostEffectHandlerMap
);
@ -1076,7 +1080,7 @@ void DivEngine::registerSystems() {
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB},
{DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA},
fmEffectHandlerMap,
fmExtChEffectHandlerMap,
fmOPNAPostEffectHandlerMap
);
@ -1368,7 +1372,7 @@ void DivEngine::registerSystems() {
{DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB},
{DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
fmEffectHandlerMap,
fmExtChEffectHandlerMap,
fmOPNAPostEffectHandlerMap
);
@ -1380,7 +1384,7 @@ void DivEngine::registerSystems() {
{DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB},
{DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
fmEffectHandlerMap,
fmExtChEffectHandlerMap,
fmOPNAPostEffectHandlerMap
);
@ -1457,7 +1461,7 @@ void DivEngine::registerSystems() {
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB},
{DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
fmEffectHandlerMap,
fmExtChEffectHandlerMap,
fmOPNAPostEffectHandlerMap
);
@ -1469,7 +1473,7 @@ void DivEngine::registerSystems() {
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB},
{DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
fmEffectHandlerMap,
fmExtChEffectHandlerMap,
fmOPNAPostEffectHandlerMap
);

View File

@ -69,7 +69,7 @@ void _nfdThread(const NFDState state, std::atomic<bool>* ok, std::vector<String>
(*errorOutput)=true;
break;
default:
logE("NFD unknown return code %d!\n",ret);
logE("NFD unknown return code %d!\n",(int)ret);
break;
}
(*ok)=true;

View File

@ -20,8 +20,12 @@
#define _USE_MATH_DEFINES
#include "gui.h"
#include "../../extern/opn/ym3438.h"
#include "../../extern/opm/opm.h"
#include "../../extern/opl/opl3.h"
#include "../../extern/Nuked-OPLL/opll.h"
#include "../engine/platform/sound/ymfm/ymfm_opz.h"
#define FM_WRITE(addr,val) \
#define OPN_WRITE(addr,val) \
OPN2_Write((ym3438_t*)fmPreviewOPN,0,(addr)); \
do { \
OPN2_Clock((ym3438_t*)fmPreviewOPN,out); \
@ -35,7 +39,7 @@ const unsigned char dtTableFMP[8]={
7,6,5,0,1,2,3,4
};
void FurnaceGUI::renderFMPreview(const DivInstrumentFM& params, int pos) {
void FurnaceGUI::renderFMPreviewOPN(const DivInstrumentFM& params, int pos) {
if (fmPreviewOPN==NULL) {
fmPreviewOPN=new ym3438_t;
}
@ -57,19 +61,19 @@ void FurnaceGUI::renderFMPreview(const DivInstrumentFM& params, int pos) {
for (int i=0; i<4; i++) {
const DivInstrumentFM::Operator& op=params.op[i];
unsigned short baseAddr=i*4;
FM_WRITE(baseAddr+0x40,op.tl);
FM_WRITE(baseAddr+0x30,(op.mult&15)|(dtTableFMP[op.dt&7]<<4));
FM_WRITE(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
FM_WRITE(baseAddr+0x60,(op.dr&31)|(op.am<<7));
FM_WRITE(baseAddr+0x70,op.d2r&31);
FM_WRITE(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
FM_WRITE(baseAddr+0x90,op.ssgEnv&15);
OPN_WRITE(baseAddr+0x40,op.tl);
OPN_WRITE(baseAddr+0x30,(op.mult&15)|(dtTableFMP[op.dt&7]<<4));
OPN_WRITE(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
OPN_WRITE(baseAddr+0x60,(op.dr&31)|(op.am<<7));
OPN_WRITE(baseAddr+0x70,op.d2r&31);
OPN_WRITE(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
OPN_WRITE(baseAddr+0x90,op.ssgEnv&15);
}
FM_WRITE(0xb0,(params.alg&7)|((params.fb&7)<<3));
FM_WRITE(0xb4,0xc0|(params.fms&7)|((params.ams&3)<<4));
FM_WRITE(0xa4,mult0?0x1c:0x14); // frequency
FM_WRITE(0xa0,0);
FM_WRITE(0x28,0xf0); // key on
OPN_WRITE(0xb0,(params.alg&7)|((params.fb&7)<<3));
OPN_WRITE(0xb4,0xc0|(params.fms&7)|((params.ams&3)<<4));
OPN_WRITE(0xa4,mult0?0x1c:0x14); // frequency
OPN_WRITE(0xa0,0);
OPN_WRITE(0x28,0xf0); // key on
}
// render
@ -84,3 +88,37 @@ void FurnaceGUI::renderFMPreview(const DivInstrumentFM& params, int pos) {
fmPreview[i]=aOut;
}
}
void FurnaceGUI::renderFMPreviewOPM(const DivInstrumentFM& params, int pos) {
}
void FurnaceGUI::renderFMPreviewOPLL(const DivInstrumentFM& params, int pos) {
}
void FurnaceGUI::renderFMPreviewOPL(const DivInstrumentFM& params, int pos) {
}
void FurnaceGUI::renderFMPreviewOPZ(const DivInstrumentFM& params, int pos) {
}
void FurnaceGUI::renderFMPreview(const DivInstrument* ins, int pos) {
switch (ins->type) {
case DIV_INS_FM:
renderFMPreviewOPN(ins->fm,pos);
break;
case DIV_INS_OPM:
renderFMPreviewOPM(ins->fm,pos);
break;
case DIV_INS_OPLL:
renderFMPreviewOPLL(ins->fm,pos);
break;
case DIV_INS_OPL:
renderFMPreviewOPL(ins->fm,pos);
break;
case DIV_INS_OPZ:
renderFMPreviewOPZ(ins->fm,pos);
break;
default:
break;
}
}

View File

@ -5262,6 +5262,7 @@ bool FurnaceGUI::loop() {
}
}
if (!e->isExporting()) {
e->finishAudioFile();
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
@ -6872,6 +6873,10 @@ FurnaceGUI::FurnaceGUI():
fmPreviewOn(false),
fmPreviewPaused(false),
fmPreviewOPN(NULL),
fmPreviewOPM(NULL),
fmPreviewOPL(NULL),
fmPreviewOPLL(NULL),
fmPreviewOPZ(NULL),
editString(NULL),
pendingRawSampleDepth(8),
pendingRawSampleChannels(1),

View File

@ -1349,6 +1349,10 @@ class FurnaceGUI {
short fmPreview[FM_PREVIEW_SIZE];
bool updateFMPreview, fmPreviewOn, fmPreviewPaused;
void* fmPreviewOPN;
void* fmPreviewOPM;
void* fmPreviewOPL;
void* fmPreviewOPLL;
void* fmPreviewOPZ;
String* editString;
String pendingRawSample;
@ -2137,7 +2141,12 @@ class FurnaceGUI {
bool drawSysConf(int chan, DivSystem type, DivConfig& flags, bool modifyOnChange, bool fromMenu=false);
void kvsConfig(DivInstrument* ins);
void drawFMPreview(const ImVec2& size);
void renderFMPreview(const DivInstrumentFM& params, int pos=0);
void renderFMPreview(const DivInstrument* ins, int pos=0);
void renderFMPreviewOPN(const DivInstrumentFM& params, int pos=0);
void renderFMPreviewOPM(const DivInstrumentFM& params, int pos=0);
void renderFMPreviewOPLL(const DivInstrumentFM& params, int pos=0);
void renderFMPreviewOPL(const DivInstrumentFM& params, int pos=0);
void renderFMPreviewOPZ(const DivInstrumentFM& params, int pos=0);
// these ones offer ctrl-wheel fine value changes.
bool CWSliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format=NULL, ImGuiSliderFlags flags=0);

View File

@ -1284,7 +1284,7 @@ inline bool enBit30(const int val) {
void FurnaceGUI::kvsConfig(DivInstrument* ins) {
if (ins->type==DIV_INS_FM && fmPreviewOn) {
if (fmPreviewOn) {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("left click to restart\nmiddle click to pause\nright click to see algorithm");
}
@ -1299,10 +1299,10 @@ void FurnaceGUI::kvsConfig(DivInstrument* ins) {
ImGui::SetTooltip("left click to configure TL scaling\nright click to see FM preview");
}
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right) && ins->type==DIV_INS_FM) {
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
fmPreviewOn=!fmPreviewOn;
}
if (!fmPreviewOn || ins->type!=DIV_INS_FM) {
if (!fmPreviewOn) {
int opCount=4;
if (ins->type==DIV_INS_OPLL) opCount=2;
if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2;
@ -1568,6 +1568,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("Bottom");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
@ -1590,6 +1591,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("Attack");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
@ -1609,6 +1611,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("Hold");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
@ -1628,6 +1631,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("Decay");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
@ -1650,6 +1654,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("Release");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
@ -1671,6 +1676,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("Bottom");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
@ -1693,6 +1699,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("Speed");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
@ -1711,6 +1718,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
} rightClickable
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("Shape");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
@ -2281,7 +2289,7 @@ void FurnaceGUI::drawInsEdit() {
} else {
DivInstrument* ins=e->song.ins[curIns];
if (updateFMPreview) {
renderFMPreview(ins->fm);
renderFMPreview(ins);
updateFMPreview=false;
}
if (settings.insEditColorize) {
@ -2469,10 +2477,10 @@ void FurnaceGUI::drawInsEdit() {
P(CWSliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); rightClickable
P(CWSliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); rightClickable
ImGui::TableNextColumn();
if (ins->type==DIV_INS_FM && fmPreviewOn) {
if (fmPreviewOn) {
drawFMPreview(ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
if (!fmPreviewPaused) {
renderFMPreview(ins->fm,1);
renderFMPreview(ins,1);
WAKE_UP;
}
} else {
@ -2490,7 +2498,15 @@ void FurnaceGUI::drawInsEdit() {
P(CWSliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); rightClickable
P(CWSliderScalar(FM_NAME(FM_AMS2),ImGuiDataType_U8,&ins->fm.ams2,&_ZERO,&_THREE)); rightClickable
ImGui::TableNextColumn();
drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
if (fmPreviewOn) {
drawFMPreview(ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
if (!fmPreviewPaused) {
renderFMPreview(ins,1);
WAKE_UP;
}
} else {
drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
}
kvsConfig(ins);
if (ImGui::Button("Request from TX81Z")) {
@ -2524,7 +2540,15 @@ void FurnaceGUI::drawInsEdit() {
}
}
ImGui::TableNextColumn();
drawAlgorithm(ins->fm.alg&algMax,fourOp?FM_ALGS_4OP_OPL:FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
if (fmPreviewOn) {
drawFMPreview(ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
if (!fmPreviewPaused) {
renderFMPreview(ins,1);
WAKE_UP;
}
} else {
drawAlgorithm(ins->fm.alg&algMax,fourOp?FM_ALGS_4OP_OPL:FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
}
kvsConfig(ins);
break;
}
@ -2549,7 +2573,15 @@ void FurnaceGUI::drawInsEdit() {
}
ImGui::EndDisabled();
ImGui::TableNextColumn();
drawAlgorithm(0,FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,24.0*dpiScale));
if (fmPreviewOn) {
drawFMPreview(ImVec2(ImGui::GetContentRegionAvail().x,24.0*dpiScale));
if (!fmPreviewPaused) {
renderFMPreview(ins,1);
WAKE_UP;
}
} else {
drawAlgorithm(0,FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,24.0*dpiScale));
}
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);

View File

@ -293,6 +293,9 @@ void FurnaceGUI::drawSampleEdit() {
if (sample->samples>131070) {
SAMPLE_WARN(warnLength,"Amiga: maximum sample length is 131070");
}
if (dispatch!=NULL) {
MAX_RATE("Amiga",31250.0);
}
break;
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_SEGAPCM_COMPAT:

View File

@ -1093,7 +1093,7 @@ void FurnaceGUI::drawWaveEdit() {
MARK_MODIFIED;
});
}
if (ImGui::Button("Invert",buttonSize)) {
if (ImGui::Button("Invert",buttonSizeHalf)) {
e->lockEngine([this,wave]() {
for (int i=0; i<wave->len; i++) {
wave->data[i]=wave->max-wave->data[i];
@ -1101,6 +1101,18 @@ void FurnaceGUI::drawWaveEdit() {
MARK_MODIFIED;
});
}
ImGui::SameLine();
if (ImGui::Button("Reverse",buttonSizeHalf)) {
e->lockEngine([this,wave]() {
int origData[256];
memcpy(origData,wave->data,wave->len*sizeof(int));
for (int i=0; i<wave->len; i++) {
wave->data[i]=origData[wave->len-1-i];
}
MARK_MODIFIED;
});
}
if (ImGui::Button("Half",buttonSizeHalf)) {
int origData[256];

View File

@ -97,7 +97,11 @@ int writeLog(int level, const char* msg, fmt::printf_args args) {
time_t thisMakesNoSense=time(NULL);
int pos=(logPosition.fetch_add(1))&TA_LOG_MASK;
#if FMT_VERSION >= 100100
logEntries[pos].text.assign(fmt::vsprintf(fmt::basic_string_view<char>(msg),args));
#else
logEntries[pos].text.assign(fmt::vsprintf(msg,args));
#endif
// why do I have to pass a pointer
// can't I just pass the time_t directly?!
#ifdef _WIN32