diff --git a/CMakeLists.txt b/CMakeLists.txt index a5e91fb0..8fb2aa91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -455,6 +455,8 @@ src/engine/platform/sound/c64_fp/WaveformCalculator.cpp src/engine/platform/sound/c64_fp/WaveformGenerator.cpp src/engine/platform/sound/c64_fp/resample/SincResampler.cpp +src/engine/platform/sound/c64_d/dsid.c + src/engine/platform/sound/tia/AudioChannel.cpp src/engine/platform/sound/tia/Audio.cpp @@ -860,7 +862,7 @@ endif() string(REPLACE ";" " " WARNING_FLAGS_STRING "${WARNING_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS_STRING}") if (WARNINGS_ARE_ERRORS) - message(WARNING + message(STATUS "Treating all warnings in furnace's C++ code as errors! " "Please report any errors you encounter on the bug tracker." ) diff --git a/doc/2-interface/README.md b/doc/2-interface/README.md index 2ff1ebfc..6de22e92 100644 --- a/doc/2-interface/README.md +++ b/doc/2-interface/README.md @@ -36,6 +36,7 @@ advanced topics: other topics: +- [settings](../2-interface/settings.md) - [UI components](components.md) - [global keyboard shortcuts](keyboard.md) - [basic mode](basic-mode.md) diff --git a/doc/2-interface/menu-bar.md b/doc/2-interface/menu-bar.md index f023ba66..822f3889 100644 --- a/doc/2-interface/menu-bar.md +++ b/doc/2-interface/menu-bar.md @@ -199,7 +199,7 @@ it's not really useful, unless you're a developer and want to use a command stre - **basic mode**: toggles [Basic Mode](basic-mode.md). - **visualizer**: toggles pattern view particle effects when the song plays. - **reset layout**: resets the workspace to its defaults. -- **settings...**: opens the Settings window. +- **settings...**: opens the Settings window. these are detailed in [settings.md]. # window diff --git a/doc/2-interface/settings.md b/doc/2-interface/settings.md new file mode 100644 index 00000000..06c21f1c --- /dev/null +++ b/doc/2-interface/settings.md @@ -0,0 +1,387 @@ +# settings + +settings are saved when clicking the **OK** button at the bottom of the dialog. + + + +# General + +- **Workspace layout** + - **Import**: reads a .ini layout file. + - **Export**: writes current layout to a .ini file. + - **Reset**: resets layout to default. + +- **Initial system**: the system of chips loaded on starting Furnace. + - **Current system**: sets current chips as default. + - **Randomize**: set default to a random system. + - this will not choose a random system at each start. + - **Reset to defaults**: sets default to "Sega Genesis/Mega Drive". + - **Name**: name for the default system. may be set to any text. + - system configuration: same as in the [chip manager](../8-advanced/chip-manager.md) and [mixer](../8-advanced/mixer.md). + +- **Play intro on start-up:** + - **No**: skips intro entirely. + - **Short**: shows silent title screen briefly. + - **Full (short when loading song)**: shows animated musical intro unless started with a song (command line, double-clicking a .fur file, etc.) + - **Full (always)**: always shows animated musical intro. +- **When creating new song**: + - **Display system preset selector** + - **Start with initial system** + +- **Double-click time (seconds)**: maximum time between mouse clicks to recognize them as a double-click. +- **Toggle channel solo on:** select which interactions with a channel header will toggle solo for that channel. +- **Push value when overwriting instead of clearing it**: in the order list and pattern editors, typing into an already-filled value will shift digits instead of starting fresh. + - if off: moving the cursor onto the value `A5` and typing a "B" results in `0B`. + - if on: with the cursor on the value `A5` and typing a "B" results in `5B`. +- **Move cursor up on backspace-delete** +- **Move cursor by edit step on delete** +- **Change current instrument when changing instrument column (absorb)** +- **Delete effect value when deleting effect** +- **Change order when scrolling outside of pattern bounds**: + - if off, the pattern edit cursor will stay locked within the current order. + - if on, moving the cursor past the edge of the previous or next order will move to that order. +- **Move cursor by edit step on insert (push)** +- **Move cursor to end of clipboard content when pasting** +- **Don't scroll when moving cursor** +- **Double click selects entire column** +- **Allow docking editors** +- **Don't raise pattern editor on click** +- **Focus pattern editor when selecting instrument** +- **Restart song when changing chip properties** +- **Use system file picker**: use native OS file dialog instead of Furnace's. +- **Only allow window movement when clicking on title bar** +- **Enable event delay** + - may cause issues with high-polling-rate mice when previewing notes. +- **Power-saving mode** + - saves power by lowering the frame rate to 2fps when idle. + - may cause issues under Mesa drivers! +- **Disable threaded input (restart after changing!)** + - threaded input processes key presses for note preview on a separate thread (on supported platforms), which reduces latency. + - however, crashes have been reported when threaded input is on. enable this option if that is the case. +- **Remember window position** + - remembers the window's last position on start-up. +- **New instruments are blank** +- **Save unused patterns** +- **Compress when saving** + - use zlib to compress saved songs. +- **Cursor follows current order when moving it** + - applies when playback is stopped. +- **Audio export loop/fade out time:** + - **Set to these values on start-up:** + - **Loops**: number of additional times to play through `0Bxx` song loop. + - **Fade out (seconds)**: length of fade out after final loop. + - **Remember last values** +- **Note preview behavior:** + - **Never** + - **When cursor is in Note column** + - **When cursor is in Note column or not in edit mode** + - **Always** +- **Wrap pattern cursor horizontally:** + - **No** + - **Yes** + - **Yes, and move to next/prev row** +- **Wrap pattern cursor vertically:** + - **No** + - **Yes** + - **Yes, and move to next/prev pattern** +- **Cursor movement keys behavior:** + - **Move by one** + - **Move by Edit Step** +- **Effect input cursor behavior:** + - **Move down** + - **Move to effect value (otherwise move down)** + - **Move to effect value/next effect and wrap around** +- **Allow dragging selection:** + - **No** + - **Yes** + - **Yes (while holding Ctrl only)** + + + +# Audio/MIDI + +- **Backend**: select SDL or JACK for audio output. + - only appears on Linux, or MacOS compiled with JACK support +- **Device**: audio device for playback. +- **Sample rate** +- **Outputs**: select number of audio outputs created, up to 16. + - only appears when Backend is JACK. +- **Channels**: number of output channels to use. +- **Buffer size**: size of buffer in both samples and milliseconds. +- **Quality**: selects quality of resampling. low quality reduces CPU load. +- **Metronome volume** +- **Low-latency mode (experimental!)**: reduces latency by running the engine faster than the tick rate. useful for live playback/jam mode. + - _warning:_ experimental! may produce glitches. only enable if your buffer size is small (10ms or less). +- **Force mono audio** +- **Software clipping**: clips output to nominal range (-1.0 to 1.0) before passing it to the audio device. + - this avoids activating Windows' built-in limiter. +- **want:** displays requested audio configuration. +- **got:** displays actual audio configuration returned by audio backend. + +- **MIDI input** +- **MIDI output** +- **MIDI input settings** + - **Note input** + - **Velocity input** + - **Map MIDI channels to direct channels** + - **Map Yamaha FM voice data to instruments** + - **Program change is instrument selection** + - **Value input style**: + - **Disabled/custom** + - **Two octaves (0 is C-4, F is D#5)** + - **Raw (note number is value)** + - **Two octaves alternate (lower keys are 0-9, upper keys are A-F)** + - **Use dual control change (one for each nibble)** + - **CC of upper nibble** + - **CC of lower nibble** + - **Use 14-bit control change** + - **MSB CC** + - **LSB CC** + - **Use single control change** + - **Control** + - **Per-column control change** + - **Instrument**\ + **Volume**\ + **Effect `x` type**\ + **Effect `x` value** + - **Disabled/custom** + - **Use dual control change (one for each nibble)** + - **CC of upper nibble** + - **CC of lower nibble** + - **Use 14-bit control change** + - **MSB CC** + - **LSB CC** + - **Use single control change (imprecise)** + - **Control** + - **Volume curve** + - **Actions:** + - **`+`** button: adds a new action. + - window-with-arrow button: new action with learning! press a button or move a slider/knob/something on your device. + - each action has the following: + - **Type** + - **Channel** + - **Note/Control** + - **Velocity/Value** + - **Action** + - **Learn** + - **Remove** + +- **MIDI output settings** + - **Output mode:** + - **Off (use for TX81Z)** + - **Melodic** + - **Send Program Change** + - **Send MIDI clock** + - **Send MIDI timecode** + - **Timecode frame rate:** + - **Closest to Tick Rate** + - **Film (24fps)** + - **PAL (25fps)** + - **NTSC drop (29.97fps)** + - **NTSC non-drop (30fps)** + +# Emulation +- **Arcade/YM2151 core** + - **ymfm** + - **Nuked-OPM** +- **Genesis/YM2612 core** + - **Nuked-OPN2** + - **ymfm** +- **SN76489 core** + - **MAME** + - **Nuked-PSG Mod** +- **NES core** + - **puNES** + - **NSFplay** +- **FDS core** + - **puNES** + - **NSFplay** +- **SID core** + - **reSID** + - **reSIDfp** +- **POKEY core** + - **Atari800 (mzpokeysnd)** + - **ASAP (C++ port)** +- **OPN/OPNA/OPNB cores** + - **ymfm only** + - **Nuked-OPN2 (FM) + ymfm (SSG/ADPCM)** + +- **PC Speaker strategy:** + - **evdev SND_TONE** + - **KIOCSOUND on /dev/tty1** + - **/dev/port** + - **KIOCSOUND on standard output** + - **outb()** + +- **Sample ROMs:** + - **OPL4 YRW801 path** + - **MultiPCM TG100 path** + - **MultiPCM MU5 path** + + + +# Appearance + +- **Render driver** +- **Automatic UI scaling factor**: automatically match the OS's UI scaling. +- **UI scaling factor**: only if "Automatic UI scaling factor" is off. +- **Main font**: if "Custom...", a file path selector will appear beneath. +- **Size** +- **Pattern font**: if "Custom...", a file path selector will appear beneath. +- **Size** +- **Icon size** +- **Display Japanese characters**\ + **Display Chinese (Simplified) characters**\ + **Display Chinese (Traditional) characters**\ + **Display Korean characters** + - only toggle these options if you have enough graphics memory. + - these are a temporary solution until dynamic font atlas is implemented in Dear ImGui. + +- **Number of recent files** + +- **Pattern view labels:** +- **Note off (3-char)**: default is `OFF` +- **Note release (3-char)**: default is `===`. +- **Macro release (3-char)**: default is `REL`. +- **Empty field (3-char)**: default is `...`. +- **Empty field (2-char)**: default is `..`. + +- **Orders row number format:** + - **Decimal** + - **Hexadecimal** +- **Pattern row number format:** + - **Decimal** + - **Hexadecimal** +- **FM parameter names:** + - **Friendly** + - **Technical** + - **Technical (alternate)** + +- **Title bar:** + - **Furnace** + - **Song Name - Furnace** + - **file_name.fur - Furnace** + - **/path/to/file.fur - Furnace** +- **Display system name on title bar** +- **Display chip names instead of "multi-system" in title bar** +- **Status bar:** + - **Cursor details** + - **File path** + - **Cursor details or file path** + - **Nothing** +- **Play/edit controls layout:** + - **Classic** + - **Compact** + - **Compact (vertical)** + - **Split** +- **Position of buttons in Orders:** + - **Top** + - **Left** + - **Right** +- **FM parameter editor layout:** + - **Modern** + - **Compact (2x2, classic)** + - **Compact (1x4)** + - **Compact (4x1)** + - **Alternate (2x2)** + - **Alternate (1x4)** + - **Alternate (4x1)** +- **Position of Sustain in FM editor:** + - **Between Decay and Sustain Rate** + - **After Release Rate** +- **Macro editor layout:** + - **Unified** + - **Mobile** + - **Grid** + - **Single (with list)** + - **Single (combo box)** + +- **Namco 163 chip name** + +- **Channel colors:** + - **Single** + - **Channel type** + - **Instrument type** +- **Channel name colors:** + - **Single** + - **Channel type** + - **Instrument type** +- **Channel style:** + - **Classic** + - **Line** + - **Round** + - **Split button** + - **Square border** + - **Round border** +- **Channel volume bar:** + - **None** + - **Simple** + - **Stereo** + - **Real** + - **Real (stereo)** +- **Channel feedback style:** + - **Off** + - **Note** + - **Volume** + - **Active** +- **Channel font:** + - **Regular** + - **Monospace** +- **Center channel name** + +- **Colorize instrument editor using instrument type** +- **Use separate colors for carriers/modulators in FM editor** +- **Unified instrument/wavetable/sample list** +- **Horizontal instrument list** +- **Use standard OPL waveform names** +- **Overflow pattern highlights** +- **Display previous/next pattern** +- **Use German notation**: display `B` notes as `H`, and `A#` notes as `B`. +- **Single-digit effects for 00-0F** +- **Center pattern view**: centers pattern horizontally in view. +- **Unsigned FM detune values** +- **Highlight channel at cursor in Orders** +- **About screen party time** + - _warning:_ may cause epileptic seizures. + +- **Use compact wave editor** +- **Use classic macro editor vertical slider** +- **Rounded window corners** +- **Rounded buttons** +- **Rounded menu corners** +- **Borders around widgets** +- **Disable fade-in during start-up** + +- **Oscilloscope settings:** + - **Rounded corners** + - **Fill entire window** + - **Waveform goes out of bounds** + - **Border** + +- **Pattern view spacing after:** + - **Note** + - **Instrument** + - **Volume** + - **Effect** + - **Effect value** +- **Color scheme** + - **Import** + - **Export** + - **Reset defaults** + - **General** + - **Color scheme type:** + - **Dark** + - **Light** + - **Frame shading** + - several more categories... + + + +# Keyboard + +- **Import** +- **Export** +- **Reset defaults** +- several categories of keybinds... + - click on a keybind then enter a key or key combination to change it + - right-click to clear the keybind diff --git a/doc/7-systems/vera.md b/doc/7-systems/vera.md index 8f50e0e7..e89744a9 100644 --- a/doc/7-systems/vera.md +++ b/doc/7-systems/vera.md @@ -13,3 +13,5 @@ currently Furnace does not support the PCM channel's stereo mode, though (except - `2`: triangle - `3`: noise - `22xx`: **set duty cycle.** range is `0` to `3F`. +- `EExx`: **ZSM synchronization event.** + - Where `xx` is the event payload. This has no effect in how the music is played in Furnace, but the ZSMKit library for the Commander X16 interprets these events inside ZSM files and optionally triggers a callback routine. This can be used, for instance, to cause game code to respond to beats or at certain points in the music. diff --git a/papers/zsm-format.md b/papers/zsm-format.md index 5957e3c6..e0fd2c02 100644 --- a/papers/zsm-format.md +++ b/papers/zsm-format.md @@ -99,7 +99,7 @@ Any offset values contained in the PCM data header block are relative to the beg ### PCM Sample Data -This is blob of PCM data with no internal formatting. Offsets into this blob are provided via the PCM header. The end of this blob will be the end of the ZSM file. +This is a blob of PCM data with no internal formatting. Offsets into this blob are provided via the PCM header. The end of this blob will be the end of the ZSM file. ## EXTCMD Channel Scifications @@ -149,7 +149,7 @@ Players implementing this channel should implement detection routines during ini An expansion HW write will contain the following data: -Chip ID|Nuber of writes (`N`)| `N` tuples of data +Chip ID|Number of writes (`N`)| `N` tuples of data --|--|-- one byte|one byte|N * tuple_size bytes @@ -162,7 +162,14 @@ There are currently no supported expansion HW IDs assigned. The purpose of this channel is to provide for music synchronization cues that applications may use to perform operations in sync with the music (such as when the Goombas jump in New Super Mario Bros in time with the BOP! BOP! notes in the music). It is intended for the reference player to provide a sync channel callback, passing the data bytes to the callback function, and then to proceed with playback. -The data structure within this channel is not yet defined. It is our intention to work with the community in order to collaborate on a useful structure. +The synchronization format currently defines this one event type: + +Event Type|Description|Message Format +--|--|-- +`0x00`|Generic sync message|`xx` (any value from `0x00`-`0xff`) + +An example of an EXTCMD containing one sync event might look as follows: `0x40 0x82 0x00 0x05` + #### 3: Custom diff --git a/src/audio/sdlAudio.cpp b/src/audio/sdlAudio.cpp index 6d0ceff0..5d07921e 100644 --- a/src/audio/sdlAudio.cpp +++ b/src/audio/sdlAudio.cpp @@ -127,14 +127,20 @@ bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) { ac.callback=taSDLProcess; ac.userdata=this; - ai=SDL_OpenAudioDevice(request.deviceName.empty()?NULL:request.deviceName.c_str(),0,&ac,&ar,SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + ai=SDL_OpenAudioDevice(request.deviceName.empty()?NULL:request.deviceName.c_str(),0,&ac,&ar,0); if (ai==0) { logE("could not open audio device: %s",SDL_GetError()); return false; } + const char* backendName=SDL_GetCurrentAudioDriver(); + desc.deviceName=request.deviceName; - desc.name=""; + if (backendName==NULL) { + desc.name=""; + } else { + desc.name=backendName; + } desc.rate=ar.freq; desc.inChans=0; desc.outChans=ar.channels; diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index c19e7d95..c458cf9c 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -236,6 +236,8 @@ enum DivDispatchCmds { DIV_CMD_NES_LINEAR_LENGTH, + DIV_CMD_EXTERNAL, // (value) + DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol DIV_CMD_MAX diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index c30d7f23..6a532548 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -172,6 +172,7 @@ void DivDispatchContainer::fillBuf(size_t runtotal, size_t offset, size_t size) if (bbIn[i]==NULL) continue; if (bb[i]==NULL) continue; for (size_t j=0; jsetFP(eng->getConfInt("c64Core",1)==1); + ((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64Core",0)); ((DivPlatformC64*)dispatch)->setChipModel(true); break; case DIV_SYSTEM_C64_8580: dispatch=new DivPlatformC64; - ((DivPlatformC64*)dispatch)->setFP(eng->getConfInt("c64Core",1)==1); + ((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64Core",0)); ((DivPlatformC64*)dispatch)->setChipModel(false); break; case DIV_SYSTEM_YM2151: diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 1aa8188e..c7b5ac9e 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -4586,6 +4586,15 @@ bool DivEngine::initAudioBackend() { } } +#ifdef HAVE_SDL2 + if (audioEngine==DIV_AUDIO_SDL) { + String audioDriver=getConfString("sdlAudioDriver",""); + if (!audioDriver.empty()) { + SDL_SetHint("SDL_HINT_AUDIODRIVER",audioDriver.c_str()); + } + } +#endif + lowQuality=getConfInt("audioQuality",0); forceMono=getConfInt("forceMono",0); clampSamples=getConfInt("clampSamples",0); @@ -4594,7 +4603,7 @@ bool DivEngine::initAudioBackend() { midiOutClock=getConfInt("midiOutClock",0); midiOutTime=getConfInt("midiOutTime",0); midiOutTimeRate=getConfInt("midiOutTimeRate",0); - midiOutProgramChange = getConfInt("midiOutProgramChange",0); + midiOutProgramChange=getConfInt("midiOutProgramChange",0); midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE); if (metroVol<0.0f) metroVol=0.0f; if (metroVol>2.0f) metroVol=2.0f; diff --git a/src/engine/engine.h b/src/engine/engine.h index aceee308..e4b3e56a 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -570,6 +570,7 @@ class DivEngine { float oscSize; int oscReadPos, oscWritePos; int tickMult; + int lastNBIns, lastNBOuts, lastNBSize; std::atomic processTime; void runExportThread(); @@ -1252,6 +1253,9 @@ class DivEngine { oscReadPos(0), oscWritePos(0), tickMult(1), + lastNBIns(0), + lastNBOuts(0), + lastNBSize(0), processTime(0), yrw801ROM(NULL), tg100ROM(NULL), diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 6d03ba2f..b41a52d5 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -65,34 +65,45 @@ const char** DivPlatformC64::getRegisterSheet() { } void DivPlatformC64::acquire(short** buf, size_t len) { - int dcOff=isFP?0:sid.get_dc(0); + int dcOff=(sidCore)?0:sid->get_dc(0); for (size_t i=0; iwrite(w.addr,w.val); } else { - sid.write(w.addr,w.val); - }; + sid->write(w.addr,w.val); + } regPool[w.addr&0x1f]=w.val; writes.pop(); } - if (isFP) { - sid_fp.clock(4,&buf[0][i]); + if (sidCore==2) { + double o=dSID_render(sid_d); + buf[0][i]=32767*CLAMP(o,-1.0,1.0); if (++writeOscBuf>=4) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp.lastChanOut[0]-dcOff)>>5; - oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp.lastChanOut[1]-dcOff)>>5; - oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp.lastChanOut[2]-dcOff)>>5; + oscBuf[0]->data[oscBuf[0]->needle++]=sid_d->lastOut[0]; + oscBuf[1]->data[oscBuf[1]->needle++]=sid_d->lastOut[1]; + oscBuf[2]->data[oscBuf[2]->needle++]=sid_d->lastOut[2]; + } + } else if (sidCore==1) { + sid_fp->clock(4,&buf[0][i]); + if (++writeOscBuf>=4) { + writeOscBuf=0; + oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp->lastChanOut[0]-dcOff)>>5; + oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp->lastChanOut[1]-dcOff)>>5; + oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp->lastChanOut[2]-dcOff)>>5; } } else { - sid.clock(); - buf[0][i]=sid.output(); + sid->clock(); + buf[0][i]=sid->output(); if (++writeOscBuf>=16) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>5; - oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>5; - oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>5; + oscBuf[0]->data[oscBuf[0]->needle++]=(sid->last_chan_out[0]-dcOff)>>5; + oscBuf[1]->data[oscBuf[1]->needle++]=(sid->last_chan_out[1]-dcOff)>>5; + oscBuf[2]->data[oscBuf[2]->needle++]=(sid->last_chan_out[2]-dcOff)>>5; } } } @@ -366,7 +377,7 @@ int DivPlatformC64::dispatch(DivCommand c) { break; case DIV_CMD_C64_CUTOFF: if (c.value>100) c.value=100; - filtCut=c.value*2047/100; + filtCut=(c.value+2)*2047/102; updateFilter(); break; case DIV_CMD_C64_FINE_CUTOFF: @@ -452,10 +463,17 @@ int DivPlatformC64::dispatch(DivCommand c) { void DivPlatformC64::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - if (isFP) { - sid_fp.mute(ch,mute); + if (sidCore==2) { + dSID_setMuteMask( + sid_d, + (isMuted[0]?0:1)| + (isMuted[1]?0:2)| + (isMuted[2]?0:4) + ); + } else if (sidCore==1) { + sid_fp->mute(ch,mute); } else { - sid.set_is_muted(ch,mute); + sid->set_is_muted(ch,mute); } } @@ -514,7 +532,7 @@ bool DivPlatformC64::getWantPreNote() { } float DivPlatformC64::getPostAmp() { - return isFP?3.0f:1.0f; + return (sidCore==1)?3.0f:1.0f; } void DivPlatformC64::reset() { @@ -524,11 +542,20 @@ void DivPlatformC64::reset() { chan[i].std.setEngine(parent); } - if (isFP) { - sid_fp.reset(); - sid_fp.clockSilent(16000); + if (sidCore==2) { + dSID_init(sid_d,chipClock,rate,sidIs6581?6581:8580,needInitTables); + dSID_setMuteMask( + sid_d, + (isMuted[0]?0:1)| + (isMuted[1]?0:2)| + (isMuted[2]?0:4) + ); + needInitTables=false; + } else if (sidCore==1) { + sid_fp->reset(); + sid_fp->clockSilent(16000); } else { - sid.reset(); + sid->reset(); } memset(regPool,0,32); @@ -554,23 +581,11 @@ void DivPlatformC64::poke(std::vector& wlist) { } void DivPlatformC64::setChipModel(bool is6581) { - if (is6581) { - if (isFP) { - sid_fp.setChipModel(reSIDfp::MOS6581); - } else { - sid.set_chip_model(MOS6581); - } - } else { - if (isFP) { - sid_fp.setChipModel(reSIDfp::MOS8580); - } else { - sid.set_chip_model(MOS8580); - } - } + sidIs6581=is6581; } -void DivPlatformC64::setFP(bool fp) { - isFP=fp; +void DivPlatformC64::setCore(unsigned char which) { + sidCore=which; } void DivPlatformC64::setFlags(const DivConfig& flags) { @@ -591,9 +606,9 @@ void DivPlatformC64::setFlags(const DivConfig& flags) { for (int i=0; i<3; i++) { oscBuf[i]->rate=rate/16; } - if (isFP) { + if (sidCore>0) { rate/=4; - sid_fp.setSamplingParameters(chipClock,reSIDfp::DECIMATE,rate,0); + if (sidCore==1) sid_fp->setSamplingParameters(chipClock,reSIDfp::DECIMATE,rate,0); } keyPriority=flags.getBool("keyPriority",true); testAD=((flags.getInt("testAttack",0)&15)<<4)|(flags.getInt("testDecay",0)&15); @@ -604,11 +619,45 @@ int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, const DivConfi parent=p; dumpWrites=false; skipRegisterWrites=false; + needInitTables=true; writeOscBuf=0; for (int i=0; i<3; i++) { isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } + + if (sidCore==2) { + sid=NULL; + sid_fp=NULL; + sid_d=new struct SID_chip; + } else if (sidCore==1) { + sid=NULL; + sid_fp=new reSIDfp::SID; + sid_d=NULL; + } else { + sid=new SID; + sid_fp=NULL; + sid_d=NULL; + } + + if (sidIs6581) { + if (sidCore==2) { + // do nothing + } else if (sidCore==1) { + sid_fp->setChipModel(reSIDfp::MOS6581); + } else { + sid->set_chip_model(MOS6581); + } + } else { + if (sidCore==2) { + // do nothing + } else if (sidCore==1) { + sid_fp->setChipModel(reSIDfp::MOS8580); + } else { + sid->set_chip_model(MOS8580); + } + } + setFlags(flags); reset(); @@ -620,6 +669,9 @@ void DivPlatformC64::quit() { for (int i=0; i<3; i++) { delete oscBuf[i]; } + if (sid!=NULL) delete sid; + if (sid_fp!=NULL) delete sid_fp; + if (sid_d!=NULL) delete sid_d; } DivPlatformC64::~DivPlatformC64() { diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h index b9b30b6e..26b39c80 100644 --- a/src/engine/platform/c64.h +++ b/src/engine/platform/c64.h @@ -24,6 +24,7 @@ #include #include "sound/c64/sid.h" #include "sound/c64_fp/SID.h" +#include "sound/c64_d/dsid.h" class DivPlatformC64: public DivDispatch { struct Channel: public SharedChannel { @@ -64,15 +65,16 @@ class DivPlatformC64: public DivDispatch { unsigned char filtControl, filtRes, vol; unsigned char writeOscBuf; + unsigned char sidCore; int filtCut, resetTime; - bool isFP; - bool keyPriority; + bool keyPriority, sidIs6581, needInitTables; unsigned char chanOrder[3]; unsigned char testAD, testSR; - SID sid; - reSIDfp::SID sid_fp; + SID* sid; + reSIDfp::SID* sid_fp; + struct SID_chip* sid_d; unsigned char regPool[32]; friend void putDispatchChip(void*,int); @@ -105,7 +107,7 @@ class DivPlatformC64: public DivDispatch { const char** getRegisterSheet(); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void setChipModel(bool is6581); - void setFP(bool fp); + void setCore(unsigned char which); void quit(); ~DivPlatformC64(); }; diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 683f9721..9b565cdc 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -322,7 +322,7 @@ void DivPlatformGB::tick(bool sysTick) { 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)|((chan[i].soundLen<63)<<6)); + rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||(chan[i].keyOff && i!=2))?0x80:0x00)|((chan[i].soundLen<63)<<6)); } if (enoughAlready) { // more compat garbage rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); @@ -466,7 +466,9 @@ int DivPlatformGB::dispatch(DivCommand c) { if (c.chan!=2) break; chan[c.chan].wave=c.value; ws.changeWave1(chan[c.chan].wave); - chan[c.chan].keyOn=true; + if (chan[c.chan].active) { + chan[c.chan].keyOn=true; + } break; case DIV_CMD_NOTE_PORTA: { int destFreq=NOTE_PERIODIC(c.value2); diff --git a/src/engine/platform/sound/c64_d/LICENSE b/src/engine/platform/sound/c64_d/LICENSE new file mode 100644 index 00000000..2127f8a3 --- /dev/null +++ b/src/engine/platform/sound/c64_d/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021 DefleMask Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/engine/platform/sound/c64_d/README.md b/src/engine/platform/sound/c64_d/README.md new file mode 100644 index 00000000..63b7fa53 --- /dev/null +++ b/src/engine/platform/sound/c64_d/README.md @@ -0,0 +1,11 @@ +dSID +=== + +This is the SID core used in DefleMask. + +The project started as a very careful port from jsSID, comparing the wave +output from both, ensuring they were exactly the same. + +## License + +MIT License diff --git a/src/engine/platform/sound/c64_d/dsid.c b/src/engine/platform/sound/c64_d/dsid.c new file mode 100644 index 00000000..d01dc7ad --- /dev/null +++ b/src/engine/platform/sound/c64_d/dsid.c @@ -0,0 +1,367 @@ +#include "dsid.h" +#include +#include // INFINITY +#include +#include // memset, memcpy + +#define SID_OUT_SCALE (0x10000 * 3 * 16) + +// CONTROL +#define GAT 0x01 +#define SYN 0x02 +#define RNG 0x04 +#define TST 0x08 +#define TRI 0x10 +#define SAW 0x20 +#define PUL 0x40 +#define NOI 0x80 + +#define _HZ 0x10 +#define DECSUS 0x40 +#define ATK 0x80 + +// filter mode (high) +#define LP 0x10 +#define BP 0x20 +#define HP 0x40 +#define OFF3 0x80 + +#define waveforms_add_sample(_id,_s) \ + sid->lastOut[_id]=(_s); + +const int Aexp[256] = { + 1, 30, 30, 30, 30, 30, 30, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + +double cmbWF(int chn, int *wfa, int index, int differ6581, struct SID_globals *g) { + if (differ6581 && g->model == 6581) + index &= 0x7FF; + + return wfa[index]; +} + +void cCmbWF(int *wfa, double bitmul, double bstr, double trh) { + for (int i = 0; i < 4096; i++) { + wfa[i] = 0; + for (int j = 0; j < 12; j++) { + double blvl = 0; + for (int k = 0; k < 12; k++) { + blvl += (bitmul / pow(bstr, abs(k - j))) * (((i >> k) & 1) - 0.5); + } + wfa[i] += (blvl >= trh) ? pow(2, j) : 0; + } + wfa[i] *= 12; + } +} + +void dSID_init(struct SID_chip* sid, double clockRate, double samplingRate, int model, unsigned char init_wf) { + if (model == 6581) { + sid->g.model = 6581; + } else { + sid->g.model = 8580; + } + + memset(sid->M,0,MemLen); + memset(sid->SIDct, 0, sizeof(sid->SIDct)); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + sid->SIDct[i].ch[j].Ast = _HZ; + sid->SIDct[i].ch[j].nLFSR = 0x7FFFF8; + sid->SIDct[i].ch[j].prevwfout = 0; + } + sid->SIDct[i].ch[0].FSW = 1; + sid->SIDct[i].ch[1].FSW = 2; + sid->SIDct[i].ch[2].FSW = 4; + } + + sid->g.ctfr = -2.0 * 3.14 * (12500.0 / 256.0) / samplingRate, + sid->g.ctf_ratio_6581 = -2.0 * 3.14 * (samplingRate / 44100.0) * (20000.0 / 256.0) / samplingRate; + sid->g.ckr = clockRate / samplingRate; + + const double bAprd[16] = {9, 32 * 1, 63 * 1, 95 * 1, 149 * 1, 220 * 1, + 267 * 1, 313 * 1, 392 * 1, 977 * 1, 1954 * 1, 3126 * 1, + 3907 * 1, 11720 * 1, 19532 * 1, 31251 * 1}; + const int bAstp[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + memcpy(&sid->g.Aprd, &bAprd, sizeof(bAprd)); + memcpy(&sid->g.Astp, &bAstp, sizeof(bAstp)); + if (init_wf) { + cCmbWF(sid->g.trsaw, 0.8, 2.4, 0.64); + cCmbWF(sid->g.pusaw, 1.4, 1.9, 0.68); + cCmbWF(sid->g.Pulsetrsaw, 0.8, 2.5, 0.64); + + for (int i = 0; i < 2048; i++) { + double ctf = (double) i / 8.0 + 0.2; + if (model == 8580) { + ctf = 1 - exp(ctf * sid->g.ctfr); + } else { + if (ctf < 24) { + ctf = 2.0 * sin(771.78 / samplingRate); + } else { + ctf = (44100.0 / samplingRate) - 1.263 * (44100.0 / samplingRate) * exp(ctf * sid->g.ctf_ratio_6581); + } + } + sid->g.ctf_table[i] = ctf; + } + } + + double prd0 = sid->g.ckr > 9 ? sid->g.ckr : 9; + sid->g.Aprd[0] = prd0; + sid->g.Astp[0] = ceil(prd0 / 9); +} + +double dSID_render(struct SID_chip* sid) { + double flin = 0, output = 0; + double wfout = 0; + for (int chn = 0; chn < 3; chn++) { + struct SIDVOICE *voic = &((struct SIDMEM *) (sid->M))->v[chn]; + double pgt = (sid->SIDct->ch[chn].Ast & GAT); + uint8_t ctrl = voic->control; + uint8_t wf = ctrl & 0xF0; + uint8_t test = ctrl & TST; + uint8_t SR = voic->susres; + double tmp = 0; + if (pgt != (ctrl & GAT)) { + if (pgt) { + sid->SIDct->ch[chn].Ast &= 0xFF - (GAT | ATK | DECSUS); + } else { + sid->SIDct->ch[chn].Ast = (GAT | ATK | DECSUS); + if ((SR & 0xF) > (sid->SIDct->ch[chn].pSR & 0xF)) + tmp = 1; + } + } + sid->SIDct->ch[chn].pSR = SR; + sid->SIDct->ch[chn].rcnt += sid->g.ckr; + if (sid->SIDct->ch[chn].rcnt >= 0x8000) + sid->SIDct->ch[chn].rcnt -= 0x8000; + + static double step; + double prd; + + if (sid->SIDct->ch[chn].Ast & ATK) { + step = voic->attack; + prd = sid->g.Aprd[(int) step]; + } else if (sid->SIDct->ch[chn].Ast & DECSUS) { + step = voic->decay; + prd = sid->g.Aprd[(int) step]; + } else { + step = SR & 0xF; + prd = sid->g.Aprd[(int) step]; + } + step = sid->g.Astp[(int) step]; + if (sid->SIDct->ch[chn].rcnt >= prd && sid->SIDct->ch[chn].rcnt < prd + sid->g.ckr && + tmp == 0) { + sid->SIDct->ch[chn].rcnt -= prd; + if ((sid->SIDct->ch[chn].Ast & ATK) || + ++sid->SIDct->ch[chn].expcnt == Aexp[(int) sid->SIDct->ch[chn].envcnt]) { + if (!(sid->SIDct->ch[chn].Ast & _HZ)) { + if (sid->SIDct->ch[chn].Ast & ATK) { + sid->SIDct->ch[chn].envcnt += step; + if (sid->SIDct->ch[chn].envcnt >= 0xFF) { + sid->SIDct->ch[chn].envcnt = 0xFF; + sid->SIDct->ch[chn].Ast &= 0xFF - ATK; + } + } else if (!(sid->SIDct->ch[chn].Ast & DECSUS) || + sid->SIDct->ch[chn].envcnt > (SR >> 4) + (SR & 0xF0)) { + sid->SIDct->ch[chn].envcnt -= step; + if (sid->SIDct->ch[chn].envcnt <= 0 && + sid->SIDct->ch[chn].envcnt + step != 0) { + sid->SIDct->ch[chn].envcnt = 0; + sid->SIDct->ch[chn].Ast |= _HZ; + } + } + } + sid->SIDct->ch[chn].expcnt = 0; + } else { + } + } + sid->SIDct->ch[chn].envcnt = (int) sid->SIDct->ch[chn].envcnt & 0xFF; + double aAdd = (voic->freq_low + voic->freq_high * 256) * sid->g.ckr; + if (test || ((ctrl & SYN) && sid->SIDct->sMSBrise)) { + sid->SIDct->ch[chn].pacc = 0; + } else { + sid->SIDct->ch[chn].pacc += aAdd; + if (sid->SIDct->ch[chn].pacc > 0xFFFFFF) + sid->SIDct->ch[chn].pacc -= 0x1000000; + } + double MSB = (int) sid->SIDct->ch[chn].pacc & 0x800000; + sid->SIDct->sMSBrise = (MSB > ((int) sid->SIDct->ch[chn].pracc & 0x800000)) ? 1 : 0; + + if (wf & NOI) { + tmp = sid->SIDct->ch[chn].nLFSR; + if ((((int) sid->SIDct->ch[chn].pacc & 0x100000) != + ((int) sid->SIDct->ch[chn].pracc & 0x100000)) || + aAdd >= 0x100000) { + step = ((int) tmp & 0x400000) ^ (((int) tmp & 0x20000) << 5); + tmp = (((int) tmp << 1) + (step > 0 || test)) & 0x7FFFFF; + sid->SIDct->ch[chn].nLFSR = tmp; + } + wfout = (wf & 0x70) ? 0 + : (((int) tmp & 0x100000) >> 5) + (((int) tmp & 0x40000) >> 4) + + (((int) tmp & 0x4000) >> 1) + (((int) tmp & 0x800) << 1) + + (((int) tmp & 0x200) << 2) + (((int) tmp & 0x20) << 5) + + (((int) tmp & 0x04) << 7) + (((int) tmp & 0x01) << 8); + } else if (wf & PUL) { + double pw = (voic->pw_low + (voic->pw_high) * 256) * 16; + tmp = (int) aAdd >> 9; + if (0 < pw && pw < tmp) + pw = tmp; + tmp = (int) tmp ^ 0xFFFF; + if (pw > tmp) + pw = tmp; + tmp = (int) sid->SIDct->ch[chn].pacc >> 8; + if (wf == PUL) { + int lel = ((int) aAdd >> 16); + if (lel > 0) { + step = 256.0 / (double) lel; + } else { + step = INFINITY; + } + if (test) + wfout = 0xFFFF; + else if (tmp < pw) { + double lim = (0xFFFF - pw) * step; + if (lim > 0xFFFF) + lim = 0xFFFF; + wfout = lim - (pw - tmp) * step; + if (wfout < 0) + wfout = 0; + } else { + double lim = pw * step; + if (lim > 0xFFFF) + lim = 0xFFFF; + wfout = (0xFFFF - tmp) * step - lim; + if (wfout >= 0) + wfout = 0xFFFF; + wfout = (int) wfout & 0xFFFF; + } + } else { + wfout = (tmp >= pw || test) ? 0xFFFF : 0; + if (wf & TRI) { + if (wf & SAW) { + wfout = + (wfout) ? cmbWF(chn, sid->g.Pulsetrsaw, (int) tmp >> 4, 1, &sid->g) : 0; + } else { + tmp = (int) sid->SIDct->ch[chn].pacc ^ (ctrl & RNG ? sid->SIDct->sMSB : 0); + wfout = + (wfout) + ? cmbWF(chn, sid->g.pusaw, + ((int) tmp ^ ((int) tmp & 0x800000 ? 0xFFFFFF : 0)) >> 11, + 0, &sid->g) + : 0; + } + } else if (wf & SAW) + wfout = (wfout) ? cmbWF(chn, sid->g.pusaw, (int) tmp >> 4, 1, &sid->g) : 0; + } + } else if (wf & SAW) { + wfout = (int) sid->SIDct->ch[chn].pacc >> 8; + if (wf & TRI) + wfout = cmbWF(chn, sid->g.trsaw, (int) wfout >> 4, 1, &sid->g); + else { + step = aAdd / 0x1200000; + wfout += wfout * step; + if (wfout > 0xFFFF) + wfout = 0xFFFF - (wfout - 0x10000) / step; + } + } else if (wf & TRI) { + tmp = (int) sid->SIDct->ch[chn].pacc ^ (ctrl & RNG ? sid->SIDct->sMSB : 0); + wfout = ((int) tmp ^ ((int) tmp & 0x800000 ? 0xFFFFFF : 0)) >> 7; + } + if (wf) + sid->SIDct->ch[chn].prevwfout = wfout; + else { + wfout = sid->SIDct->ch[chn].prevwfout; + } + sid->SIDct->ch[chn].pracc = sid->SIDct->ch[chn].pacc; + sid->SIDct->sMSB = MSB; + // double preflin = flin; + if ((sid->mute_mask & (1 << chn))) { + if (sid->M[0x17] & sid->SIDct->ch[chn].FSW) { + double chnout = (wfout - 0x8000) * (sid->SIDct->ch[chn].envcnt / 256); + flin += chnout; + + // fake filter for solo waveform ahead + // mostly copypasted from below + double fakeflin = chnout; + double fakeflout = 0; + static double fakeplp[3] = {0}; + static double fakepbp[3] = {0}; + double ctf = sid->g.ctf_table[((sid->M[0x15]&7)|(sid->M[0x16]<<3))&0x7ff]; + double reso; + if (sid->g.model == 8580) { + reso = pow(2, ((double) (4 - (double) (sid->M[0x17] >> 4)) / 8)); + } else { + reso = (sid->M[0x17] > 0x5F) ? 8.0 / (double) (sid->M[0x17] >> 4) : 1.41; + } + double tmp = fakeflin + fakepbp[chn] * reso + fakeplp[chn]; + if (sid->M[0x18] & HP) + fakeflout -= tmp; + tmp = fakepbp[chn] - tmp * ctf; + fakepbp[chn] = tmp; + if (sid->M[0x18] & BP) + fakeflout -= tmp; + tmp = fakeplp[chn] + tmp * ctf; + fakeplp[chn] = tmp; + if (sid->M[0x18] & LP) + fakeflout += tmp; + + double wf_out = (fakeflout / SID_OUT_SCALE) * (sid->M[0x18] & 0xF) * 65535; + waveforms_add_sample(chn, wf_out); + } else if ((chn % 3) != 2 || !(sid->M[0x18] & OFF3)) { + double chnout = (wfout - 0x8000) * (sid->SIDct->ch[chn].envcnt / 256); + output += chnout; + + double wf_out = (chnout / SID_OUT_SCALE) * (sid->M[0x18] & 0xF) * 65535; + waveforms_add_sample(chn, wf_out); + } + } else { + waveforms_add_sample(chn, 0); + } + } + int M1 = 0; + if (M1 & 3) + sid->M[0x1B] = (int) wfout >> 8; + sid->M[0x1C] = sid->SIDct->ch[2].envcnt; + + double ctf = sid->g.ctf_table[((sid->M[0x15]&7)|(sid->M[0x16]<<3))&0x7ff]; + double reso; + if (sid->g.model == 8580) { + reso = pow(2, ((double) (4 - (double) (sid->M[0x17] >> 4)) / 8)); + } else { + reso = (sid->M[0x17] > 0x5F) ? 8.0 / (double) (sid->M[0x17] >> 4) : 1.41; + } + + double tmp = flin + sid->SIDct->pbp * reso + sid->SIDct->plp; + if (sid->M[0x18] & HP) + output -= tmp; + tmp = sid->SIDct->pbp - tmp * ctf; + sid->SIDct->pbp = tmp; + if (sid->M[0x18] & BP) + output -= tmp; + tmp = sid->SIDct->plp + tmp * ctf; + sid->SIDct->plp = tmp; + if (sid->M[0x18] & LP) + output += tmp; + return (output / SID_OUT_SCALE) * (sid->M[0x18] & 0xF); +} + +void dSID_setMuteMask(struct SID_chip* sid, int mute_mask) { + sid->mute_mask = mute_mask; +} + +float dSID_getVolume(struct SID_chip* sid, int channel) { + if ((sid->M[0x18] & 0xF) == 0) + return 0; + return sid->SIDct[0].ch[channel].envcnt / 256.0f; +} + +void dSID_write(struct SID_chip* sid, unsigned char addr, unsigned char val) { + sid->M[addr&0x1f]=val; +} diff --git a/src/engine/platform/sound/c64_d/dsid.h b/src/engine/platform/sound/c64_d/dsid.h new file mode 100644 index 00000000..b02d1870 --- /dev/null +++ b/src/engine/platform/sound/c64_d/dsid.h @@ -0,0 +1,97 @@ +#ifndef DSID_H +#define DSID_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct SID_ctx_chan { + double rcnt; + double envcnt; + double expcnt; + double pacc; + double pracc; + int FSW; + int nLFSR; + double prevwfout; + uint8_t pSR; + int Ast; +}; + +struct SID_ctx { + int sMSBrise; + int sMSB; + double plp; + double pbp; + struct SID_ctx_chan ch[3]; +}; + +struct SIDVOICE { + uint8_t freq_low; + uint8_t freq_high; + uint8_t pw_low; + uint8_t pw_high : 4; + uint8_t UNUSED : 4; + uint8_t control; + uint8_t decay : 4; + uint8_t attack : 4; + uint8_t susres; + // uint8_t release : 4; + // uint8_t sustain : 4; +}; + +struct SIDMEM { + struct SIDVOICE v[3]; + uint8_t UNUSED : 4; + uint8_t cutoff_low : 4; + uint8_t cutoff_high; + uint8_t reso_rt : 4; + uint8_t reso : 4; + uint8_t volume : 4; + uint8_t filter_mode : 4; + uint8_t paddlex; + uint8_t paddley; + uint8_t osc3; + uint8_t env3; +}; + +struct SID_globals { + double ckr; + double ctfr; + double ctf_ratio_6581; + + double ctf_table[2048]; + + int trsaw[4096]; + int pusaw[4096]; + int Pulsetrsaw[4096]; + + double Aprd[16]; + int Astp[16]; + int model; +}; + +#define MemLen 65536 + +struct SID_chip { + struct SID_globals g; + struct SID_ctx SIDct[3]; + uint8_t M[MemLen]; + int16_t lastOut[3]; + int mute_mask; +}; + +double dSID_render(struct SID_chip* sid); +void dSID_init(struct SID_chip* sid, double clockRate, double samplingRate, int model, unsigned char init_wf); +float dSID_getVolume(struct SID_chip* sid, int channel); +void dSID_setMuteMask(struct SID_chip* sid, int mute_mask); + +void dSID_write(struct SID_chip* sid, unsigned char addr, unsigned char val); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 3e3ac42c..fe34b8bc 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -37,6 +37,7 @@ extern "C" { #define rWritePCMRate(d) {regPool[65]=(d); pcm_write_rate(pcm,d);if (dumpWrites) addWrite(65,(d));} #define rWritePCMData(d) {regPool[66]=(d); pcm_write_fifo(pcm,d);} #define rWritePCMVol(d) rWritePCMCtrl((regPool[64]&(~0x8f))|((d)&15)) +#define rWriteZSMSync(d) {if (dumpWrites) addWrite(68,(d));} const char* regCheatSheetVERA[]={ "CHxFreq", "00+x*4", @@ -46,6 +47,7 @@ const char* regCheatSheetVERA[]={ "AUDIO_CTRL", "40", "AUDIO_RATE", "41", "AUDIO_DATA", "42", + "ZSM_SYNC", "44", NULL }; @@ -414,6 +416,9 @@ int DivPlatformVERA::dispatch(DivCommand c) { case DIV_CMD_MACRO_ON: chan[c.chan].std.mask(c.value,false); break; + case DIV_CMD_EXTERNAL: + rWriteZSMSync(c.value); + break; case DIV_ALWAYS_SET_VOLUME: return 0; break; diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index 7476a316..227512a7 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -51,7 +51,7 @@ class DivPlatformVERA: public DivDispatch { Channel chan[17]; DivDispatchOscBuffer* oscBuf[17]; bool isMuted[17]; - unsigned char regPool[67]; + unsigned char regPool[69]; struct VERA_PSG* psg; struct VERA_PCM* pcm; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index ce94b883..7825e188 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -236,6 +236,8 @@ const char* cmdName[]={ "NES_LINEAR_LENGTH", + "EXTERNAL", + "ALWAYS_SET_VOLUME" }; @@ -913,6 +915,7 @@ void DivEngine::processRow(int i, bool afterDelay) { //printf("\x1b[1;36m%d: extern command %d\x1b[m\n",i,effectVal); extValue=effectVal; extValuePresent=true; + dispatchCmd(DivCommand(DIV_CMD_EXTERNAL,effectVal)); break; case 0xef: // global pitch globalPitch+=(signed char)(effectVal-0x80); @@ -1691,6 +1694,10 @@ void DivEngine::runMidiTime(int totalCycles) { } void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size) { + lastNBIns=inChans; + lastNBOuts=outChans; + lastNBSize=size; + if (!size) { logW("nextBuf called with size 0!"); return; @@ -1980,14 +1987,10 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } else { // 3. run MIDI clock int midiTotal=MIN(cycles,runLeftG); - for (int i=0; i=67) { - logD ("ZSM: ignoring VERA PSG write a=%02x v=%02x",a,v); + if (a>=69) { + logD("ZSM: ignoring VERA PSG write a=%02x v=%02x",a,v); return; + } else if (a==68) { + // Sync event + numWrites++; + return syncCache.push_back(v); } else if (a>=64) { return writePCM(a-64,v); } @@ -259,7 +263,7 @@ SafeWriter* DivZSM::finish() { } void DivZSM::flushWrites() { - logD("ZSM: flushWrites.... numwrites=%d ticks=%d ymwrites=%d pcmMeta=%d pcmCache=%d pcmData=%d",numWrites,ticks,ymwrites.size(),pcmMeta.size(),pcmCache.size(),pcmData.size()); + logD("ZSM: flushWrites.... numwrites=%d ticks=%d ymwrites=%d pcmMeta=%d pcmCache=%d pcmData=%d syncCache=%d",numWrites,ticks,ymwrites.size(),pcmMeta.size(),pcmCache.size(),pcmData.size(),syncCache.size()); if (numWrites==0) return; flushTicks(); // only flush ticks if there are writes pending. for (unsigned char i=0; i<64; i++) { @@ -287,43 +291,43 @@ void DivZSM::flushWrites() { unsigned int pcmInst=0; int pcmOff=0; int pcmLen=0; - int extCmdLen=pcmMeta.size()*2; + int extCmd0Len=pcmMeta.size()*2; if (pcmCache.size()) { // collapse stereo data to mono if both channels are fully identical // which cuts PCM data size in half for center-panned PCM events - if (pcmCtrlDCCache & 0x10) { // stereo bit is on + if (pcmCtrlDCCache&0x10) { // stereo bit is on unsigned int e; - if (pcmCtrlDCCache & 0x20) { // 16-bit + if (pcmCtrlDCCache&0x20) { // 16-bit // for 16-bit PCM data, the size must be a multiple of 4 if (pcmCache.size()%4==0) { // check for identical L+R channels - for (e=0;e>1;e+=2) { + for (e=0; e>1; e+=2) { pcmCache[e]=pcmCache[e<<1]; pcmCache[e+1]=pcmCache[(e<<1)+1]; } pcmCache.resize(pcmCache.size()>>1); - pcmCtrlDCCache &= ~0x10; // clear stereo bit + pcmCtrlDCCache&=(unsigned char)~0x10; // clear stereo bit } } } else { // 8-bit // for 8-bit PCM data, the size must be a multiple of 2 if (pcmCache.size()%2==0) { // check for identical L+R channels - for (e=0;e>1;e++) { + for (e=0; e>1; e++) { pcmCache[e]=pcmCache[e<<1]; } pcmCache.resize(pcmCache.size()>>1); - pcmCtrlDCCache &= ~0x10; // clear stereo bit + pcmCtrlDCCache&=(unsigned char)~0x10; // clear stereo bit } } } @@ -340,7 +344,7 @@ void DivZSM::flushWrites() { pcmData.insert(pcmData.end(),pcmCache.begin(),pcmCache.end()); } pcmCache.clear(); - extCmdLen+=2; + extCmd0Len+=2; // search for a matching PCM instrument definition for (S_pcmInst& inst: pcmInsts) { if (inst.offset==pcmOff && inst.length==pcmLen && inst.geometry==pcmCtrlDCCache) @@ -355,14 +359,14 @@ void DivZSM::flushWrites() { pcmInsts.push_back(inst); } } - if (extCmdLen>63) { // this would be bad, but will almost certainly never happen - logE("ZSM: extCmd exceeded maximum length of 63: %d",extCmdLen); - extCmdLen=0; + if (extCmd0Len>63) { // this would be bad, but will almost certainly never happen + logE("ZSM: extCmd 0 exceeded maximum length of 63: %d",extCmd0Len); + extCmd0Len=0; pcmMeta.clear(); } - if (extCmdLen) { // we have some PCM events to write - w->writeC(0x40); - w->writeC((unsigned char)extCmdLen); // the high two bits are guaranteed to be zero, meaning this is a PCM command + if (extCmd0Len) { // we have some PCM events to write + w->writeC(ZSM_EXT); + w->writeC(ZSM_EXT_PCM|(unsigned char)extCmd0Len); for (DivRegWrite& write: pcmMeta) { w->writeC(write.addr); w->writeC(write.val); @@ -373,6 +377,18 @@ void DivZSM::flushWrites() { w->writeC((unsigned char)pcmInst&0xff); } } + n=0; + while (n<(long)syncCache.size()) { // we have one or more sync events to write + int writes=syncCache.size()-n; + w->writeC(ZSM_EXT); + if (writes>ZSM_SYNC_MAX_WRITES) writes=ZSM_SYNC_MAX_WRITES; + w->writeC(ZSM_EXT_SYNC|(writes<<1)); + for (; writes>0; writes--) { + w->writeC(0x00); // 0x00 = Arbitrary sync message + w->writeC(syncCache[n++]); + } + } + syncCache.clear(); numWrites=0; } diff --git a/src/engine/zsm.h b/src/engine/zsm.h index 17a1fd06..42300cf7 100644 --- a/src/engine/zsm.h +++ b/src/engine/zsm.h @@ -30,9 +30,16 @@ #define ZSM_YM_CMD 0x40 #define ZSM_DELAY_CMD 0x80 #define ZSM_YM_MAX_WRITES 63 +#define ZSM_SYNC_MAX_WRITES 31 #define ZSM_DELAY_MAX 127 #define ZSM_EOF ZSM_DELAY_CMD +#define ZSM_EXT ZSM_YM_CMD +#define ZSM_EXT_PCM 0x00 +#define ZSM_EXT_CHIP 0x40 +#define ZSM_EXT_SYNC 0x80 +#define ZSM_EXT_CUSTOM 0xC0 + enum YM_STATE { ym_PREV, ym_NEW, ym_STATES }; enum PSG_STATE { psg_PREV, psg_NEW, psg_STATES }; @@ -52,6 +59,7 @@ class DivZSM { std::vector pcmData; std::vector pcmCache; std::vector pcmInsts; + std::vector syncCache; int loopOffset; int numWrites; int ticks; diff --git a/src/engine/zsmOps.cpp b/src/engine/zsmOps.cpp index c59efeae..85ddb5d4 100644 --- a/src/engine/zsmOps.cpp +++ b/src/engine/zsmOps.cpp @@ -151,7 +151,7 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) { for (DivRegWrite& write: writes) { if (i==YM) zsm.writeYM(write.addr&0xff,write.val); if (i==VERA) { - if (done && write.addr >= 64) continue; // don't process any PCM events on the loop lookahead + if (done && write.addr>=64) continue; // don't process any PCM or sync events on the loop lookahead zsm.writePSG(write.addr&0xff,write.val); } } diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 8131d3c3..7afd015c 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -134,6 +134,7 @@ const char* aboutLine[]={ "fd", "GENATARi", "host12prog", + "jvsTSX", "Lumigado", "Lunathir", "plane", @@ -173,6 +174,7 @@ const char* aboutLine[]={ "reSID by Dag Lem", "reSIDfp by Dag Lem, Antti Lankila", "and Leandro Nini", + "dSID by DefleMask Team based on jsSID", "Stella by Stella Team", "QSound emulator by superctr and Valley Bell", "VICE VIC-20 sound core by Rami Rasanen and viznut", diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index 49d568b3..60806bde 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -356,7 +356,6 @@ void putDispatchChip(void* data, int type) { ImGui::Text("- filtCut: %d",ch->filtCut); ImGui::Text("- resetTime: %d",ch->resetTime); COMMON_CHIP_DEBUG_BOOL; - ImGui::TextColored(ch->isFP?colorOn:colorOff,">> IsFP"); break; } case DIV_SYSTEM_ARCADE: diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index b9dc6c42..bfeb9067 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -388,6 +388,34 @@ void FurnaceGUI::drawDebug() { ImGui::Text("System Managed Scale: %d",sysManagedScale); ImGui::TreePop(); } + if (ImGui::TreeNode("Audio Debug")) { + TAAudioDesc& audioWant=e->getAudioDescWant(); + TAAudioDesc& audioGot=e->getAudioDescGot(); + + ImGui::Text("want:"); + ImGui::Text("- name: %s",audioWant.name.c_str()); + ImGui::Text("- device name: %s",audioWant.deviceName.c_str()); + ImGui::Text("- rate: %f",audioWant.rate); + ImGui::Text("- buffer size: %d",audioWant.bufsize); + ImGui::Text("- fragments: %d",audioWant.fragments); + ImGui::Text("- inputs: %d",audioWant.inChans); + ImGui::Text("- outputs: %d",audioWant.outChans); + ImGui::Text("- format: %d",audioWant.outFormat); + + ImGui::Text("got:"); + ImGui::Text("- name: %s",audioGot.name.c_str()); + ImGui::Text("- device name: %s",audioGot.deviceName.c_str()); + ImGui::Text("- rate: %f",audioGot.rate); + ImGui::Text("- buffer size: %d",audioGot.bufsize); + ImGui::Text("- fragments: %d",audioGot.fragments); + ImGui::Text("- inputs: %d",audioGot.inChans); + ImGui::Text("- outputs: %d",audioGot.outChans); + ImGui::Text("- format: %d",audioGot.outFormat); + + ImGui::Text("last call to nextBuf(): in %d, out %d, size %d",e->lastNBIns,e->lastNBOuts,e->lastNBSize); + + ImGui::TreePop(); + } if (ImGui::TreeNode("Visualizer Debug")) { if (ImGui::BeginTable("visX",3,ImGuiTableFlags_Borders)) { ImGui::TableNextRow(ImGuiTableRowFlags_Headers); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 62608960..3e7ed027 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6329,6 +6329,19 @@ bool FurnaceGUI::init() { } #endif + int numDriversA=SDL_GetNumAudioDrivers(); + if (numDriversA<0) { + logW("could not list audio drivers! %s",SDL_GetError()); + } else { + for (int i=0; iinit(sdlWin)) { - if (settings.renderBackend!="SDL" && !settings.renderBackend.empty()) { - settings.renderBackend=""; + if (settings.renderBackend!="SDL") { + settings.renderBackend="SDL"; //e->setConf("renderBackend",""); //e->saveConf(); //lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace."); diff --git a/src/gui/gui.h b/src/gui/gui.h index a8befff2..df778e3d 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -75,16 +75,20 @@ enum FurnaceGUIRenderBackend { GUI_BACKEND_DX11 }; -#ifdef HAVE_RENDER_SDL -#define GUI_BACKEND_DEFAULT GUI_BACKEND_SDL -#define GUI_BACKEND_DEFAULT_NAME "SDL" -#else #ifdef HAVE_RENDER_DX11 #define GUI_BACKEND_DEFAULT GUI_BACKEND_DX11 #define GUI_BACKEND_DEFAULT_NAME "DirectX 11" #else +#ifdef HAVE_RENDER_GL #define GUI_BACKEND_DEFAULT GUI_BACKEND_GL #define GUI_BACKEND_DEFAULT_NAME "OpenGL" +#else +#ifdef HAVE_RENDER_SDL +#define GUI_BACKEND_DEFAULT GUI_BACKEND_SDL +#define GUI_BACKEND_DEFAULT_NAME "SDL" +#else +#error how did you manage to do that? +#endif #endif #endif @@ -1300,6 +1304,7 @@ class FurnaceGUI { std::deque recentFile; std::vector makeInsTypeList; std::vector availRenderDrivers; + std::vector availAudioDrivers; bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, zsmExportLoop, vgmExportPatternHints; bool vgmExportDirectStream, displayInsTypeList; @@ -1530,6 +1535,7 @@ class FurnaceGUI { String macroRelLabel; String emptyLabel; String emptyLabel2; + String sdlAudioDriver; DivConfig initialSys; Settings(): @@ -1544,7 +1550,7 @@ class FurnaceGUI { snCore(0), nesCore(0), fdsCore(0), - c64Core(1), + c64Core(0), pokeyCore(1), opnCore(1), pcSpeakerOutMethod(0), @@ -1682,7 +1688,8 @@ class FurnaceGUI { noteRelLabel("==="), macroRelLabel("REL"), emptyLabel("..."), - emptyLabel2("..") {} + emptyLabel2(".."), + sdlAudioDriver("") {} } settings; struct Tutorial { diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index a34a856d..72d953b7 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -133,17 +133,20 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int } ImGui::PopStyleColor(); // for each column + int mustSetXOf=0; for (int j=0; j