├── fuzz ├── .gitignore └── Cargo.toml ├── crates ├── shvc-sound-emu │ ├── src │ │ ├── nall │ │ │ ├── vfs.hpp │ │ │ ├── view.hpp │ │ │ ├── queue.hpp │ │ │ ├── windows │ │ │ │ ├── guid.hpp │ │ │ │ ├── windows.hpp │ │ │ │ ├── launcher.hpp │ │ │ │ ├── guid.cpp │ │ │ │ ├── service.hpp │ │ │ │ ├── registry.hpp │ │ │ │ ├── shared-memory.hpp │ │ │ │ ├── utf8.cpp │ │ │ │ └── utf8.hpp │ │ │ ├── shared-memory.hpp │ │ │ ├── bcd.hpp │ │ │ ├── macos │ │ │ │ └── guard.hpp │ │ │ ├── service.hpp │ │ │ ├── xorg │ │ │ │ ├── clipboard.hpp │ │ │ │ ├── xorg.hpp │ │ │ │ └── guard.hpp │ │ │ ├── main.hpp │ │ │ ├── cd │ │ │ │ ├── crc16.hpp │ │ │ │ ├── sync.hpp │ │ │ │ ├── scrambler.hpp │ │ │ │ └── edc.hpp │ │ │ ├── recompiler │ │ │ │ ├── amd64 │ │ │ │ │ └── amd64.hpp │ │ │ │ └── generic │ │ │ │ │ └── constants.hpp │ │ │ ├── vfs │ │ │ │ ├── attribute.hpp │ │ │ │ ├── vfs.hpp │ │ │ │ ├── file.hpp │ │ │ │ ├── node.hpp │ │ │ │ └── disk.hpp │ │ │ ├── inode.cpp │ │ │ ├── dsp │ │ │ │ └── iir │ │ │ │ │ ├── dc-removal.hpp │ │ │ │ │ └── one-pole.hpp │ │ │ ├── string │ │ │ │ ├── atoi.hpp │ │ │ │ ├── utf8.hpp │ │ │ │ ├── eval │ │ │ │ │ └── node.hpp │ │ │ │ ├── convert.hpp │ │ │ │ ├── vector.hpp │ │ │ │ └── split.hpp │ │ │ ├── vector │ │ │ │ ├── compare.hpp │ │ │ │ ├── assign.hpp │ │ │ │ ├── specialization │ │ │ │ │ └── u8.hpp │ │ │ │ ├── access.hpp │ │ │ │ ├── core.hpp │ │ │ │ └── utility.hpp │ │ │ ├── encode │ │ │ │ ├── html.hpp │ │ │ │ ├── mtf.hpp │ │ │ │ ├── url.hpp │ │ │ │ ├── wav.hpp │ │ │ │ ├── base.hpp │ │ │ │ ├── rle.hpp │ │ │ │ └── bmp.hpp │ │ │ ├── decode │ │ │ │ ├── mtf.hpp │ │ │ │ ├── huffman.hpp │ │ │ │ ├── bwt.hpp │ │ │ │ ├── rle.hpp │ │ │ │ ├── base64.hpp │ │ │ │ ├── html.hpp │ │ │ │ ├── base.hpp │ │ │ │ └── url.hpp │ │ │ ├── inline-if.hpp │ │ │ ├── property.hpp │ │ │ ├── tcptext │ │ │ │ ├── tcptext-server.hpp │ │ │ │ └── tcptext-server.cpp │ │ │ ├── counting-sort.hpp │ │ │ ├── terminal.cpp │ │ │ ├── instance.hpp │ │ │ ├── algorithm.hpp │ │ │ ├── utility.hpp │ │ │ ├── image │ │ │ │ ├── static.hpp │ │ │ │ └── multifactor.hpp │ │ │ ├── arithmetic │ │ │ │ ├── barrett.hpp │ │ │ │ └── unsigned.hpp │ │ │ ├── http │ │ │ │ ├── client.hpp │ │ │ │ ├── client.cpp │ │ │ │ └── server.hpp │ │ │ ├── primitives.hpp │ │ │ ├── matrix-multiply.hpp │ │ │ ├── gdb │ │ │ │ └── watchpoint.hpp │ │ │ ├── pointer.hpp │ │ │ ├── nall.cpp │ │ │ ├── platform.cpp │ │ │ ├── dl.cpp │ │ │ ├── primitives │ │ │ │ ├── boolean.hpp │ │ │ │ └── real.hpp │ │ │ ├── cd.hpp │ │ │ ├── hash │ │ │ │ ├── crc16.hpp │ │ │ │ ├── crc32.hpp │ │ │ │ ├── crc64.hpp │ │ │ │ └── hash.hpp │ │ │ ├── literals.hpp │ │ │ ├── memory.cpp │ │ │ ├── random.cpp │ │ │ ├── thread.cpp │ │ │ ├── adaptive-array.hpp │ │ │ ├── traits.hpp │ │ │ ├── queue │ │ │ │ └── spsc.hpp │ │ │ ├── elliptic-curve │ │ │ │ └── curve25519.hpp │ │ │ ├── path.hpp │ │ │ └── interpolation.hpp │ │ ├── spc700 │ │ │ ├── memory.cpp │ │ │ └── spc700.cpp │ │ ├── smp │ │ │ ├── smp.cpp │ │ │ └── memory.cpp │ │ ├── sample-buffer.hpp │ │ ├── dsp │ │ │ ├── misc.cpp │ │ │ ├── counter.cpp │ │ │ ├── gaussian.cpp │ │ │ └── brr.cpp │ │ └── shvc-sound-emu.hpp │ ├── build.rs │ ├── Cargo.toml │ ├── LICENSE │ └── README.md ├── brr │ ├── Cargo.toml │ └── LICENSE ├── compiler │ ├── src │ │ ├── command_compiler.rs │ │ └── lib.rs │ ├── Cargo.toml │ └── LICENSE ├── tad-compiler │ ├── Cargo.toml │ └── LICENSE ├── wav2brr │ ├── Cargo.toml │ └── LICENSE ├── tad-emu │ ├── Cargo.toml │ └── LICENSE └── tad-gui │ ├── Cargo.toml │ ├── LICENSE │ └── src │ ├── licenses_dialog.rs │ └── monitor_timer.rs ├── audio-driver ├── ca65-api │ └── tests │ │ ├── api-tests │ │ └── obj │ │ │ └── .gitignore │ │ ├── sound-test │ │ ├── obj │ │ │ └── .gitignore │ │ ├── gen │ │ │ └── .gitignore │ │ └── screenshot.png │ │ └── _common │ │ └── hirom256k.cfg ├── asar-api │ ├── test │ │ ├── out │ │ │ └── .gitignore │ │ └── GNUmakefile │ └── example │ │ └── gen │ │ └── .gitignore ├── pvsneslib-api │ ├── sound-test │ │ ├── gen │ │ │ └── .gitignore │ │ ├── screenshot.png │ │ ├── tiles │ │ │ └── font-2bpp.png │ │ ├── tad-audio.asm │ │ └── data.asm │ └── api-tests │ │ ├── tad-audio.asm │ │ ├── Makefile │ │ └── audio-data.asm ├── 64tass-api │ ├── example │ │ └── gen │ │ │ └── .gitignore │ └── test │ │ ├── out │ │ └── .gitignore │ │ ├── ca65 │ │ └── minimal.cfg │ │ └── GNUmakefile ├── GNUmakefile └── LICENSE ├── .gitmodules ├── examples ├── samples │ ├── sine.wav │ ├── square.wav │ ├── sawtooth.wav │ ├── triangle.wav │ ├── nesdoug │ │ ├── Conga.brr │ │ ├── Cowbell.brr │ │ ├── PIANO_C5.brr │ │ ├── ViolinC4.brr │ │ ├── ViolinSpicatoB.brr │ │ └── _source.txt │ ├── sine.txt │ ├── sawtooth.txt │ ├── triangle.txt │ └── square.txt ├── mml │ ├── samples.mml │ ├── broken-chord.mml │ ├── quantize-with-temp-gain.mml │ ├── multi-channel-noise.mml │ ├── pan.mml │ ├── chords.mml │ ├── continuous-portamento.mml │ ├── chords-transpose.mml │ ├── play-pitch.mml │ ├── play-noise.mml │ ├── portamento.mml │ ├── echo.mml │ ├── fir.mml │ ├── panbrello.mml │ ├── pan-slide.mml │ ├── bc-asm-in-mml.mml │ ├── tremolo.mml │ ├── volume-slide.mml │ ├── vibrato.mml │ ├── pitch-modulation.mml │ ├── detune.mml │ └── early-release.mml └── songs │ └── ode-to-joy.mml ├── manual-tests ├── samples │ ├── sine.wav │ ├── square.wav │ ├── sawtooth.wav │ ├── square48.wav │ ├── triangle.wav │ ├── sawtooth.txt │ ├── triangle.txt │ ├── square.txt │ └── square48.txt ├── bug-tests │ ├── volume-pop-bug.mml │ ├── keyoff-scrn-bug.mml │ ├── two-channels-modify-timer.mml │ ├── tremolo-underflow-bug.mml │ ├── unbalanced-vibrato-bug.mml │ ├── subroutine-vibrato-bug.mml │ ├── vibrato-after-portamento.mml │ ├── volume-slide-down-bug.mml │ ├── subroutine-cursor-tick-count-bug.mml │ ├── noise-and-sfx-test.mml │ ├── envelope-race-condition.mml │ └── portamento-first-tick-bugtest.mml ├── mml │ ├── song-loop.mml │ ├── disable-keyoff.mml │ ├── q-early-release.mml │ ├── loop.mml │ ├── echo-invert.mml │ ├── envelope.mml │ ├── loop-stack.mml │ ├── set-echo-delay.mml │ ├── invert.mml │ ├── channel-transpose.mml │ └── key-signature.mml ├── manual │ ├── tempo-and-timer.mml │ ├── noise-volume-restored-test.mml │ ├── vibrato-with-delay.mml │ ├── mml-subroutine-play-at-cursor.mml │ ├── transpose.mml │ ├── noise-priority-test.mml │ ├── noise-timing-test-a.mml │ ├── portamento-without-instrument.mml │ └── cut-portamento-unknown-velocity.mml └── bc-interpreter-tests │ ├── early-release-and-temp-gain.mml │ └── large-skip-last-loop.mml ├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── deny.toml ├── LICENSE.md ├── docs └── special-thanks.md └── Cargo.toml /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/vfs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /audio-driver/ca65-api/tests/api-tests/obj/.gitignore: -------------------------------------------------------------------------------- 1 | # ca65 object files go here 2 | *.o 3 | 4 | -------------------------------------------------------------------------------- /audio-driver/ca65-api/tests/sound-test/obj/.gitignore: -------------------------------------------------------------------------------- 1 | # ca65 object files go here 2 | *.o 3 | 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wiz"] 2 | path = wiz 3 | url = https://github.com/undisbeliever/wiz.git 4 | -------------------------------------------------------------------------------- /audio-driver/asar-api/test/out/.gitignore: -------------------------------------------------------------------------------- 1 | # test binaries and object files go here 2 | *.bin 3 | *.sym 4 | 5 | -------------------------------------------------------------------------------- /audio-driver/pvsneslib-api/sound-test/gen/.gitignore: -------------------------------------------------------------------------------- 1 | # generated data and source files go here 2 | *.* 3 | 4 | -------------------------------------------------------------------------------- /audio-driver/64tass-api/example/gen/.gitignore: -------------------------------------------------------------------------------- 1 | # generated data and source files go here 2 | *.bin 3 | *.inc 4 | 5 | -------------------------------------------------------------------------------- /audio-driver/asar-api/example/gen/.gitignore: -------------------------------------------------------------------------------- 1 | # generated data and source files go here 2 | *.bin 3 | *.inc 4 | 5 | -------------------------------------------------------------------------------- /audio-driver/64tass-api/test/out/.gitignore: -------------------------------------------------------------------------------- 1 | # test binaries and object files go here 2 | *.o 3 | *.bin 4 | *.txt 5 | 6 | -------------------------------------------------------------------------------- /examples/samples/sine.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/examples/samples/sine.wav -------------------------------------------------------------------------------- /examples/samples/square.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/examples/samples/square.wav -------------------------------------------------------------------------------- /audio-driver/ca65-api/tests/sound-test/gen/.gitignore: -------------------------------------------------------------------------------- 1 | # generated data and source files go here 2 | *.bin 3 | *.inc 4 | *.s 5 | 6 | -------------------------------------------------------------------------------- /examples/samples/sawtooth.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/examples/samples/sawtooth.wav -------------------------------------------------------------------------------- /examples/samples/triangle.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/examples/samples/triangle.wav -------------------------------------------------------------------------------- /manual-tests/samples/sine.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/manual-tests/samples/sine.wav -------------------------------------------------------------------------------- /manual-tests/samples/square.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/manual-tests/samples/square.wav -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/view.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template struct view; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /manual-tests/samples/sawtooth.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/manual-tests/samples/sawtooth.wav -------------------------------------------------------------------------------- /manual-tests/samples/square48.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/manual-tests/samples/square48.wav -------------------------------------------------------------------------------- /manual-tests/samples/triangle.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/manual-tests/samples/triangle.wav -------------------------------------------------------------------------------- /examples/samples/nesdoug/Conga.brr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/examples/samples/nesdoug/Conga.brr -------------------------------------------------------------------------------- /examples/samples/nesdoug/Cowbell.brr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/examples/samples/nesdoug/Cowbell.brr -------------------------------------------------------------------------------- /examples/samples/nesdoug/PIANO_C5.brr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/examples/samples/nesdoug/PIANO_C5.brr -------------------------------------------------------------------------------- /examples/samples/nesdoug/ViolinC4.brr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/examples/samples/nesdoug/ViolinC4.brr -------------------------------------------------------------------------------- /examples/samples/nesdoug/ViolinSpicatoB.brr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/examples/samples/nesdoug/ViolinSpicatoB.brr -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/queue.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | -------------------------------------------------------------------------------- /audio-driver/ca65-api/tests/sound-test/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/audio-driver/ca65-api/tests/sound-test/screenshot.png -------------------------------------------------------------------------------- /audio-driver/pvsneslib-api/sound-test/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/audio-driver/pvsneslib-api/sound-test/screenshot.png -------------------------------------------------------------------------------- /audio-driver/pvsneslib-api/sound-test/tiles/font-2bpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/undisbeliever/terrific-audio-driver/HEAD/audio-driver/pvsneslib-api/sound-test/tiles/font-2bpp.png -------------------------------------------------------------------------------- /examples/samples/sine.txt: -------------------------------------------------------------------------------- 1 | Generated with Audacity. 2 | 3 | Waveform: Sine 4 | Frequency (Hz): 500 5 | Amplitude (0-1): 0.95 6 | Duration: 64 samples 7 | 8 | -------------------------------------------------------------------------------- /examples/samples/sawtooth.txt: -------------------------------------------------------------------------------- 1 | Generated with Audacity. 2 | 3 | Waveform: Sawtooth 4 | Frequency (Hz): 500 5 | Amplitude (0-1): 0.8 6 | Duration: 64 samples 7 | 8 | -------------------------------------------------------------------------------- /examples/samples/triangle.txt: -------------------------------------------------------------------------------- 1 | Generated with Audacity. 2 | 3 | Waveform: Triangle 4 | Frequency (Hz): 500 5 | Amplitude (0-1): 0.8 6 | Duration: 64 samples 7 | 8 | -------------------------------------------------------------------------------- /manual-tests/samples/sawtooth.txt: -------------------------------------------------------------------------------- 1 | Generated with Audacity. 2 | 3 | Waveform: Sawtooth 4 | Frequency (Hz): 500 5 | Amplitude (0-1): 0.8 6 | Duration: 64 samples 7 | 8 | -------------------------------------------------------------------------------- /manual-tests/samples/triangle.txt: -------------------------------------------------------------------------------- 1 | Generated with Audacity. 2 | 3 | Waveform: Triangle 4 | Frequency (Hz): 500 5 | Amplitude (0-1): 0.8 6 | Duration: 64 samples 7 | 8 | -------------------------------------------------------------------------------- /examples/samples/square.txt: -------------------------------------------------------------------------------- 1 | Generated with Audacity. 2 | 3 | Waveform: Square, no alias 4 | Frequency (Hz): 500 5 | Amplitude (0-1): 0.75 6 | Duration: 64 samples 7 | 8 | -------------------------------------------------------------------------------- /manual-tests/samples/square.txt: -------------------------------------------------------------------------------- 1 | Generated with Audacity. 2 | 3 | Waveform: Square, no alias 4 | Frequency (Hz): 500 5 | Amplitude (0-1): 0.75 6 | Duration: 64 samples 7 | 8 | -------------------------------------------------------------------------------- /manual-tests/bug-tests/volume-pop-bug.mml: -------------------------------------------------------------------------------- 1 | ; Changing the volume immediately after key-off causes a pop 2 | ; 3 | ; Reported by KungFuFurby 4 | 5 | @1 sine adsr 14 2 7 0 6 | 7 | A @1 v2 c v16 c r 8 | 9 | -------------------------------------------------------------------------------- /manual-tests/samples/square48.txt: -------------------------------------------------------------------------------- 1 | Generated with Audacity. 2 | 3 | Waveform: Square, no alias 4 | Frequency (Hz): 666.66666 5 | Amplitude (0-1): 0.75 6 | Duration: 48 samples 7 | 8 | -------------------------------------------------------------------------------- /manual-tests/bug-tests/keyoff-scrn-bug.mml: -------------------------------------------------------------------------------- 1 | ; Changing the instrument sample immediately after key-off causes the sample to change in the middle of the Release envelope. 2 | 3 | @1 sine adsr 14 7 7 0 4 | @2 square gain 127 5 | 6 | A @1 v16 c @2 c r 7 | 8 | -------------------------------------------------------------------------------- /manual-tests/bug-tests/two-channels-modify-timer.mml: -------------------------------------------------------------------------------- 1 | ; Minimal repoducable bugtest of a rust bytecode interpreter 2 | ; timer mismatch when 2 channels want to change the timer. 3 | ; 4 | ; Reported by KungFuFurby 5 | 6 | A r4 t150 r1 7 | B r8 t110 r1 8 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/windows/guid.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall { 6 | 7 | auto guid() -> string; 8 | 9 | } 10 | 11 | #if defined(NALL_HEADER_ONLY) 12 | #include 13 | #endif 14 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/windows/windows.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #undef NOMINMAX 4 | #define NOMINMAX 5 | 6 | #undef UNICODE 7 | #define UNICODE 8 | 9 | #undef WIN32_LEAN_AND_MEAN 10 | #define WIN32_LEAN_AND_MEAN 11 | 12 | #include 13 | -------------------------------------------------------------------------------- /audio-driver/pvsneslib-api/sound-test/tad-audio.asm: -------------------------------------------------------------------------------- 1 | ; Including `tad-audio.asm` instead of copying it so there is only 1 copy in the repo. 2 | 3 | .include "../tad-audio.asm" 4 | 5 | ; MUST NOT write any code after this include, the .bank value has been reset to 0 6 | 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | - package-ecosystem: "cargo" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /manual-tests/mml/song-loop.mml: -------------------------------------------------------------------------------- 1 | ; bc_interpreter song loop test 2 | ; Tests `L` commands at different tick counts 3 | 4 | #Tempo 140 5 | 6 | @1 square 7 | @2 triangle 8 | 9 | A @1 v6 o3 10 | A e r e r L c r 11 | 12 | B @2 v12 13 | B L d e f g | d e f f | d e f g | d e f r 14 | -------------------------------------------------------------------------------- /crates/brr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brr" 3 | license = "MIT" 4 | 5 | edition = "2021" 6 | rust-version.workspace = true 7 | 8 | publish = false 9 | 10 | # This is a private internal crate. 11 | # Using version "0.0.0" as I do not care about breaking changes. 12 | version = "0.0.0" 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.sym 3 | *.dbg 4 | *.label 5 | *.sfc 6 | *-map.txt 7 | *.obj 8 | 9 | *.pic 10 | *.pal 11 | linkfile 12 | 13 | /*.sh 14 | /*.py 15 | 16 | /target 17 | /vendor 18 | crates/*/target 19 | /.cargo 20 | /.vscode 21 | SDL2.dll 22 | 23 | fuzz/target 24 | fuzz/corpus 25 | fuzz/artifacts 26 | 27 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/shared-memory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #if defined(API_POSIX) 7 | #include 8 | #endif 9 | 10 | #if defined(API_WINDOWS) 11 | #include 12 | #endif 13 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/bcd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall { 6 | 7 | struct BCD { 8 | static auto encode(u8 value) -> u8 { return value / 10 << 4 | value % 10; } 9 | static auto decode(u8 value) -> u8 { return (value >> 4) * 10 + (value & 15); } 10 | }; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /manual-tests/mml/disable-keyoff.mml: -------------------------------------------------------------------------------- 1 | #Title disable key-off 2 | 3 | @1 triangle adsr 12 2 5 8 4 | @2 sawtooth adsr 12 2 5 8 5 | 6 | A v12 7 | 8 | ; triangle pops 9 | A @1 K0 g d e g r r8 10 | 11 | ; No noticeable pops in sawtooth 12 | A @2 K0 g d e g r r8 13 | 14 | ; Normal key-off behaviour 15 | A @1 K1 g d e g2 r8 16 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | cxx_build::bridge("src/lib.rs") 3 | .file("src/shvc-sound-emu.cpp") 4 | .cpp(true) 5 | .std("c++17") 6 | .include("src") 7 | .warnings(false) 8 | .compile("cxx-apu"); 9 | 10 | println!("cargo:rerun-if-changed=src/") 11 | } 12 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/macos/guard.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NALL_MACOS_GUARD_HPP 2 | #define NALL_MACOS_GUARD_HPP 3 | 4 | #define Boolean CocoaBoolean 5 | #define decimal CocoaDecimal 6 | #define DEBUG CocoaDebug 7 | 8 | #else 9 | #undef NALL_MACOS_GUARD_HPP 10 | 11 | #undef Boolean 12 | #undef decimal 13 | #undef DEBUG 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/service.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //service model template built on top of shared-memory 4 | 5 | #include 6 | 7 | #if defined(API_POSIX) 8 | #include 9 | #endif 10 | 11 | #if defined(API_WINDOWS) 12 | #include 13 | #endif 14 | -------------------------------------------------------------------------------- /manual-tests/bug-tests/tremolo-underflow-bug.mml: -------------------------------------------------------------------------------- 1 | ; This manual (audible) test is used to confirm tremolo with an 2 | ; amplitude matching the current volume does not underflow. 3 | 4 | #Title Tremolo underflow bugtest 5 | #Tempo 120 6 | 7 | @1 square gain F127 8 | 9 | A @1 10 | 11 | A v4 v~4,3 c2 r8 12 | 13 | A V90 V~90,10 c1^1 r8 14 | -------------------------------------------------------------------------------- /crates/compiler/src/command_compiler.rs: -------------------------------------------------------------------------------- 1 | //! Command compiler 2 | 3 | // SPDX-FileCopyrightText: © 2025 Marcus Rowe 4 | // 5 | // SPDX-License-Identifier: MIT 6 | 7 | pub(crate) mod analysis; 8 | pub(crate) mod channel_bc_generator; 9 | pub(crate) mod commands; 10 | pub(crate) mod parsers; 11 | pub(crate) mod subroutines; 12 | -------------------------------------------------------------------------------- /examples/mml/samples.mml: -------------------------------------------------------------------------------- 1 | #Title How to play samples in MML 2 | 3 | #Tempo 120 4 | 5 | @conga conga 6 | @cowbell cowbell 7 | 8 | A @conga 9 | 10 | A l4 [ @conga s0 s1 @cowbell s r ]2 11 | A l4 [ @conga s0 s1 @cowbell l2 s ]2 12 | A l4 [ @conga s0,4 s1,4 @cowbell s,2 ]2 13 | A l1 [ @conga s0,%24 s1,%24 @cowbell s,%48 ]2 14 | -------------------------------------------------------------------------------- /examples/samples/nesdoug/_source.txt: -------------------------------------------------------------------------------- 1 | 2 | Public domain BRR samples by nesdoug (Doug Fraker). 3 | 4 | Samples are tuned to B +21 cents (wavelength of 32, 64, 128 or 256 samples per loop) 5 | 6 | SOURCE: 7 | * https://nesdoug.com/2022/01/27/why-b21-cents/#free-samples 8 | * https://github.com/nesdoug/SNES_13/tree/master/MUSIC/More_Samples 9 | 10 | 11 | -------------------------------------------------------------------------------- /manual-tests/mml/q-early-release.mml: -------------------------------------------------------------------------------- 1 | ; q early-release test 2 | 3 | ; 1 tick = 8ms 4 | #Timer 64 5 | #ZenLen 192 6 | 7 | @1 square adsr 10 2 4 5 8 | 9 | A @1 o4 10 | 11 | ; These two notes should sound the same (from mml-syntax.md) 12 | A q4 c%24 r 13 | A q0 c%20 w%4 r 14 | 15 | A q20,8 c%48 | c%24 | r 16 | A q0 c%28 w%20 | c%9 w%11 | r 17 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/xorg/clipboard.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall::Clipboard { 6 | 7 | inline auto clear() -> void { 8 | XDisplay display; 9 | if(auto atom = XInternAtom(display, "CLIPBOARD", XlibTrue)) { 10 | XSetSelectionOwner(display, atom, XlibNone, XlibCurrentTime); 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/main.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace nall { 8 | auto main(Arguments arguments) -> void; 9 | 10 | auto main(int argc, char** argv) -> int; 11 | } 12 | 13 | #if defined(NALL_HEADER_ONLY) 14 | #include 15 | #endif 16 | -------------------------------------------------------------------------------- /examples/songs/ode-to-joy.mml: -------------------------------------------------------------------------------- 1 | #Title Ode To Joy (G Major) 2 | #Composer Ludwig van Beethoven 3 | #Tempo 144 4 | 5 | @1 triangle 6 | 7 | !repeated_notes l4 o4 b b > c d d c < b a g g a b 8 | 9 | 10 | A @1 l4 o4 v16 11 | 12 | A !repeated_notes b. a8 a2 13 | A !repeated_notes a. g8 g2 14 | A a a b g a b8 > c8 < b g a b8 > c8 < b a g a d2 15 | A !repeated_notes a. g8 g2 16 | 17 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/cd/crc16.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //CRC-16/KERMIT 4 | 5 | namespace nall::CD { 6 | 7 | inline auto CRC16(array_view data) -> u16 { 8 | u16 crc = 0; 9 | while(data) { 10 | crc ^= *data++ << 8; 11 | for(u32 bit : range(8)) { 12 | crc = crc << 1 ^ (crc & 0x8000 ? 0x1021 : 0); 13 | } 14 | } 15 | return ~crc; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /manual-tests/manual/tempo-and-timer.mml: -------------------------------------------------------------------------------- 1 | ; Song tempo test 2 | ; 3 | ; Test Instruction: 4 | ; * Manually time this song 5 | ; * Confirm it matches the reported duration. 6 | ; 7 | ; Also tests T256. 8 | 9 | #Timer 256 10 | 11 | @1 sine 12 | 13 | !s @1 c c c r 14 | 15 | A !s 16 | A T64 !s 17 | A T128 !s 18 | A T255 !s 19 | A T256 !s 20 | 21 | A t60 !s 22 | A t90 !s 23 | A t120 !s 24 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/windows/launcher.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall { 6 | 7 | //launch a new process and inject specified DLL into it 8 | 9 | auto launch(const char* applicationName, const char* libraryName, u32 entryPoint) -> bool; 10 | 11 | } 12 | 13 | #if defined(NALL_HEADER_ONLY) 14 | #include 15 | #endif 16 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/windows/guid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace nall { 6 | 7 | NALL_HEADER_INLINE auto guid() -> string { 8 | GUID guidInstance; 9 | CoCreateGuid(&guidInstance); 10 | 11 | wchar_t guidString[39]; 12 | StringFromGUID2(guidInstance, guidString, 39); 13 | 14 | return (char*)utf8_t(guidString); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /manual-tests/bug-tests/unbalanced-vibrato-bug.mml: -------------------------------------------------------------------------------- 1 | ; Quick test to confirm vibrato is balanced with 2 | ; a maximum quarter-wavelength value. 3 | 4 | #SpcSongLength 960 5 | #Timer 64 6 | #ZenLen 192 7 | 8 | @1 sine adsr 14 7 7 0 9 | 10 | A @1 v16 l4 11 | 12 | ; Using L song-loop to extend the vibrato 13 | ; so reduce test_bc_interpreter processing time. 14 | ; (this is a manual test) 15 | A ~12,128 a & L w 16 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/recompiler/amd64/amd64.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall::recompiler { 4 | struct amd64 { 5 | #include "emitter.hpp" 6 | #include "constants.hpp" 7 | #include "encoder-instructions.hpp" 8 | #if defined(PLATFORM_WINDOWS) 9 | #include "encoder-calls-windows.hpp" 10 | #else 11 | #include "encoder-calls-systemv.hpp" 12 | #endif 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /manual-tests/bug-tests/subroutine-vibrato-bug.mml: -------------------------------------------------------------------------------- 1 | ; If ~ vibrato is enabled in a subroutine and MP is disabled in channel A 2 | ; then vibrato erroneously disabled in the note after the subroutine call. 3 | ; 4 | ; Reported by KungFuFurby 5 | 6 | #ZenLen 192 7 | #Tempo 120 8 | 9 | @1 square 10 | 11 | ; The two notes after the subroutine should be played with manual vibrato 12 | !s ~250,4 13 | A @1 c !s d e 14 | 15 | -------------------------------------------------------------------------------- /crates/tad-compiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tad-compiler" 3 | description = "CLI app" 4 | license = "MIT" 5 | 6 | edition = "2021" 7 | rust-version.workspace = true 8 | 9 | publish = false 10 | 11 | # Use the same version as `tad-gui` 12 | version.workspace = true 13 | 14 | 15 | [dependencies] 16 | # Local crates 17 | compiler.workspace = true 18 | 19 | # External crates 20 | clap.workspace = true 21 | 22 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/windows/service.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | struct service { 6 | explicit operator bool() const { return false; } 7 | auto command(const string& name, const string& command) -> bool { return false; } 8 | auto receive() -> string { return ""; } 9 | auto name() const -> string { return ""; } 10 | auto stop() const -> bool { return false; } 11 | }; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /manual-tests/mml/loop.mml: -------------------------------------------------------------------------------- 1 | ; bc_interpreter loop test 2 | 3 | #Tempo 140 4 | 5 | @1 triangle 6 | 7 | A @1 v16 l16 8 | A [ [ [c d e f g]2 r32 : b4 b4]2 : a4 a4]2 b4 a4 9 | 10 | ; Used to test the "Play Song from cursor" button 11 | B @1 l32 v0 12 | B aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 13 | B aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 14 | B aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 15 | B aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 16 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/vfs/attribute.hpp: -------------------------------------------------------------------------------- 1 | namespace nall::vfs { 2 | 3 | struct attribute { 4 | attribute(const string& name, const any& value = {}) : name(name), value(value) {} 5 | auto operator==(const attribute& source) const -> bool { return name == source.name; } 6 | auto operator< (const attribute& source) const -> bool { return name < source.name; } 7 | 8 | string name; 9 | any value; 10 | }; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/vfs/vfs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | -------------------------------------------------------------------------------- /manual-tests/mml/echo-invert.mml: -------------------------------------------------------------------------------- 1 | ; This is a manual test, echo invert is confirmed using the Mesen register viewer 2 | #Title echo invert test 3 | 4 | #EchoLength 64 5 | #EchoFeedback 60 6 | #EchoVolume 70 7 | #EchoInvert right 8 | 9 | @1 triangle 10 | 11 | A @1 l8 12 | A E1 13 | 14 | ; echo invert is right 15 | A c r 16 | 17 | A \ei B c r 18 | 19 | A \ei L c r 20 | 21 | A \ei R c r 22 | 23 | A \ei M c r 24 | 25 | -------------------------------------------------------------------------------- /crates/wav2brr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wav2brr" 3 | description = "A mono PCM wav to SNES brr encoder" 4 | license = "MIT" 5 | 6 | edition = "2021" 7 | rust-version.workspace = true 8 | 9 | publish = false 10 | 11 | # Use the same version as `tad-compiler` as `tad-gui` 12 | version.workspace = true 13 | 14 | 15 | [dependencies] 16 | # Local crates 17 | brr.workspace = true 18 | 19 | # External crates 20 | clap.workspace = true 21 | 22 | -------------------------------------------------------------------------------- /examples/mml/broken-chord.mml: -------------------------------------------------------------------------------- 1 | #Title Broken-chord example 2 | 3 | #Tempo 120 4 | #ZenLen 192 5 | 6 | @1 square 7 | @2 square adsr 13 2 5 10 8 | 9 | A @1 v14 l4 10 | 11 | ; Examples from docs/mml-syntax.md 12 | A {{ceg}} w 13 | A {{ce-g}}2,32 w 14 | A {{c}}4,,0 w 15 | A {{de}}4,,0 w 16 | A {{fg}}4,%3,0 w 17 | A {{ab}}4,16,0 w 18 | 19 | ; Broken chords can be slurred. 20 | A @2 21 | A {{ceg}} & {{gbd}} & r w 22 | A {{ceg}},,0 & {{gbd}},,0 & r w 23 | -------------------------------------------------------------------------------- /audio-driver/pvsneslib-api/api-tests/tad-audio.asm: -------------------------------------------------------------------------------- 1 | ; Including `tad-audio.asm` instead of copying it so there is only 1 copy in the repo. 2 | 3 | ; Custom default values 4 | TAD_DEFAULT_FLAGS = TAD_FLAGS_RESET_GLOBAL_VOLUMES_ON_SONG_START 5 | TAD_DEFAULT_AUDIO_MODE = TAD_SURROUND 6 | TAD_DEFAULT_TRANSFER_PER_FRAME = 500 7 | 8 | .include "../tad-audio.asm" 9 | 10 | ; MUST NOT write any code after this include, the .bank value has been reset to 0 11 | 12 | -------------------------------------------------------------------------------- /examples/mml/quantize-with-temp-gain.mml: -------------------------------------------------------------------------------- 1 | #Title Quantize with temp-GAIN examples 2 | 3 | #Tempo 144 4 | #ZenLen 192 5 | 6 | @1 square adsr 10 2 4 10 7 | 8 | A @1 o4 9 | 10 | A Q4 11 | A c d d f2. c d d e2. r 12 | 13 | ; Quantize with linear decrease temp-GAIN release envelope 14 | A Q4,D10 15 | A c d d f2. c d d e2. r 16 | 17 | ; Quantize with exponential decrease temp-GAIN release envelope 18 | A Q%128,E24 19 | A c d d f2. c d d e2. r 20 | 21 | -------------------------------------------------------------------------------- /examples/mml/multi-channel-noise.mml: -------------------------------------------------------------------------------- 1 | #Title Multi-channel noise 2 | 3 | #Tempo 90 4 | 5 | @1 sine gain F127 6 | 7 | ABCDEFGH @1 8 | 9 | ; There is only 1 noise generator. 10 | ; A play-noise command will change the noise frequency on all channels playing noise. 11 | A w N16,1 c 12 | B w w N20,1 c 13 | C w w w N24,1 c 14 | D w w w w N28,1 c 15 | E w w w w w N24,1 c 16 | F w w w w w w N20,1 c 17 | G w w w w w w w N16,1 c 18 | H w w w w w w w w N12,1 c 19 | 20 | -------------------------------------------------------------------------------- /manual-tests/mml/envelope.mml: -------------------------------------------------------------------------------- 1 | ; bc_interpreter envelope test 2 | 3 | @0 square 4 | @g1 square gain F32 5 | @g2 square gain B10 6 | @a1 square adsr 14 4 6 28 7 | @a2 square adsr 2 4 5 31 8 | 9 | ; Default 10 | A @0 c2 r 11 | 12 | ; GAIN 13 | A @g1 c2 r 14 | A @g2 c2 r 15 | A G60 c2 r 16 | A GI15 c4 & GD10 r4 r 17 | A GB15 c4 & GE20 r4 r 18 | 19 | 20 | ; ADSR 21 | A @a1 c2 r 22 | A @a2 c2 r 23 | A A10,2,3,12 c2 r 24 | A A5,3,2,0 c2 r 25 | 26 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shvc-sound-emu" 3 | 4 | license = "ISC" 5 | 6 | edition = "2021" 7 | rust-version.workspace = true 8 | 9 | publish = false 10 | 11 | # This is a private internal crate. 12 | # Using version "0.0.0" as I do not care about breaking changes. 13 | version = "0.0.0" 14 | 15 | 16 | [dependencies] 17 | # External crates 18 | cxx.workspace = true 19 | 20 | [build-dependencies] 21 | cxx-build.workspace = true 22 | 23 | -------------------------------------------------------------------------------- /crates/tad-emu/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tad-emu" 3 | description = "TAD audio driver emulator" 4 | license = "MIT" 5 | 6 | edition = "2021" 7 | rust-version.workspace = true 8 | 9 | publish = false 10 | 11 | # This is a private internal crate. 12 | # Using version "0.0.0" as I do not care about breaking changes. 13 | version = "0.0.0" 14 | 15 | 16 | [dependencies] 17 | # Local crates 18 | compiler.workspace = true 19 | shvc-sound-emu.workspace = true 20 | 21 | -------------------------------------------------------------------------------- /examples/mml/pan.mml: -------------------------------------------------------------------------------- 1 | #Title pan example 2 | 3 | #Tempo 96 4 | 5 | @1 sawtooth gain 127 6 | 7 | A @1 v5 o4 8 | 9 | A p0 c2 r p64 c2 r p128 c2 10 | A r1 11 | 12 | A px-64 c2 r px0 c2 r px+64 c2 13 | A r1 14 | 15 | ; Pan can be changed in the middle of a note if the note is slurred into a pan command. 16 | A px0 c3 & px-40 w3 px+40 r3 17 | A r1 18 | 19 | ; Complex mid-note pan change that changes the pan in discrete steps. 20 | A p0 a & [ w4 p+16 ]8 w4 [ w4 p-16 ]8 r4 21 | 22 | -------------------------------------------------------------------------------- /examples/mml/chords.mml: -------------------------------------------------------------------------------- 1 | #Title Chords example 2 | 3 | @1 piano 4 | 5 | ABC @1 6 | 7 | ;; Major triad 8 | A o4 c d e f g a b r 9 | B o4 e f+ g+ a b >c+ d+ r 10 | C o4 g a b >c d e f+ r 11 | 12 | ;; Minor triad 13 | A o4 c d e f g a b r 14 | B o4 e- f g a- b- >c d r 15 | C o4 g a b >c d e f+ r 16 | 17 | ;; Augmented triad 18 | A o4 c d e f g a b r 19 | B o4 e f+ g+ a b >c+ d+ r 20 | C o4 g+ a+ >c c+ d+ f g r 21 | 22 | -------------------------------------------------------------------------------- /manual-tests/mml/loop-stack.mml: -------------------------------------------------------------------------------- 1 | ; Loop stack test 2 | ; 3 | ; Tests that a 7 nested loops work correctly on all music channels 4 | ; and the bytecode stack is not corrupted when the maximum loop depth has been reached. 5 | ; 6 | ; This test should be run with test_bc_interpreter. 7 | 8 | #Timer 64 9 | 10 | @1 triangle 11 | 12 | ABCDEFGH @1 v5 l32 13 | 14 | ABCDEFGH [[[[[[[ c ]2 ]2 ]2 ]2 ]2 ]2 ]2 15 | 16 | ABCDEFGH [[[[[[[ w%1 : w%1 ]3 : w%1 ]3 : c ]3 : d ]3 : e ]3 : f ]3 : g ]3 17 | -------------------------------------------------------------------------------- /examples/mml/continuous-portamento.mml: -------------------------------------------------------------------------------- 1 | #Title Continuous portamento example 2 | #Tempo 120 3 | #ZenLen 192 4 | 5 | @1 sine gain F127 6 | 7 | A @1 o5 8 | 9 | ; Continuous portamento using portamento delay (hold then pitch-slide) 10 | A {cb}4,8 & {e}4,8. & {d}4,8. & {c}4,8. & 11 | A [ {cg}4,8. & {c}4,8. & ]3 r 12 | 13 | A r 14 | 15 | ; Continuous portamento using ties (pitch-slide then hold) 16 | A c8 & {b}8^8. & {e}16^8. & {d}16^8. & {c}16 & 17 | A [ c8. & {g}16^8. & {c}16 & ]3 r 18 | 19 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/inode.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace nall { 4 | 5 | NALL_HEADER_INLINE auto inode::hidden(const string& name) -> bool { 6 | #if defined(PLATFORM_WINDOWS) 7 | auto attributes = GetFileAttributes(utf16_t(name)); 8 | return attributes & FILE_ATTRIBUTE_HIDDEN; 9 | #else 10 | //todo: is this really the best way to do this? stat doesn't have S_ISHIDDEN ... 11 | return name.split("/").last().beginsWith("."); 12 | #endif 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/xorg/xorg.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct XDisplay { 12 | XDisplay() { _display = XOpenDisplay(nullptr); } 13 | ~XDisplay() { XCloseDisplay(_display); } 14 | operator XlibDisplay*() const { return _display; } 15 | 16 | private: 17 | XlibDisplay* _display; 18 | }; 19 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/spc700/memory.cpp: -------------------------------------------------------------------------------- 1 | inline auto SPC700::fetch() -> n8 { 2 | return read(PC++); 3 | } 4 | 5 | inline auto SPC700::load(n8 address) -> n8 { 6 | return read(PF << 8 | address); 7 | } 8 | 9 | inline auto SPC700::store(n8 address, n8 data) -> void { 10 | return write(PF << 8 | address, data); 11 | } 12 | 13 | inline auto SPC700::pull() -> n8 { 14 | return read(1 << 8 | ++S); 15 | } 16 | 17 | inline auto SPC700::push(n8 data) -> void { 18 | return write(1 << 8 | S--, data); 19 | } 20 | -------------------------------------------------------------------------------- /manual-tests/bug-tests/vibrato-after-portamento.mml: -------------------------------------------------------------------------------- 1 | ; Vibrato after portamento bugfix test 2 | ; 3 | ; If vibrato is active and a portamento ends without a new play_note 4 | ; instruction the resumed vibrato was not reset and vibrato's mid-point 5 | ; was not the intended pitch. 6 | ; 7 | ; Manually tested using Tenacity's spectrogram viewer. 8 | 9 | #Tempo 120 10 | #ZenLen 192 11 | 12 | @1 square gain F127 13 | 14 | A @1 v16 l4 15 | 16 | ; The r2 and b2 should sound the same 17 | A ~80,7 c & {cb}3 & r2 | b2 18 | 19 | -------------------------------------------------------------------------------- /manual-tests/bug-tests/volume-slide-down-bug.mml: -------------------------------------------------------------------------------- 1 | ; This manual (audible) test is used to confirm volume-slide down 2 | ; does not overshoot and finishes at the correct volume. 3 | 4 | #Title Volume slide down bugtest 5 | #Tempo 120 6 | 7 | @1 square gain F127 8 | 9 | A @1 10 | 11 | ; These two notes should end with the same volume 12 | ; (using V4 as that is the smallest audible centered value) 13 | A V50 Vs-46,10 d2 r8 | V4 d2 r8 14 | 15 | ; These two notes should end with the same volume 16 | A V20 Vs+80,20 e3 r8 | V100 e3 r8 17 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/dsp/iir/dc-removal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //DC offset removal IIR filter 4 | 5 | namespace nall::DSP::IIR { 6 | 7 | struct DCRemoval { 8 | auto reset() -> void; 9 | auto process(f64 in) -> f64; //normalized sample (-1.0 to +1.0) 10 | 11 | private: 12 | f64 x; 13 | f64 y; 14 | }; 15 | 16 | inline auto DCRemoval::reset() -> void { 17 | x = 0.0; 18 | y = 0.0; 19 | } 20 | 21 | inline auto DCRemoval::process(f64 in) -> f64 { 22 | x = 0.999 * x + in - y; 23 | y = in; 24 | return x; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/string/atoi.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | inline auto string::boolean() const -> bool { 6 | return equals("true"); 7 | } 8 | 9 | inline auto string::integer() const -> s64 { 10 | return toInteger(data()); 11 | } 12 | 13 | inline auto string::natural() const -> u64 { 14 | return toNatural(data()); 15 | } 16 | 17 | inline auto string::hex() const -> u64 { 18 | return toHex(data()); 19 | } 20 | 21 | inline auto string::real() const -> f64 { 22 | return toReal(data()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/vector/compare.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template auto vector::operator==(const vector& source) const -> bool { 6 | if(this == &source) return true; 7 | if(size() != source.size()) return false; 8 | for(u64 n = 0; n < size(); n++) { 9 | if(operator[](n) != source[n]) return false; 10 | } 11 | return true; 12 | } 13 | 14 | template auto vector::operator!=(const vector& source) const -> bool { 15 | return !operator==(source); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /manual-tests/mml/set-echo-delay.mml: -------------------------------------------------------------------------------- 1 | ; bc_interpreter echo test 2 | 3 | #MaxEchoLength 128 4 | #EchoFeedback 70 5 | #EchoVolume 60 6 | 7 | @1 square 8 | 9 | AH l8 10 | 11 | !restore_efb_evol \efb 70 \evol 60 12 | !change_edl_in_subroutine \evol 0 \efb 0 \edl 32 r1 !restore_efb_evol 13 | 14 | A @1 v16 l8 o4 15 | 16 | A E1 17 | A cdefg r 18 | 19 | A \evol 0 \efb 0 \edl 64 r1 !restore_efb_evol 20 | A cdefg r2 21 | 22 | A \evol 0 \efb 0 \edl 128 r1 !restore_efb_evol 23 | A cdefg r2 24 | 25 | A !change_edl_in_subroutine 26 | A cdefg r 27 | 28 | -------------------------------------------------------------------------------- /examples/mml/chords-transpose.mml: -------------------------------------------------------------------------------- 1 | ; Example that uses the `_` transpose command to simplify chords. 2 | ; 3 | ; This example produces identical output to `chords.mml`. 4 | 5 | #Title Chords using transpose example 6 | 7 | @1 piano 8 | 9 | ABC @1 10 | 11 | ;; Major triad 12 | A _+0 13 | B _+4 14 | C _+7 15 | ABC o4 c d e f g a b r 16 | 17 | ;; Minor triad 18 | A _+0 19 | B _+3 20 | C _+7 21 | ABC o4 c d e f g a b r 22 | 23 | ;; Augmented triad 24 | A _+0 25 | B _+4 26 | C _+8 27 | ABC o4 c d e f g a b r 28 | 29 | ; Reset transpose 30 | ABC _+0 31 | 32 | -------------------------------------------------------------------------------- /examples/mml/play-pitch.mml: -------------------------------------------------------------------------------- 1 | #Title Play pitch example 2 | 3 | @1 square 4 | 5 | A @1 6 | 7 | ; Play at native sample rate 8 | A P$1000 P$1000,8 P$1000,8 P$1000,8 r8 9 | 10 | ; Play at double native sample rate 11 | A P$2000 P$2000,8 P$2000,8 P$2000,8 r8 12 | 13 | ; Play at quarter native sample rate 14 | A P$400 P$400,8 P$400,8 P$400,8 r8 15 | 16 | ; Quantization can be applied to play-pitch 17 | A Q4 P$800,2 P$800,4 r8 18 | A Q4,E18 P$800,2 P$800,4 r8 19 | 20 | 21 | ; PF plays an instrument at a given frequency (in Hertz) 22 | A GF127 Q8 23 | A PF600,2 r8 24 | 25 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/encode/html.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall::Encode { 4 | 5 | inline auto HTML(const string& input) -> string { 6 | string output; 7 | for(char c : input) { 8 | if(c == '&' ) { output.append("&" ); continue; } 9 | if(c == '<' ) { output.append("<" ); continue; } 10 | if(c == '>' ) { output.append(">" ); continue; } 11 | if(c == '"' ) { output.append("""); continue; } 12 | if(c == '\'') { output.append("'"); continue; } 13 | output.append(c); 14 | } 15 | return output; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/decode/mtf.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //move to front 4 | 5 | namespace nall::Decode { 6 | 7 | inline auto MTF(array_view input) -> vector { 8 | vector output; 9 | output.resize(input.size()); 10 | 11 | u8 order[256]; 12 | for(u32 n : range(256)) order[n] = n; 13 | 14 | for(u32 offset : range(input.size())) { 15 | u32 data = input[offset]; 16 | u32 value = order[data]; 17 | output[offset] = value; 18 | memory::move(&order[1], &order[0], data); 19 | order[0] = value; 20 | } 21 | 22 | return output; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /examples/mml/play-noise.mml: -------------------------------------------------------------------------------- 1 | #Title Play noise example 2 | 3 | @1 sine gain F127 4 | 5 | ; Must set an instrument before using noise 6 | ABCDEFGH @1 7 | 8 | A N 0 N 1 N 2 N 3 N 4 N 5 N 6 N 7 9 | A N 8 N 9 N10 N11 N12 N13 N14 N15 10 | A N16 N17 N18 N19 N20 N21 N22 N23 11 | A N24 N25 N26 N27 N28 N29 N30 N31 12 | A r 13 | 14 | ; Noise with length 15 | A N$10,4 N$15,8 N$1a,16 N$1f,24 r 16 | 17 | ; Noise stops when a note is played 18 | A N16 c d r 19 | 20 | ; Noise can be slurred like a regular note 21 | A N15,8 & N16,8 & N17,8 & N18,8 N19,8 22 | 23 | ; N- disables noise 24 | A d4 & N16,2 & N- w4 r 25 | 26 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tad-fuzz" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | libfuzzer-sys = "0.4" 12 | 13 | [workspace] 14 | members = ["."] 15 | 16 | [dependencies.compiler] 17 | path = "../crates/compiler" 18 | 19 | [[bin]] 20 | name = "fuzz_bytecode_sfx" 21 | path = "fuzz_targets/fuzz_bytecode_sfx.rs" 22 | test = false 23 | doc = false 24 | bench = false 25 | 26 | [[bin]] 27 | name = "fuzz_mml_song" 28 | path = "fuzz_targets/fuzz_mml_song.rs" 29 | test = false 30 | doc = false 31 | bench = false 32 | 33 | -------------------------------------------------------------------------------- /manual-tests/bug-tests/subroutine-cursor-tick-count-bug.mml: -------------------------------------------------------------------------------- 1 | ; BUGFIX test for: cursor position in MML subroutine not correct after end-loop command 2 | 3 | ; These three lines must have the same MML status bar tick count for each line char position 4 | !s1 @1 [a]2 !c \asm { play_note d4 24 } [ !c : !c ]2 [ !c \asm { play_note f4 12 | play_note g4 12 } ]2 !c q8,D8 5 | !s2 @1 [a]2 !c \asm { play_note d4 24 } [ !c : !c ]2 [ !c \asm { play_note f4 12 | play_note g4 12 } ]2 !c 6 | A @1 [a]2 !c \asm { play_note d4 24 } [ !c : !c ]2 [ !c \asm { play_note f4 12 | play_note g4 12 } ]2 !c 7 | 8 | @1 sine 9 | !c c 10 | A !s1 !s2 11 | -------------------------------------------------------------------------------- /audio-driver/64tass-api/test/ca65/minimal.cfg: -------------------------------------------------------------------------------- 1 | # minimal ca65 config 2 | 3 | MEMORY { 4 | ZEROPAGE: start = $000000, size = $100; 5 | STACK: start = $000100, size = $100, define = yes; 6 | BSS: start = $7e0200, size = $1e00; 7 | WRAM7E: start = $7e2000, size = $e000; 8 | 9 | ROM0: start = $808000, size = $8000, type = ro, file = %O; 10 | } 11 | 12 | SEGMENTS { 13 | CODE: load = ROM0, type = ro; 14 | 15 | ZEROPAGE: load = ZEROPAGE, type = zp; 16 | BSS: load = BSS, type = bss; 17 | WRAM7E: load = WRAM7E, type = bss, optional=yes; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/smp/smp.cpp: -------------------------------------------------------------------------------- 1 | namespace shvc_sound_emu { 2 | 3 | #include "memory.cpp" 4 | #include "io.cpp" 5 | #include "timing.cpp" 6 | 7 | auto SMP::main() -> void { 8 | // ::TODO verify Wait and Stop will advance the DSP:: 9 | if(r.wait) return instructionWait(); 10 | if(r.stop) return instructionStop(); 11 | 12 | instruction(); 13 | } 14 | 15 | auto SMP::power(bool reset) -> void { 16 | SPC700::power(); 17 | 18 | dsp.power(reset); 19 | 20 | r.pc.byte.l = iplrom[62]; 21 | r.pc.byte.h = iplrom[63]; 22 | 23 | io = {}; 24 | timer0 = {}; 25 | timer1 = {}; 26 | timer2 = {}; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /crates/tad-gui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tad-gui" 3 | description = "GUI app" 4 | license = "MIT" 5 | 6 | edition = "2021" 7 | rust-version.workspace = true 8 | 9 | publish = false 10 | 11 | # Use the same version as `tad-compiler` 12 | version.workspace = true 13 | 14 | 15 | [dependencies] 16 | # Local crates 17 | compiler = { workspace = true } 18 | brr.workspace = true 19 | tad-emu.workspace = true 20 | 21 | # External crates 22 | fltk.workspace = true 23 | sdl2.workspace = true 24 | spectrum-analyzer.workspace = true 25 | 26 | [build-dependencies] 27 | markdown.workspace = true 28 | regex.workspace = true 29 | 30 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/inline-if.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #warning "these defines break if statements with multiple parameters to templates" 3 | 4 | #define if1(statement) if(statement) 5 | #define if2(condition, false) ([&](auto&& value) -> decltype(condition) { \ 6 | return (bool)value ? value : (decltype(condition))false; \ 7 | })(condition) 8 | #define if3(condition, true, false) ((condition) ? (true) : (decltype(true))(false)) 9 | #define if4(type, condition, true, false) ((condition) ? (type)(true) : (type)(false)) 10 | #define if_(_1, _2, _3, _4, name, ...) name 11 | #define if(...) if_(__VA_ARGS__, if4, if3, if2, if1)(__VA_ARGS__) 12 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # cargo-deny configuration 2 | 3 | [licenses] 4 | version = 2 5 | 6 | confidence-threshold = 0.93 7 | 8 | allow = [ 9 | "MIT", 10 | "ISC", 11 | "Zlib", 12 | "Apache-2.0", 13 | "Unicode-3.0", 14 | ] 15 | 16 | 17 | [bans] 18 | multiple-versions = "deny" 19 | wildcards = "deny" 20 | 21 | deny = [] 22 | 23 | skip = [ 24 | { name="bitflags", version = "1.3", reason = "used by sdl2" }, 25 | { name="bitflags", version = "2.4", reason = "used by fltk" }, 26 | ] 27 | 28 | 29 | [advisories] 30 | version = 2 31 | yanked = "deny" 32 | 33 | 34 | [sources] 35 | unknown-registry = "deny" 36 | unknown-git = "deny" 37 | 38 | 39 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/cd/sync.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall::CD::Sync { 4 | 5 | inline auto create(array_span sector) -> bool { 6 | if(sector.size() != 12 && sector.size() != 2352) return false; 7 | 8 | for(u32 n : range(12)) { 9 | sector[n] = ((n == 0 || n == 11) ? 0x00 : 0xff); 10 | } 11 | 12 | return true; 13 | } 14 | 15 | // 16 | 17 | inline auto verify(array_view sector) -> bool { 18 | if(sector.size() != 12 && sector.size() != 2352) return false; 19 | 20 | for(u32 n : range(12)) { 21 | if(sector[n] != ((n == 0 || n == 11) ? 0x00 : 0xff)) return false; 22 | } 23 | 24 | return true; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/property.hpp: -------------------------------------------------------------------------------- 1 | #if !defined(property) 2 | #define property1(declaration) public: declaration 3 | #define property2(declaration, getter) public: __declspec(property(get=getter)) declaration; protected: declaration##_ 4 | #define property3(declaration, getter, setter) public: __declspec(property(get=getter, put=setter)) declaration; protected: declaration##_ 5 | #define property_(_1, _2, _3, name, ...) name 6 | #define property(...) property_(__VA_ARGS__, property3, property2, property1)(__VA_ARGS__) 7 | #else 8 | #undef property1 9 | #undef property2 10 | #undef property3 11 | #undef property_ 12 | #undef property 13 | #endif 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Licensing 2 | ========= 3 | 4 | The terrific audio driver is copyright (c) 2023, Marcus Rowe. 5 | 6 | See [docs/licenses.md](docs/licenses.md) for full license text. 7 | 8 | * The audio driver (S-SMP and `.spc` code) is licensed under the [zlib License](audio-driver/LICENSE). 9 | * The compiler and GUI are licensed under the [MIT License](crates/tad-compiler/LICENSE). 10 | * The audio emulator used by the GUI is licensed under the [ISC License](crates/shvc-sound-emu/LICENSE). 11 | 12 | * The compiler and GUI make use of multiple third party open source projects. 13 | * The GUI is based in part on the work of the FLTK project (https://www.fltk.org). 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/mml/portamento.mml: -------------------------------------------------------------------------------- 1 | #Title Portamento examples 2 | 3 | @1 square gain 127 4 | 5 | A @1 v12 l4 6 | 7 | A {cg}1 r 8 | 9 | A {df}4,8 r d8 & {df}8 r 10 | 11 | A {df}4 ^2 r 12 | 13 | A {df}4,,100 r 14 | 15 | A {a > c}2 r 16 | 17 | A {o3 c o4 c}2 & w {o4 c o3 c}2 & w r 18 | 19 | 20 | ; `P` play-pitch can be used in portamento 21 | A {P$1000 P$2000}2 & w {P$2000 P$1000}2 & w r 22 | 23 | A {P$1000 o4 g}8. & w8 r 24 | A {P$0200 o4 c}8. & w8 r 25 | A {c P$1000}8. & w8 r 26 | 27 | 28 | ; pitch1 is optional if the previous note is slurred and known 29 | A o3 d & {g}4 & {c}4 & {f}6 & {g}6 & {d}8 r 30 | 31 | ; PF in portamento test 32 | A {PF200 PF800}2,8 & r8 r 33 | 34 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/windows/registry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace nall { 7 | 8 | struct registry { 9 | static auto exists(const string& name) -> bool; 10 | 11 | static auto read(const string& name) -> string; 12 | 13 | static auto write(const string& name, const string& data = "") -> void; 14 | 15 | static auto remove(const string& name) -> bool; 16 | 17 | static auto contents(const string& name) -> vector; 18 | 19 | private: 20 | static auto root(const string& name); 21 | }; 22 | 23 | } 24 | 25 | #if defined(NALL_HEADER_ONLY) 26 | #include 27 | #endif 28 | -------------------------------------------------------------------------------- /audio-driver/pvsneslib-api/api-tests/Makefile: -------------------------------------------------------------------------------- 1 | 2 | ifeq ($(strip $(PVSNESLIB_HOME)),) 3 | $(error "PVSNESLIB_HOME is not defined. Please create a PVSNESLIB_HOME environment variable using this guide: https://github.com/alekmaul/pvsneslib/wiki/Installation") 4 | endif 5 | 6 | SRC := ./src 7 | ROMNAME := pv-unit-tests 8 | 9 | include ${PVSNESLIB_HOME}/devkitsnes/snes_rules 10 | 11 | 12 | #--------------------------------------------------------------------------------- 13 | 14 | 15 | .PHONY: all 16 | all: $(ROMNAME).sfc 17 | 18 | clean: cleanBuildRes cleanRom cleanGfx 19 | 20 | 21 | # Dependency graph 22 | $(CFILES:.c=.obj): ../tad-audio.h 23 | tad-audio.obj: ../tad-audio.asm 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/mml/echo.mml: -------------------------------------------------------------------------------- 1 | #Title Echo volume and feedback examples 2 | 3 | #EchoLength 240 4 | #EchoFeedback 80 5 | #EchoVolume 100 6 | 7 | @1 square 8 | 9 | AH l8 10 | 11 | A @1 v16 l8 o4 12 | 13 | A E1 14 | A cdefg r1 15 | H wwwww w1 16 | 17 | ; Incrementing echo volume over multiple ticks 18 | ; Using channel H so it can be ducked by sfx without ducking notes (H is silent) 19 | A cdefg r1 20 | H \evol 0 [ w%1 \evol +1]120 w%36 21 | 22 | ; Moving echo 23 | ; (This echo is constant in mono) 24 | A cdefg r1 25 | H \evol 120,0 w%36 [ w%1 \evol -2,+2]60 w%60 26 | 27 | H \evol 80 28 | 29 | 30 | ; Slowly decrease echo feedback 31 | A c^^^^ r1 32 | H \efb 100 w%12 [ w%1 \efb- 1]60 w%84 33 | 34 | -------------------------------------------------------------------------------- /manual-tests/mml/invert.mml: -------------------------------------------------------------------------------- 1 | #Title Invert example 2 | 3 | @1 sine adsr 14 7 7 0 4 | 5 | AB @1 v14 6 | 7 | ; Invert both channels on A and B 8 | A i c r 9 | B i c r 10 | 11 | ; CAUTION: invert can cause destructive interference 12 | ; (This example is silent) 13 | A i c i0 c 14 | B i0 c i c 15 | 16 | 17 | ; Single character invert commands 18 | ; 0 = disable invert 19 | ; B = invert both channels 20 | A i0 c 21 | A iB c 22 | 23 | ; Multiple character invert commands 24 | ; L = left 25 | ; R = right 26 | ; M = invert if in mono mode 27 | A i0 c 28 | A iL c 29 | A i0 c 30 | A iR c 31 | A i0 c 32 | A iLR c 33 | A i0 c 34 | A iM c 35 | A iLM c 36 | A iRM c 37 | A iLRM c 38 | 39 | -------------------------------------------------------------------------------- /examples/mml/fir.mml: -------------------------------------------------------------------------------- 1 | #Title FIR filter command example 2 | 3 | #EchoLength 128 4 | #EchoFeedback 80 5 | #EchoVolume 70 6 | 7 | #FirFilter 32 32 32 16 0 0 0 0 8 | 9 | @1 square 10 | 11 | AG l8 12 | A @1 v16 o4 13 | 14 | 15 | ;; Set FIR test 16 | A E 17 | A cd r fd r dc r1 18 | 19 | A \fir { 48 48 0 0 0 0 -32 -32 } 20 | A cd r fd r dc r1 21 | 22 | G w%384 23 | 24 | ;; With echo disabled 25 | A E0 26 | A cd r fd r dc r1 27 | 28 | G \fir { 0 0 0 0 0 0 0 0 } 29 | G w%192 30 | 31 | 32 | ;; Set FIR test 33 | A E1 34 | A cd r fd r dc r1 35 | ; FIR is all zeros 36 | G \ftap 2,20 www \ftap 4,80 www ww w1 37 | 38 | ;; Adjust FIR test 39 | A cd r fd r dc r1 40 | G [w%8 \ftap+ 0,4 \ftap- 3,1 ]20 w%32 41 | 42 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/encode/mtf.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //move to front 4 | 5 | namespace nall::Encode { 6 | 7 | inline auto MTF(array_view input) -> vector { 8 | vector output; 9 | output.resize(input.size()); 10 | 11 | u8 order[256]; 12 | for(u32 n : range(256)) order[n] = n; 13 | 14 | for(u32 offset : range(input.size())) { 15 | u32 data = input[offset]; 16 | for(u32 index : range(256)) { 17 | u32 value = order[index]; 18 | if(value == data) { 19 | output[offset] = index; 20 | memory::move(&order[1], &order[0], index); 21 | order[0] = value; 22 | break; 23 | } 24 | } 25 | } 26 | 27 | return output; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/tcptext/tcptext-server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * Provides text-based TCP server on top of the Socket. 7 | * This handles incoming messages and can send data back to the client. 8 | */ 9 | namespace nall::TCPText { 10 | 11 | class Server : public TCP::Socket { 12 | public: 13 | bool hadHandshake{false}; 14 | 15 | protected: 16 | auto onData(const vector &data) -> void override; 17 | 18 | auto sendText(const string &text) -> void; 19 | virtual auto onText(string_view text) -> void = 0; 20 | }; 21 | 22 | } 23 | 24 | #if defined(NALL_HEADER_ONLY) 25 | #include 26 | #endif 27 | -------------------------------------------------------------------------------- /examples/mml/panbrello.mml: -------------------------------------------------------------------------------- 1 | #Title Panbrello example 2 | 3 | #Tempo 120 4 | 5 | @1 square gain F127 6 | 7 | A @1 8 | 9 | A p~50,10 c1 r4 10 | 11 | A px0 p~64,50 [c8 d8]25 r4 12 | 13 | ; Panbrello is disabled on overflow 14 | A px+50 p~15,10 c4 r4 15 | 16 | ; Tremolo is disabled on underflow 17 | A px-50 p~15,10 c2 r4 18 | 19 | ; Changing pan disables panbrello 20 | A px0 p~15,10 c2 px0 c4 r4 21 | 22 | A px0 p~15,10 c2 px+4 c4 r4 23 | 24 | ; Test set_pan_and_volume disables panbrello 25 | A px0 p~15,2 c2 \asm { set_pan_and_volume 64 64 } c4 r4 26 | 27 | 28 | ; The ticks argument is an MML length when prefixed with `l` 29 | ; (these two lines should sound the same) 30 | A px0 p~32,l16 c1 r4 31 | A px0 p~32,6 c1 r4 32 | 33 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/counting-sort.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall { 6 | 7 | //counting sort by powers of two: used to implement radix sort 8 | template 9 | auto counting_sort(T* output, const T* input, u32 size) -> void { 10 | static_assert(Bits >= 1 && Bits <= 20, "must be between 1 and 20 bits"); 11 | enum : u32 { Base = 1 << Bits, Mask = Base - 1 }; 12 | 13 | u64 count[Base] = {}, last = 0; 14 | for(u32 n : range(size)) ++count[(input[n] >> Shift) & Mask]; 15 | for(u32 n : range(Base)) last += count[n], count[n] = last - count[n]; 16 | for(u32 n : range(size)) output[count[(input[n] >> Shift) & Mask]++] = input[n]; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /examples/mml/pan-slide.mml: -------------------------------------------------------------------------------- 1 | #Title Pan slide examples 2 | 3 | #Tempo 120 4 | 5 | @1 square gain F127 6 | 7 | A @1 8 | 9 | ; Pan slide up. left to right 10 | A p0 ps+128,60 c1 r4 11 | 12 | ; Pan slide down. right to center 13 | A ps-64,60 c1 r4 14 | 15 | ; Pan slide up stops on overflow 16 | A px+48 ps+64,80 c1 r4 17 | 18 | ; Pan slide down stops on underflow 19 | A px-48 ps-64,80 c1 r4 20 | 21 | ; Pan slide stops when pan changes 22 | A px-64 ps+128,100 c4 & px0 c4 r4 23 | 24 | A px-64 ps+128,100 c4 & p+1 c4 r4 25 | 26 | ; Test set_pan_and_volume disables pan slide 27 | A px0 ps+100,200 c2 \asm { set_pan_and_volume 32 128 } c4 r4 28 | 29 | ; The ticks argument is an MML length when prefixed with `l` 30 | A p0 ps+128,l2 c2 r4 31 | -------------------------------------------------------------------------------- /manual-tests/manual/noise-volume-restored-test.mml: -------------------------------------------------------------------------------- 1 | #Title Noise timing test (channel A) 2 | 3 | #Timer 96 4 | 5 | ; Test instructions 6 | ; 7 | ; 1. Record tad-gui audio output 8 | ; 2. Play this song in the GUI 9 | ; 3. Use the Sound Effects window to play the following sound effects: 10 | ; * noise 11 | ; * noise_no_keyoff 12 | ; * noise_rest 13 | ; 4. Review the tad-gui audio output to verify: 14 | ; * Music noise is restored shortly after the sound effect stops 15 | ; playing noise. 16 | ; * The music noise volume before and after the SFX is the same 17 | 18 | 19 | @1 sine 20 | 21 | ; music is on the right 22 | ; sfx is on the left 23 | BDE px+64 24 | 25 | ; 3 channels all playing noise 26 | BDE @1 v2 N22,%600 27 | 28 | -------------------------------------------------------------------------------- /manual-tests/manual/vibrato-with-delay.mml: -------------------------------------------------------------------------------- 1 | ; Vibrato with delay test 2 | ; 3 | ; Test Instruction: 4 | ; * Record this song with an audio recorder 5 | ; * Inspect the recording with a spectrogram viewer with a small window size 6 | ; * Count the number of ticks before the VxPITCH changes 7 | ; * Confirm they match the numbers below 8 | 9 | #Title Vibrato with delay test 10 | #Timer 256 11 | 12 | @1 sine gain F127 13 | 14 | A px-64 v10 @1 15 | A ~180,2 c r%2 ; 1 c4 tick before VxPITCH changes 16 | A ~180,2,1 c r%2 ; 2 c4 ticks 17 | A ~180,2,2 c r%2 ; 3 c4 ticks 18 | A ~180,2,12 c r%2 ; 13 c4 ticks 19 | A ~180,2,50 c r%2 ; no vibrato 20 | 21 | ; The right channel is the tick clock 22 | B px+64 v4 @1 23 | B [ c%1& v0 w%1 v4 ]65 24 | -------------------------------------------------------------------------------- /manual-tests/mml/channel-transpose.mml: -------------------------------------------------------------------------------- 1 | #Title Channel transpose test 2 | #Tempo 120 3 | 4 | ; Sets the default channel transpose for all channels and subroutines 5 | ; This is the equivalent to adding `_M +2` to the start of every channel and subroutine. 6 | #Transpose +2 7 | 8 | @1 square 9 | 10 | A @1 11 | 12 | A c ; plays d (from #Transpose header) 13 | A _M0 r 14 | 15 | A _M0 c [__M+1 c]3 r 16 | 17 | A _M+2 c 18 | A _M-2 c 19 | A __M+3 c 20 | A __M-4 c 21 | A r 22 | 23 | A _M+1 24 | A !s 25 | A c ; plays c+ (from _M+1 before the subroutine call) 26 | A r 27 | 28 | 29 | ; Subroutines have independent _M channel transpose 30 | !s c ; plays d (from #Transpose header) 31 | !s _0 r 32 | !s _M0 c [__M+1 c]3 r 33 | !s __M+2 c __M-6 c r 34 | 35 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/sample-buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace shvc_sound_emu { 4 | 5 | struct SampleBuffer { 6 | constexpr static u32 N_SAMPLES = 256; 7 | constexpr static u32 MASK = N_SAMPLES * 2 - 2; 8 | 9 | auto reset() -> void { 10 | pos = 0; 11 | } 12 | 13 | auto write(i16 left, i16 right) -> void { 14 | pos &= MASK; 15 | 16 | buffer.at(pos) = left; 17 | buffer.at(pos + 1) = right; 18 | 19 | pos += 2; 20 | } 21 | 22 | auto isFull() -> bool { 23 | return pos > MASK; 24 | } 25 | 26 | auto samples() const -> const std::array& { 27 | return buffer; 28 | } 29 | 30 | private: 31 | std::array buffer; 32 | u32 pos = MASK; 33 | }; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /examples/mml/bc-asm-in-mml.mml: -------------------------------------------------------------------------------- 1 | #Title Bytecode assembly in MML example 2 | 3 | #Tempo 120 4 | 5 | 6 | A \asm { 7 | set_instrument_and_adsr triangle 10 2 5 10 8 | set_vibrato 15 2 9 | } 10 | 11 | A \asm { 12 | ; Using temp GAIN to create a custom envelope for multiple slurred notes 13 | set_temp_gain I12 14 | play_note d5 no_keyoff 48 15 | set_temp_gain_and_wait D6 24 16 | play_note e5 no_keyoff 72 17 | set_temp_gain I6 18 | play_note d5 no_keyoff 48 19 | set_temp_gain_and_rest E18 72 20 | } 21 | 22 | A \asm { 23 | ; Using temp GAIN for a custom release envelope 24 | play_note d5 no_keyoff 36 | set_temp_gain_and_rest D10 36 25 | play_note d5 no_keyoff 36 | reuse_temp_gain_and_rest 36 26 | play_note d5 36 27 | } 28 | -------------------------------------------------------------------------------- /manual-tests/manual/mml-subroutine-play-at-cursor.mml: -------------------------------------------------------------------------------- 1 | ; bc_interpreter subroutine test 2 | 3 | #Tempo 140 4 | 5 | @1 square 6 | 7 | ; Used to test `?@` instrument hint preview 8 | @2 sine 9 | @3 triangle 10 | @4 sawtooth 11 | 12 | 13 | !1 l8 cd !2 ab 14 | !2 ?@2 l8 efg 15 | !3 ?@3 l8 ba !4 dc 16 | !4 ?@4 l8 gfe 17 | 18 | A @1 l8 v16 19 | A !1 !2 r !3 !4 r !1 !2 r !3 !4 r !1 !2 r !3 !4 r 20 | 21 | ; Used to test the "Play Song from cursor" button 22 | B @1 l32 v0 23 | B aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 24 | B aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 25 | B aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 26 | B aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 27 | B aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 28 | B aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 29 | -------------------------------------------------------------------------------- /examples/mml/tremolo.mml: -------------------------------------------------------------------------------- 1 | #Title Tremolo test 2 | 3 | #Tempo 120 4 | 5 | @1 square gain F127 6 | 7 | A @1 8 | 9 | A v8 v~2,3 c1 r4 10 | 11 | A V128 V~80,40 [c8 d8]25 r4 12 | 13 | ; Tremolo is disabled on overflow 14 | A V200 V~60,10 c4 r4 15 | 16 | ; Tremolo is disabled on underflow 17 | A V40 V~80,10 c2 18 | 19 | ; Changing volume disables tremolo 20 | A v4 c16 21 | 22 | ; Test set_pan_and_volume disables tremolo 23 | A v8 v~5,10 c2 \asm { set_pan_and_volume 64 128 } c4 r4 24 | 25 | ; Largest tremolo values 26 | A V127 V~127,127 [c8 d8]48 r4 27 | 28 | ; The ticks argument is an MML length when prefixed with `l` 29 | ; (these two lines should sound the same) 30 | A v8 v~2,l16 c1 r4 31 | A v8 v~2,6 c1 r4 32 | 33 | A V127 V~32,l24 c1 r4 34 | A V127 V~32,4 c1 r4 35 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/terminal.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace nall::terminal { 4 | 5 | NALL_HEADER_INLINE auto redirectStdioToTerminal(bool create) -> void { 6 | #if defined(PLATFORM_WINDOWS) 7 | if(create) { 8 | FreeConsole(); 9 | if(!AllocConsole()) return; 10 | } else if(!AttachConsole(ATTACH_PARENT_PROCESS)) { 11 | return; 12 | } 13 | 14 | //unless a new terminal was requested, do not reopen already valid handles (allow redirection to/from file) 15 | if(create || _get_osfhandle(_fileno(stdin )) < 0) freopen("CONIN$" , "r", stdin ); 16 | if(create || _get_osfhandle(_fileno(stdout)) < 0) freopen("CONOUT$", "w", stdout); 17 | if(create || _get_osfhandle(_fileno(stderr)) < 0) freopen("CONOUT$", "w", stderr); 18 | #endif 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /examples/mml/volume-slide.mml: -------------------------------------------------------------------------------- 1 | #Title Volume slide examples 2 | 3 | #Tempo 120 4 | 5 | @1 square gain F127 6 | 7 | A @1 8 | 9 | ; Volume slide up. 0 to max 10 | A v0 vs+16,60 c1 r4 11 | 12 | ; Volume slide down. max to half 13 | A v16 vs-8,60 c1 r4 14 | 15 | ; Volume slide up stops on overflow 16 | A V192 Vs+200,80 c1 r4 17 | 18 | ; Volume slide down stops on overflow 19 | A V128 Vs-200,80 c1 r4 20 | 21 | ; Volume slide stops when volume changes 22 | A V128 Vs+200,80 c4 & V+0 c4 r4 23 | 24 | ; Volume slide stops when volume changes 25 | A V128 Vs-200,80 c4 & V32 c4 r4 26 | 27 | ; Test set_pan_and_volume disables volume slide 28 | A V50 Vs+200,200 c2 \asm { set_pan_and_volume 64 40 } c4 r4 29 | 30 | ; The ticks argument is an MML length when prefixed with `l` 31 | A v0 vs+16,l2 c1 & Vs-255,l2 r2 32 | 33 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/instance.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template 6 | struct Instance { 7 | ~Instance() { 8 | destruct(); 9 | } 10 | 11 | auto operator()() -> T& { 12 | return instance.object; 13 | } 14 | 15 | template 16 | auto construct(P&&... p) { 17 | if(constructed) return; 18 | constructed = true; 19 | new((void*)(&instance.object)) T(std::forward

(p)...); 20 | } 21 | 22 | auto destruct() -> void { 23 | if(!constructed) return; 24 | constructed = false; 25 | instance.object.~T(); 26 | } 27 | 28 | private: 29 | bool constructed = false; 30 | union Union { 31 | Union() {} 32 | ~Union() {} 33 | 34 | T object; 35 | char storage[sizeof(T)]; 36 | } instance; 37 | }; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/algorithm.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #undef min 6 | #undef max 7 | 8 | namespace nall { 9 | 10 | template constexpr auto min(const T& t, const U& u) -> T { 11 | return t < u ? t : (T)u; 12 | } 13 | 14 | template constexpr auto min(const T& t, const U& u, P&&... p) -> T { 15 | return t < u ? min(t, std::forward

(p)...) : min(u, std::forward

(p)...); 16 | } 17 | 18 | template constexpr auto max(const T& t, const U& u) -> T { 19 | return t > u ? t : (T)u; 20 | } 21 | 22 | template constexpr auto max(const T& t, const U& u, P&&... p) -> T { 23 | return t > u ? max(t, std::forward

(p)...) : max(u, std::forward

(p)...); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/utility.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall { 6 | 7 | using std::tuple; 8 | 9 | template struct base_from_member { 10 | base_from_member(T value) : value(value) {} 11 | T value; 12 | }; 13 | 14 | template struct castable { 15 | operator To&() { return (To&)value; } 16 | operator const To&() const { return (const To&)value; } 17 | operator With&() { return value; } 18 | operator const With&() const { return value; } 19 | auto& operator=(const With& value) { return this->value = value; } 20 | With value; 21 | }; 22 | 23 | template inline auto allocate(u64 size, const T& value) -> T* { 24 | T* array = new T[size]; 25 | for(u64 i = 0; i < size; i++) array[i] = value; 26 | return array; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/dsp/misc.cpp: -------------------------------------------------------------------------------- 1 | auto DSP::misc27() -> void { 2 | for(auto& v : voice) v._modulate = v.modulate; 3 | } 4 | 5 | auto DSP::misc28() -> void { 6 | for(auto& v : voice) v._noise = v.noise, v._echo = v.echo; 7 | brr._bank = brr.bank; 8 | } 9 | 10 | auto DSP::misc29() -> void { 11 | clock.sample = !clock.sample; 12 | if(clock.sample) { //clears KON 63 clocks after it was last read 13 | for(auto& v : voice) v._keylatch &= !v._keyon; 14 | } 15 | } 16 | 17 | auto DSP::misc30() -> void { 18 | if(clock.sample) { 19 | for(auto& v : voice) v._keyon = v._keylatch, v._keyoff = v.keyoff; 20 | } 21 | 22 | counterTick(); 23 | 24 | //noise 25 | if(counterPoll(noise.frequency)) { 26 | s32 feedback = noise.lfsr << 13 ^ noise.lfsr << 14; 27 | noise.lfsr = feedback & 0x4000 | noise.lfsr >> 1; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/vector/assign.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template auto vector::operator=(const vector& source) -> vector& { 6 | if(this == &source) return *this; 7 | reset(); 8 | _pool = memory::allocate(source._size); 9 | _size = source._size; 10 | _left = 0; 11 | _right = 0; 12 | for(u64 n : range(_size)) new(_pool + n) T(source._pool[n]); 13 | return *this; 14 | } 15 | 16 | template auto vector::operator=(vector&& source) -> vector& { 17 | if(this == &source) return *this; 18 | reset(); 19 | _pool = source._pool; 20 | _size = source._size; 21 | _left = source._left; 22 | _right = source._right; 23 | source._pool = nullptr; 24 | source._size = 0; 25 | source._left = 0; 26 | source._right = 0; 27 | return *this; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /audio-driver/GNUmakefile: -------------------------------------------------------------------------------- 1 | 2 | MAKEFLAGS += --no-builtin-rules --warn-undefined-variables 3 | .SUFFIXES: 4 | 5 | 6 | MAIN_FILES := src/loader.wiz src/audio-driver.wiz 7 | INCLUDE_FILES := $(filter-out $(MAIN_FILES), $(wildcard src/*.wiz)) 8 | 9 | BIN_OUT := $(patsubst src/%.wiz,%.bin,$(MAIN_FILES)) 10 | SYM_OUT := $(patsubst src/%.wiz,%.sym,$(MAIN_FILES)) 11 | 12 | 13 | ifndef WIZ 14 | WIZ := $(firstword $(wildcard ../wiz/bin/wiz ../bin/wiz/wiz.exe)) 15 | 16 | ifeq ($(WIZ), ) 17 | $(error "Cannot find wiz binary. Please import the wiz git submodule and compile wiz.") 18 | endif 19 | endif 20 | 21 | 22 | .PHONY: all clean 23 | 24 | 25 | all: $(BIN_OUT) $(SYM_OUT) 26 | 27 | 28 | clean: 29 | $(RM) $(BIN_OUT) $(SYM_OUT) 30 | 31 | 32 | %.bin %.sym &: src/%.wiz $(INCLUDE_FILES) 33 | $(WIZ) --system=spc700 -s wla -o $@ $< 34 | 35 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/image/static.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | inline auto image::bitDepth(u64 color) -> u32 { 6 | u32 depth = 0; 7 | if(color) while((color & 1) == 0) color >>= 1; 8 | while((color & 1) == 1) { color >>= 1; depth++; } 9 | return depth; 10 | } 11 | 12 | inline auto image::bitShift(u64 color) -> u32 { 13 | u32 shift = 0; 14 | if(color) while((color & 1) == 0) { color >>= 1; shift++; } 15 | return shift; 16 | } 17 | 18 | inline auto image::normalize(u64 color, u32 sourceDepth, u32 targetDepth) -> u64 { 19 | if(sourceDepth == 0 || targetDepth == 0) return 0; 20 | while(sourceDepth < targetDepth) { 21 | color = (color << sourceDepth) | color; 22 | sourceDepth += sourceDepth; 23 | } 24 | if(targetDepth < sourceDepth) color >>= (sourceDepth - targetDepth); 25 | return color; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/arithmetic/barrett.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template struct BarrettReduction { 6 | using type = typename ArithmeticNatural<1 * Bits>::type; 7 | using pair = typename ArithmeticNatural<2 * Bits>::type; 8 | 9 | explicit BarrettReduction(type modulo) : modulo(modulo), factor(pair(1) + -pair(modulo) / modulo) {} 10 | 11 | //return => value % modulo 12 | auto operator()(pair value) const -> type { 13 | pair hi, lo; 14 | mul(value, factor, hi, lo); 15 | pair remainder = value - hi * modulo; 16 | return remainder < modulo ? remainder : remainder - modulo; 17 | } 18 | 19 | private: 20 | const pair modulo; 21 | const pair factor; 22 | }; 23 | 24 | template auto operator%(T value, const BarrettReduction& modulo) { 25 | return modulo(value); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /manual-tests/bc-interpreter-tests/early-release-and-temp-gain.mml: -------------------------------------------------------------------------------- 1 | ; bc_interpreter test for early release and temp gain 2 | 3 | #Timer 64 4 | #ZenLen 192 5 | 6 | @1 square adsr 10 2 4 5 7 | 8 | A @1 o4 9 | 10 | ; Testing early-release and temp-gain is handled correctly 11 | A q48 GIT15 c%64 12 | A q48,D5 GIT15 c%64 13 | A q200,200,D5 GIT15 c%64 c%250 14 | 15 | ; Testing early-release on long notes 16 | A q48 GET20 c%512 17 | A q48,D5 GET20 c%512 18 | A q200,200,D5 GET20 c%300 c%900 19 | 20 | ; Testing early-release and temp-gain is handled correctly for long notes 21 | A q48 GIT25 c%512 22 | A q48,D5 GIT25 c%512 23 | A q200,200,D5 GIT25 c%300 c%900 24 | 25 | ; Testing early-release and temp-gain is handled correctly for long notes 26 | A q48 GET30 c%512 27 | A q48,D5 GET30 c%512 28 | A q200,200,D5 GET30 c%300 c%900 29 | 30 | -------------------------------------------------------------------------------- /audio-driver/LICENSE: -------------------------------------------------------------------------------- 1 | zlib License 2 | 3 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 4 | 5 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 6 | 7 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 8 | 9 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 10 | 11 | 3. This notice may not be removed or altered from any source distribution. 12 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/windows/shared-memory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | struct shared_memory { 6 | shared_memory() = default; 7 | shared_memory(const shared_memory&) = delete; 8 | auto operator=(const shared_memory&) -> shared_memory& = delete; 9 | 10 | ~shared_memory() { 11 | reset(); 12 | } 13 | 14 | explicit operator bool() const { return false; } 15 | auto empty() const -> bool { return true; } 16 | auto size() const -> u32 { return 0; } 17 | auto acquired() const -> bool { return false; } 18 | auto acquire() -> u8* { return nullptr; } 19 | auto release() -> void {} 20 | auto reset() -> void {} 21 | auto create(const string& name, u32 size) -> bool { return false; } 22 | auto remove() -> void {} 23 | auto open(const string& name, u32 size) -> bool { return false; } 24 | auto close() -> void {} 25 | }; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/http/client.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct addrinfo; 6 | 7 | namespace nall::HTTP { 8 | 9 | struct Client : Role { 10 | auto open(const string& hostname, u16 port = 80) -> bool; 11 | auto upload(const Request& request) -> bool; 12 | auto download(const Request& request) -> Response; 13 | auto close() -> void; 14 | ~Client() { close(); } 15 | 16 | private: 17 | s32 fd = -1; 18 | addrinfo* info = nullptr; 19 | }; 20 | 21 | inline auto Client::upload(const Request& request) -> bool { 22 | return Role::upload(fd, request); 23 | } 24 | 25 | inline auto Client::download(const Request& request) -> Response { 26 | Response response(request); 27 | Role::download(fd, response); 28 | return response; 29 | } 30 | 31 | } 32 | 33 | #if defined(NALL_HEADER_ONLY) 34 | #include 35 | #endif 36 | -------------------------------------------------------------------------------- /manual-tests/bug-tests/noise-and-sfx-test.mml: -------------------------------------------------------------------------------- 1 | ; Manual test to confirm the NON register is cleared after the sound effect starts playing. 2 | ; 3 | ; To test: 4 | ; * Record tad-gui audio output 5 | ; * Play this song in the sound effects tab, starting at tick 0 6 | ; * Must start at tick 0 7 | ; * Select a long sound effect 8 | ; * Rapidly hit the Play Sound effect (F5) button 9 | ; * Panning the sound effect to a single speaker improves analysis 10 | ; * Holding F5 will use the STOP_SOUND_EFFECTS IO command 11 | ; * Verify channel ducking key-off release envelopes are correct 12 | ; * No sine wave in song channel H key-off release 13 | ; * No noise in sound effect key-off release 14 | ; 15 | 16 | #Title Noise and Sound effect test 17 | ; Deliberately misaligned with SFX timer 18 | #Timer 65 19 | 20 | @1 sine gain F127 21 | 22 | H @1 o6 v16 c8 [ N31 ]150 23 | 24 | -------------------------------------------------------------------------------- /crates/compiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compiler" 3 | 4 | license = "MIT" 5 | 6 | edition = "2021" 7 | rust-version.workspace = true 8 | 9 | publish = false 10 | 11 | # This is a private internal crate. 12 | # Using version "0.0.0" as I do not care about breaking changes. 13 | version = "0.0.0" 14 | 15 | 16 | [features] 17 | default = [] 18 | 19 | # Adds a UnitTest options to BytecodeContext that disables note-range tests 20 | # allowing raw assembly compilation without an analysis step 21 | test = [] 22 | 23 | 24 | [dependencies] 25 | # Local crates 26 | brr.workspace = true 27 | 28 | # External crates 29 | serde.workspace = true 30 | serde_json.workspace = true 31 | relative-path.workspace = true 32 | 33 | 34 | [build-dependencies] 35 | regex.workspace = true 36 | 37 | 38 | [dev-dependencies] 39 | compiler = { workspace = true, features = ["test"] } 40 | 41 | tad-emu.workspace = true 42 | 43 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/cd/scrambler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall::CD::Scrambler { 4 | 5 | //polynomial(x) = x^15 + x + 1 6 | inline auto polynomial(u32 x) -> u8 { 7 | static u8 lookup[2340]{}; 8 | static bool once = false; 9 | if(!once) { once = true; 10 | u16 shift = 0x0001; 11 | for(u32 n : range(2340)) { 12 | lookup[n] = shift; 13 | for(u32 b : range(8)) { 14 | bool carry = shift & 1 ^ shift >> 1 & 1; 15 | shift = (carry << 15 | shift) >> 1; 16 | } 17 | } 18 | } 19 | return lookup[x]; 20 | } 21 | 22 | // 23 | 24 | inline auto transform(array_span sector) -> bool { 25 | if(sector.size() == 2352) sector += 12; //header is not scrambled 26 | if(sector.size() != 2340) return false; //F1 frames only 27 | 28 | for(u32 index : range(2340)) { 29 | sector[index] ^= polynomial(index); 30 | } 31 | 32 | return true; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/encode/url.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall::Encode { 4 | 5 | inline auto URL(string_view input) -> string { 6 | string output; 7 | for(auto c : input) { 8 | //unreserved characters 9 | if(c >= 'A' && c <= 'Z') { output.append(c); continue; } 10 | if(c >= 'a' && c <= 'z') { output.append(c); continue; } 11 | if(c >= '0' && c <= '9') { output.append(c); continue; } 12 | if(c == '-' || c == '_' || c == '.' || c == '~') { output.append(c); continue; } 13 | 14 | //special characters 15 | if(c == ' ') { output.append('+'); continue; } 16 | 17 | //reserved characters 18 | u32 hi = (c >> 4) & 15; 19 | u32 lo = (c >> 0) & 15; 20 | output.append('%'); 21 | output.append((char)(hi < 10 ? ('0' + hi) : ('a' + hi - 10))); 22 | output.append((char)(lo < 10 ? ('0' + lo) : ('a' + lo - 10))); 23 | } 24 | return output; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /audio-driver/pvsneslib-api/sound-test/data.asm: -------------------------------------------------------------------------------- 1 | ;; Resources and Data used by the program 2 | 3 | .include "hdr.asm" 4 | 5 | .function SnesRgb(r, g, b) ((r) | ((g) << 5) | ((b) << 10)) 6 | 7 | .section "Resources_Font" SUPERFREE 8 | Font_Tiles: .incbin "tiles/font-2bpp.pic" 9 | 10 | Font_Palette: 11 | .word 0, 0, SnesRgb( 0, 0, 0), SnesRgb(25, 25, 25) 12 | .word 0, 0, SnesRgb( 0, 0, 0), SnesRgb(31, 31, 0) 13 | .word 0, 0, SnesRgb( 0, 0, 0), SnesRgb( 0, 28, 0) 14 | .word 0, 0, SnesRgb( 0, 0, 0), SnesRgb( 7, 7, 7) 15 | .word 0, 0, SnesRgb(15, 15, 0), SnesRgb(31, 31, 31) 16 | .word 0, 0, SnesRgb(15, 15, 0), SnesRgb( 0, 0, 0) 17 | .ends 18 | 19 | 20 | ; Adding gen to the include directory list as wla-dx does not check the directory of the asm 21 | ; file in .incbin statements. 22 | .incdir "gen/" 23 | 24 | .include "gen/audio-data.asm" 25 | 26 | -------------------------------------------------------------------------------- /manual-tests/bug-tests/envelope-race-condition.mml: -------------------------------------------------------------------------------- 1 | ; There was race condition when changing the envelope from ADSR to GAIN in the 2 | ; middle of a note (or vice versa). 3 | ; 4 | ; This race condition occurred because the audio-driver wrote to the ADSR1 DSP 5 | ; voice register before the ADSR2 or GAIN voice registers. 6 | ; 7 | ; This can produce audible glitched output when: 8 | ; * A voice is playing a note 9 | ; * The previous GAIN was fixed 10 | ; * The current envelope is ADSR 11 | ; * The envelope is changed to GAIN decrement 12 | ; 13 | ; In this case the DSP could erroneously read the previous GAIN value for a 14 | ; single sample, changing the envelope to a fixed value and glitching the 15 | ; entire envelope. 16 | ; 17 | 18 | #Timer 64 19 | 20 | @0 sine 21 | 22 | ; This is an annoying sounding test, play it at a reduced volume 23 | A v4 24 | 25 | A @0 26 | A [ GF127 c16 r16 A1,7,7,20 c4 & GD10 r4 ]16 27 | 28 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/LICENSE: -------------------------------------------------------------------------------- 1 | ares 2 | 3 | Copyright (c) 2004-2021 ares team, Near et al 4 | With modifications and rust bindings Copyright (c) 2023, Marcus Rowe 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | 18 | -------------------------------------------------------------------------------- /examples/mml/vibrato.mml: -------------------------------------------------------------------------------- 1 | #Title Vibrato examples 2 | 3 | #Tempo 120 4 | #ZenLen 192 5 | 6 | @1 triangle adsr 14 7 7 15 7 | 8 | A @1 v16 l4 9 | 10 | !s1 l4 @1 e f 11 | A MP150,3 c d !s1 g a r 12 | A ~80,5 c d !s1 g a r 13 | A MP0 c d !s1 g a r 14 | 15 | !s2 l4 @1 MP50,2 e f 16 | A MP150,3 c d !s2 g a r 17 | A ~80,5 c d !s2 g a r 18 | A MP0 c d !s2 g a r 19 | 20 | !s3 l4 ~120,1 e f 21 | A MP150,3 c d !s3 g a r 22 | A ~80,5 c d !s3 g a r 23 | A MP0 c d !s3 g a r 24 | 25 | !s4 l4 ~0 e f 26 | A MP150,3 c d !s4 g a r 27 | A ~80,5 c d !s4 g a r 28 | A MP0 c d !s4 g a r 29 | 30 | ; The ticks argument is an MML length when prefixed with `l` 31 | A MP150,l64 c d !s4 g a r 32 | A ~80,l32 c d !s4 g a r 33 | 34 | ; The optional third argument is the note delay in ticks 35 | ; The delay argument is an MML length when prefixed with `l` 36 | A MP150,2,16 c d e 37 | A MP150,2,l12 f g a r 38 | A ~80,2,16 c d e 39 | A ~80,2,l12 f g a r 40 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/http/client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(PLATFORM_WINDOWS) 4 | #include 5 | #endif 6 | 7 | namespace nall::HTTP { 8 | 9 | NALL_HEADER_INLINE auto Client::open(const string& hostname, u16 port) -> bool { 10 | addrinfo hint = {}; 11 | hint.ai_family = AF_UNSPEC; 12 | hint.ai_socktype = SOCK_STREAM; 13 | hint.ai_flags = AI_ADDRCONFIG; 14 | 15 | if(getaddrinfo(hostname, string{port}, &hint, &info) != 0) return close(), false; 16 | 17 | fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol); 18 | if(fd < 0) return close(), false; 19 | 20 | if(connect(fd, info->ai_addr, info->ai_addrlen) < 0) return close(), false; 21 | return true; 22 | } 23 | 24 | NALL_HEADER_INLINE auto Client::close() -> void { 25 | if(fd) { 26 | ::close(fd); 27 | fd = -1; 28 | } 29 | 30 | if(info) { 31 | freeaddrinfo(info); 32 | info = nullptr; 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/primitives.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace nall { 8 | struct Boolean; 9 | template struct Natural; 10 | template struct Integer; 11 | template struct Real; 12 | } 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace nall { 24 | template auto Natural::integer() const -> Integer { return Integer(*this); } 25 | template auto Integer::natural() const -> Natural { return Natural(*this); } 26 | } 27 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/decode/huffman.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall::Decode { 4 | 5 | inline auto Huffman(array_view input) -> vector { 6 | vector output; 7 | 8 | u32 size = 0; 9 | for(u32 byte : range(8)) size |= *input++ << byte * 8; 10 | output.reserve(size); 11 | 12 | u32 byte = 0, bits = 0; 13 | auto read = [&]() -> bool { 14 | if(bits == 0) bits = 8, byte = *input++; 15 | return byte >> --bits & 1; 16 | }; 17 | 18 | u32 nodes[256][2] = {}; 19 | for(u32 offset : range(256)) { 20 | for(u32 index : range(9)) nodes[offset][0] = nodes[offset][0] << 1 | read(); 21 | for(u32 index : range(9)) nodes[offset][1] = nodes[offset][1] << 1 | read(); 22 | } 23 | 24 | u32 node = 511; 25 | while(output.size() < size) { 26 | node = nodes[node - 256][read()]; 27 | if(node < 256) { 28 | output.append(node); 29 | node = 511; 30 | } 31 | } 32 | 33 | return output; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/matrix-multiply.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //matrix multiplication primitives 4 | //used in: ruby/opengl/quark 5 | 6 | namespace nall { 7 | 8 | template inline auto MatrixMultiply( 9 | T* output, 10 | const T* xdata, u32 xrows, u32 xcols, 11 | const T* ydata, u32 yrows, u32 ycols 12 | ) -> void { 13 | if(xcols != yrows) return; 14 | 15 | for(u32 y : range(xrows)) { 16 | for(u32 x : range(ycols)) { 17 | T sum = 0; 18 | for(u32 z : range(xcols)) { 19 | sum += xdata[y * xcols + z] * ydata[z * ycols + x]; 20 | } 21 | *output++ = sum; 22 | } 23 | } 24 | } 25 | 26 | template inline auto MatrixMultiply( 27 | const T* xdata, u32 xrows, u32 xcols, 28 | const T* ydata, u32 yrows, u32 ycols 29 | ) -> vector { 30 | vector output; 31 | output.resize(xrows * ycols); 32 | MatrixMultiply(output.data(), xdata, xrows, xcols, ydata, yrows, ycols); 33 | return output; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/gdb/watchpoint.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall::GDB { 6 | 7 | enum class WatchpointType : u32 { 8 | WRITE, READ, ACCESS 9 | }; 10 | 11 | struct Watchpoint { 12 | u64 addressStart{0}; 13 | u64 addressEnd{0}; 14 | u64 addressStartOrg{0}; // un-normalized address, GDB needs this 15 | WatchpointType type{}; 16 | 17 | auto operator==(const Watchpoint& w) const { 18 | return addressStart == w.addressStart && addressEnd == w.addressEnd 19 | && addressStartOrg == w.addressStartOrg && type == w.type; 20 | } 21 | 22 | auto hasOverlap(u64 start, u64 end) const { 23 | return (end >= addressStart) && (start <= addressEnd); 24 | } 25 | 26 | auto getTypePrefix() const -> string { 27 | if(type == WatchpointType::WRITE)return "watch:"; 28 | if(type == WatchpointType::READ)return "rwatch:"; 29 | return "awatch:"; 30 | } 31 | }; 32 | } -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/spc700/spc700.cpp: -------------------------------------------------------------------------------- 1 | namespace shvc_sound_emu { 2 | 3 | #define PC r.pc.w 4 | #define YA r.ya.w 5 | #define A r.ya.byte.l 6 | #define X r.x 7 | #define Y r.ya.byte.h 8 | #define S r.s 9 | #define P r.p 10 | 11 | #define CF r.p.c 12 | #define ZF r.p.z 13 | #define IF r.p.i 14 | #define HF r.p.h 15 | #define BF r.p.b 16 | #define PF r.p.p 17 | #define VF r.p.v 18 | #define NF r.p.n 19 | 20 | #define alu (this->*op) 21 | 22 | #include "memory.cpp" 23 | #include "algorithms.cpp" 24 | #include "instructions.cpp" 25 | #include "instruction.cpp" 26 | 27 | auto SPC700::power() -> void { 28 | PC = 0x0000; 29 | YA = 0x0000; 30 | X = 0x00; 31 | S = 0xef; 32 | P = 0x02; 33 | 34 | r.wait = false; 35 | r.stop = false; 36 | } 37 | 38 | #undef PC 39 | #undef YA 40 | #undef A 41 | #undef X 42 | #undef Y 43 | #undef S 44 | #undef P 45 | 46 | #undef CF 47 | #undef ZF 48 | #undef IF 49 | #undef HF 50 | #undef BF 51 | #undef PF 52 | #undef VF 53 | #undef NF 54 | 55 | #undef alu 56 | 57 | } 58 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/pointer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template 6 | struct pointer { 7 | explicit operator bool() const { return value; } 8 | 9 | pointer() = default; 10 | pointer(T* source) { value = source; } 11 | pointer(const pointer& source) { value = source.value; } 12 | 13 | auto& operator=(T* source) { value = source; return *this; } 14 | auto& operator=(const pointer& source) { value = source.value; return *this; } 15 | 16 | auto operator()() -> T* { return value; } 17 | auto operator()() const -> const T* { return value; } 18 | 19 | auto operator->() -> T* { return value; } 20 | auto operator->() const -> const T* { return value; } 21 | 22 | auto operator*() -> T& { return *value; } 23 | auto operator*() const -> const T& { return *value; } 24 | 25 | auto reset() -> void { value = nullptr; } 26 | 27 | auto data() -> T* { return value; } 28 | auto data() const -> const T* { return value; } 29 | 30 | private: 31 | T* value = nullptr; 32 | }; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/nall.cpp: -------------------------------------------------------------------------------- 1 | #if !defined(NALL_HEADER_ONLY) 2 | 3 | #include 4 | 5 | #if defined(PLATFORM_WINDOWS) 6 | #include 7 | #endif 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | //currently unused by ares 23 | //#include 24 | //#include 25 | //#include 26 | #if defined(PLATFORM_WINDOWS) 27 | //#include 28 | //#include 29 | //#include 30 | #include 31 | #include 32 | #endif 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /manual-tests/manual/transpose.mml: -------------------------------------------------------------------------------- 1 | #Title Transpose test 2 | #Tempo 120 3 | 4 | @1 square 5 | 6 | ; MML transpose 7 | A @1 px-64 8 | 9 | A c _+3 c _-3 c r2 10 | A _0 r 11 | 12 | A [c __+1 ]4 c 13 | A _0 r 14 | 15 | A [c __-2 ]4 c 16 | A _0 r 17 | 18 | !s c d c e d. r8 19 | A !s 20 | A _+4 !s 21 | A _-4 !s 22 | A _0 !s 23 | A _0 r 24 | 25 | A [ {cg}4,8 ^8 __+3 ]4 26 | A _0 r 27 | 28 | 29 | ; asm transpose 30 | 31 | B @1 px+64 32 | 33 | B c \asm { set_transpose +3 } c \asm { set_transpose -3 } c r2 34 | B \asm {disable_transpose} r 35 | 36 | B [c \asm { adjust_transpose +1 }]4 c 37 | B \asm {disable_transpose} r 38 | 39 | B [c \asm { adjust_transpose -2 }]4 c 40 | B \asm {disable_transpose} r 41 | 42 | B !s 43 | B \asm { set_transpose +4 } !s 44 | B \asm { set_transpose -4 } !s 45 | B \asm { disable_transpose } !s 46 | B \asm { disable_transpose } r 47 | 48 | B \asm { 49 | start_loop 50 | play_note c4 no_keyoff 12 51 | portamento_calc g4 keyoff 11 24 52 | adjust_transpose +3 53 | end_loop 4 54 | } 55 | B \asm {disable_transpose} r 56 | -------------------------------------------------------------------------------- /examples/mml/pitch-modulation.mml: -------------------------------------------------------------------------------- 1 | #Title Pitch modulation example 2 | 3 | #Tempo 120 4 | #ZenLen 192 5 | 6 | ; All sounds in this example are made from the sine sample 7 | @sine sine adsr 13 2 5 5 8 | 9 | 10 | ;; Example 1 11 | 12 | ; Pitch modulation source. 13 | ; OUTX (sample output * envelope) the modulation source for the next channel. 14 | ; Changing Envelope adjusts the depth of the modulation. 15 | ; Volume is set to 0 so channel A is not audible. 16 | A @sine GF50 v0 P$500,%480 17 | 18 | B @sine v16 19 | B @sine c f e g r ; without pitch mod 20 | B @sine PM c f e g r ; with pitch mod 21 | C w%480 22 | 23 | 24 | ;; Example 2 25 | ; 26 | ; Pitch-mod with linear increase envelope 27 | A @sine GI15 v0 o3 g & c & f & e & r 28 | B @sine v16 PM o4 c f e g r 29 | C w%240 30 | 31 | 32 | ;; Example 3 33 | ; 34 | ; A pitch modulated channel can be used as a pitch modulation source 35 | A @sine GF40 v0 o2 c ^ ^ ^ r 36 | B @sine GF80 v0 PM o5 g & c & f & e & r 37 | C @sine v16 PM o4 c f e g r 38 | 39 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/platform.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(PLATFORM_WINDOWS) 4 | 5 | #include 6 | 7 | NALL_HEADER_INLINE auto poll(struct pollfd fds[], unsigned long nfds, int timeout) -> int { return WSAPoll(fds, nfds, timeout); } 8 | 9 | namespace nall { 10 | 11 | NALL_HEADER_INLINE auto recv(int socket, void* buffer, size_t length, int flags) -> ssize_t { 12 | return ::recv(socket, (char*)buffer, length, flags); 13 | } 14 | 15 | NALL_HEADER_INLINE auto send(int socket, const void* buffer, size_t length, int flags) -> ssize_t { 16 | return ::send(socket, (const char*)buffer, length, flags); 17 | } 18 | 19 | NALL_HEADER_INLINE auto setsockopt(int socket, int level, int option_name, const void* option_value, int option_len) -> int { 20 | return ::setsockopt(socket, level, option_name, (const char*)option_value, option_len); 21 | } 22 | 23 | NALL_HEADER_INLINE auto usleep(unsigned int us) -> int { 24 | if(us != 0) { 25 | Sleep(us / 1000); 26 | } 27 | 28 | return 0; 29 | } 30 | 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/xorg/guard.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NALL_XORG_GUARD_HPP 2 | #define NALL_XORG_GUARD_HPP 3 | 4 | #define Atom XlibAtom 5 | #define Display XlibDisplay 6 | #define Font XlibFont 7 | #define Screen XlibScreen 8 | #define Window XlibWindow 9 | 10 | #else 11 | #undef NALL_XORG_GUARD_HPP 12 | 13 | #undef Atom 14 | #undef Display 15 | #undef Font 16 | #undef Screen 17 | #undef Window 18 | 19 | #undef Above 20 | #undef Below 21 | #undef Bool 22 | 23 | #ifndef NALL_XORG_GUARD_CONSTANTS 24 | #define NALL_XORG_GUARD_CONSTANTS 25 | enum XlibConstants : int { 26 | XlibButton1 = Button1, 27 | XlibButton2 = Button2, 28 | XlibButton3 = Button3, 29 | XlibButton4 = Button4, 30 | XlibButton5 = Button5, 31 | XlibCurrentTime = CurrentTime, 32 | XlibFalse = False, 33 | XlibNone = None, 34 | XlibTrue = True, 35 | }; 36 | #endif 37 | 38 | #undef Button1 39 | #undef Button2 40 | #undef Button3 41 | #undef Button4 42 | #undef Button5 43 | #undef CurrentTime 44 | #undef False 45 | #undef None 46 | #undef True 47 | 48 | #undef MAX 49 | #undef MIN 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /manual-tests/bug-tests/portamento-first-tick-bugtest.mml: -------------------------------------------------------------------------------- 1 | ; I realised I was processing portamento before bytecode. 2 | ; This delayed portamento pitch slide by 1 tick. 3 | 4 | ; 30ms per tick 5 | #Timer 240 6 | 7 | A \asm { 8 | set_instrument_and_gain sine F64 9 | } 10 | 11 | ; This should play c4 for 1 tick, then c6 for 1 tick, then key-off 12 | A \asm { 13 | play_note c4 no_keyoff 1 14 | ; using temp-GAIN to easily see when the play_note tick ends 15 | set_temp_gain F127 16 | ; Should increment pitch for 1 tick then key-off 17 | ; Using negative portamento velocity to immediately switch to the target pitch 18 | portamento c6 keyoff -255 2 19 | wait 1 20 | } 21 | 22 | ; This should play c4 for 1 tick, then c6 for 2 ticks, then key-off 23 | A \asm { 24 | play_note c4 no_keyoff 1 25 | ; using temp-GAIN to easily see when the play_note tick ends 26 | set_temp_gain F127 27 | ; Should increment pitch for 2 ticks then key-off 28 | ; Using negative portamento velocity to immediately switch to the target pitch 29 | portamento c6 keyoff -255 3 30 | wait 1 31 | } 32 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/dl.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace nall { 4 | 5 | #if defined(PLATFORM_WINDOWS) 6 | 7 | NALL_HEADER_INLINE auto library::open(const string& name, const string& path) -> bool { 8 | if(handle) close(); 9 | if(path) { 10 | string filepath = {path, name, ".dll"}; 11 | handle = (uintptr)LoadLibraryW(utf16_t(filepath)); 12 | } 13 | if(!handle) { 14 | string filepath = {name, ".dll"}; 15 | handle = (uintptr)LoadLibraryW(utf16_t(filepath)); 16 | } 17 | return handle; 18 | } 19 | 20 | NALL_HEADER_INLINE auto library::openAbsolute(const string& name) -> bool { 21 | if(handle) close(); 22 | handle = (uintptr)LoadLibraryW(utf16_t(name)); 23 | return handle; 24 | } 25 | 26 | NALL_HEADER_INLINE auto library::sym(const string& name) -> void* { 27 | if(!handle) return nullptr; 28 | return (void*)GetProcAddress((HMODULE)handle, name); 29 | } 30 | 31 | NALL_HEADER_INLINE auto library::close() -> void { 32 | if(!handle) return; 33 | FreeLibrary((HMODULE)handle); 34 | handle = 0; 35 | } 36 | 37 | #endif 38 | 39 | } 40 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/decode/bwt.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //burrows-wheeler transform 4 | 5 | #include 6 | 7 | namespace nall::Decode { 8 | 9 | inline auto BWT(array_view input) -> vector { 10 | vector output; 11 | 12 | u32 size = 0; 13 | for(u32 byte : range(8)) size |= *input++ << byte * 8; 14 | output.resize(size); 15 | 16 | u32 I = 0; 17 | for(u32 byte : range(8)) I |= *input++ << byte * 8; 18 | 19 | auto suffixes = SuffixArray(input); 20 | 21 | auto L = input; 22 | auto F = new u8[size]; 23 | for(u32 offset : range(size)) F[offset] = L[suffixes[offset + 1]]; 24 | 25 | u64 K[256] = {}; 26 | auto C = new s32[size]; 27 | for(u32 i : range(size)) { 28 | C[i] = K[L[i]]; 29 | K[L[i]]++; 30 | } 31 | 32 | s32 M[256]; 33 | memory::fill(M, 256, -1); 34 | for(u32 i : range(size)) { 35 | if(M[F[i]] == -1) M[F[i]] = i; 36 | } 37 | 38 | u32 i = I; 39 | for(u32 j : reverse(range(size))) { 40 | output[j] = L[i]; 41 | i = C[i] + M[L[i]]; 42 | } 43 | 44 | return output; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /manual-tests/manual/noise-priority-test.mml: -------------------------------------------------------------------------------- 1 | #Title Noise priority test 2 | 3 | #Timer 96 4 | 5 | ; Test instructions 6 | ; 7 | ; 1. Record tad-gui audio output 8 | ; 2. Play this MML file 9 | ; 3. Use the Sound Effects window to play the following sound effects: 10 | ; * noise_freq_test_left 11 | ; * noise_freq_test_left then noise_freq_test_right 12 | ; * noise_freq_test_right then noise_freq_test_left 13 | ; * noise_freq_test_left then uninterruptable_noise_right 14 | ; * uninterruptable_noise_right then noise_freq_test_left 15 | ; * uninterruptable_noise_left and uninterruptable_noise_right 16 | ; * noise_freq_test_left then uninterruptable_noise_left 17 | ; * noise_freq_test_right then uninterruptable_noise_right 18 | ; 5. Review the tad-gui audio output to verify: 19 | ; * When 0 or 2 sfx is uninterruptible, the newest play_noise instruction has priority 20 | ; * When only 1 sfx is uninterruptible, the uninterruptible has priority 21 | 22 | @1 sine 23 | 24 | ; music noise is on the right 25 | AB px+64 26 | 27 | A @1 v4 l8 [N28 r ]25 28 | B @1 v4 l8 [r N28]25 29 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/primitives/boolean.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | struct Boolean { 6 | static constexpr auto bits() -> u32 { return 1; } 7 | using btype = bool; 8 | 9 | Boolean() : data(false) {} 10 | template Boolean(const T& value) : data(value) {} 11 | explicit Boolean(const char* value) { data = !strcmp(value, "true"); } 12 | 13 | operator bool() const { return data; } 14 | template auto& operator=(const T& value) { data = value; return *this; } 15 | 16 | auto flip() { return data ^= 1; } 17 | auto raise() { return data == 0 ? data = 1, true : false; } 18 | auto lower() { return data == 1 ? data = 0, true : false; } 19 | 20 | auto flip(bool value) { return data != value ? (data = value, true) : false; } 21 | auto raise(bool value) { return !data && value ? (data = value, true) : (data = value, false); } 22 | auto lower(bool value) { return data && !value ? (data = value, true) : (data = value, false); } 23 | 24 | auto serialize(serializer& s) { s(data); } 25 | 26 | private: 27 | btype data; 28 | }; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /examples/mml/detune.mml: -------------------------------------------------------------------------------- 1 | #Title Detune examples 2 | 3 | @1 sine adsr 12 7 7 0 4 | 5 | A @1 v14 6 | 7 | ; The `D` detune MML command adds a fixed VxPITCH offset to all 8 | ; subsequent play-note and portamento instructions. 9 | ; `D0` disables detune. 10 | 11 | A D0 c r16 12 | A D+80 c r16 13 | A D-80 c r16 14 | A r 15 | 16 | A D0 {eg} r16 17 | A D+100 {eg} r16 18 | A D-100 {eg} r16 19 | A r 20 | 21 | ; `P` play pitch MML commands are unaffected by detune 22 | ; (these two notes will sound the same) 23 | A D0 P$1000 r16 24 | A D+1000 P$1000 r16 25 | A r 26 | 27 | 28 | ; `MD` detune-cents MML command calculates the VxPITCH offset 29 | ; and adds a `D` detune command before each note. 30 | A MD+25 c d e r16 31 | ; `MD0` disables `MD` and `D` detune 32 | A MD0 c d e r16 33 | A r 34 | 35 | 36 | ; These two lines should sound the same 37 | ; (detuned down 200 cents, or down 2 semitones) 38 | A MD-200 o5 e f g r16 39 | A MD0 o5 d e- f r16 40 | A r 41 | 42 | ; MD also detunes portameno. 43 | ; These two portamentos should sound the same. 44 | A MD+200 {df}^ r16 45 | A D0 {eg}^ r16 46 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/cd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* CD-ROM sector functions. 4 | * 5 | * Implemented: 6 | * eight-to-fourteen modulation (encoding and decoding) 7 | * sync header creation and verification 8 | * error detection code creation and verification 9 | * reed-solomon product-code creation and verification 10 | * sector scrambling and descrambling (currently unverified) 11 | * 12 | * Unimplemented: 13 | * reed-solomon product-code correction 14 | * cross-interleave reed-solomon creation, verification, and correction 15 | * CD-ROM XA mode 2 forms 1 & 2 support 16 | * subcode insertion and removal 17 | * subcode decoding from CUE files 18 | * channel frame expansion and reduction 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/decode/rle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall::Decode { 4 | 5 | template //S = word size; M = match length 6 | inline auto RLE(array_view input) -> vector { 7 | vector output; 8 | 9 | auto load = [&]() -> u8 { 10 | return input ? *input++ : 0; 11 | }; 12 | 13 | u32 base = 0; 14 | u64 size = 0; 15 | for(u32 byte : range(8)) size |= load() << byte * 8; 16 | output.resize(size); 17 | 18 | auto read = [&]() -> u64 { 19 | u64 value = 0; 20 | for(u32 byte : range(S)) value |= load() << byte * 8; 21 | return value; 22 | }; 23 | 24 | auto write = [&](u64 value) -> void { 25 | if(base >= size) return; 26 | for(u32 byte : range(S)) output[base++] = value >> byte * 8; 27 | }; 28 | 29 | while(base < size) { 30 | auto byte = load(); 31 | if(byte < 128) { 32 | byte++; 33 | while(byte--) write(read()); 34 | } else { 35 | auto value = read(); 36 | byte = (byte & 127) + M; 37 | while(byte--) write(value); 38 | } 39 | } 40 | 41 | return output; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/tcptext/tcptext-server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace nall::TCPText { 4 | NALL_HEADER_INLINE auto Server::sendText(const string &text) -> void { 5 | sendData((const u8*)text.data(), text.size()); 6 | } 7 | 8 | NALL_HEADER_INLINE auto Server::onData(const vector &data) -> void { 9 | string_view dataStr((const char*)data.data(), (u32)data.size()); 10 | 11 | if(!hadHandshake) { 12 | hadHandshake = true; 13 | 14 | // This is a security check for browsers. 15 | // Any website can request localhost via JS or HTML, while it can't see the result, 16 | // GDB will receive the data and commands could be injected (true for all GDB-servers). 17 | // Since all HTTP requests start with headers, we can simply block anything that doesn't start like a GDB client. 18 | if(dataStr[0] != '+') { 19 | printf("Non-GDB client detected (message: %s), disconnect client\n", dataStr.data()); 20 | disconnectClient(); 21 | return; 22 | } 23 | 24 | onConnect(); 25 | } 26 | 27 | onText(dataStr); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/brr/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023, Marcus Rowe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /manual-tests/bc-interpreter-tests/large-skip-last-loop.mml: -------------------------------------------------------------------------------- 1 | ; Tests skip_last_loop_u16 2 | 3 | #Timer 64 4 | 5 | @1 sine 6 | 7 | A @1 8 | A ; This skip last loop command requires a skip_last_loop_u16 9 | A [ g : 10 | A ; This skip last loop command requires a skip_last_loop_u16 11 | A [ c : 12 | A ; This skip last loop command fits inside a skip_last_loop_u8 13 | A [ d : e2 14 | A \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } 15 | A \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } 16 | A \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } 17 | A \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } 18 | A \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } 19 | A \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } 20 | A \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } \fir { 0 0 0 0 0 0 0 0 } 21 | A ]3 22 | A ]3 23 | A ]5 24 | 25 | -------------------------------------------------------------------------------- /crates/compiler/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023, Marcus Rowe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /crates/tad-emu/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023, Marcus Rowe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /crates/tad-gui/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023, Marcus Rowe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /crates/wav2brr/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023, Marcus Rowe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /manual-tests/manual/noise-timing-test-a.mml: -------------------------------------------------------------------------------- 1 | #Title Noise timing test (channel A) 2 | 3 | #Timer 96 4 | 5 | ; Test instructions 6 | ; 7 | ; 1. Record tad-gui audio output 8 | ; 2. Play this song in the GUI 9 | ; 3. Use the Sound Effects window to play the following sound effects: 10 | ; * sine_noise_loop 11 | ; * noise 12 | ; * noise_no_keyoff 13 | ; * noise, then beep_beep (after noise has finished) 14 | ; * beep_beep, then noise (after beep_beep has finished) 15 | ; * noise_freq_test_left 16 | ; * noise_freq_test_left then uninterruptable_noise_left 17 | ; * uninterruptable_noise_left then noise_freq_test_left 18 | ; 4. Review the tad-gui audio output to verify: 19 | ; * The sine/noise wave is unchanged during the key-on GI envelope 20 | ; * The sine/noise wave is unchanged during the key-off release envelope 21 | 22 | @1 sine 23 | 24 | ; music is on the right 25 | ; sfx is on the left 26 | A px+64 27 | 28 | A @1 GI24 l8 [c N28,16 N28,16]25 29 | 30 | ; NOTES: 31 | ; * `N28,16 c N28,16` test NON is correct when 32 | ; switching between play_noise and play_note. 33 | ; * `N28,16 N28,16` tests if NON clear after key-off 34 | ; does not disable NON on the key-on tick 35 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/README.md: -------------------------------------------------------------------------------- 1 | SHVC-SOUND emulator 2 | =================== 3 | 4 | Rust bindings for a SNES audio emulator created using modified [ares](https://ares-emu.net/) source 5 | code. 6 | 7 | **NOTE:** This crate is not designed to play `.spc` files. It is designed to play music and sound 8 | effects created using the Terrific Audio Driver. 9 | 10 | Changes from ares: 11 | * Removed the Node, debugger, serialization and disassembler methods. 12 | * Added a sample buffer. 13 | * Removed the libco based scheduler and switched to a hard-coded smp/dsp scheduler that executes 14 | SPC instructions until the sample buffer is full. 15 | * Removed global variables. 16 | * Uses `std::array` for memory arrays. 17 | * Added `DSP::resetEchoBuffer()`. 18 | * Added rust bindings using the [cxx](https://cxx.rs/) crate. 19 | * Added methods to set the S-DSP and S-SMP registers. 20 | 21 | 22 | Build Requirements 23 | ================== 24 | * Rust 25 | * Cargo 26 | * A C++17 compiler, compatible with the [cxx](https://cxx.rs/) crate. 27 | 28 | 29 | Licensing 30 | ========= 31 | The `shvc-sound-emu` crate is released under the [ISC license](LICENSE) (the same license as ares). 32 | 33 | 34 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/windows/utf8.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace nall { 6 | 7 | NALL_HEADER_INLINE auto utf16_t::operator=(const char* s) -> utf16_t& { 8 | reset(); 9 | if(!s) s = ""; 10 | length = MultiByteToWideChar(CP_UTF8, 0, s, -1, nullptr, 0); 11 | buffer = new wchar_t[length + 1]; 12 | MultiByteToWideChar(CP_UTF8, 0, s, -1, buffer, length); 13 | buffer[length] = 0; 14 | return *this; 15 | } 16 | 17 | NALL_HEADER_INLINE auto utf8_t::operator=(const wchar_t* s) -> utf8_t& { 18 | reset(); 19 | if(!s) s = L""; 20 | length = WideCharToMultiByte(CP_UTF8, 0, s, -1, nullptr, 0, nullptr, nullptr); 21 | buffer = new char[length + 1]; 22 | WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, length, nullptr, nullptr); 23 | buffer[length] = 0; 24 | return *this; 25 | } 26 | 27 | NALL_HEADER_INLINE auto utf8_arguments(int& argc, char**& argv) -> void { 28 | wchar_t** wargv = CommandLineToArgvW(GetCommandLineW(), &argc); 29 | argv = new char*[argc + 1](); 30 | for(u32 i = 0; i < argc; i++) { 31 | utf8_t arg(wargv[i]); 32 | argv[i] = new char[arg.size() + 1]; 33 | strcpy(argv[i], arg); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /crates/tad-compiler/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023, Marcus Rowe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/string/utf8.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | //note: this function assumes the string contains valid UTF-8 characters 6 | //invalid characters will result in an incorrect result from this function 7 | //invalid case 1: byte 1 == 0b'01xxxxxx 8 | //invalid case 2: bytes 2-4 != 0b'10xxxxxx 9 | //invalid case 3: end of string without bytes 2-4 present 10 | inline auto characters(string_view self, s32 offset, s32 length) -> u32 { 11 | u32 characters = 0; 12 | if(offset < 0) offset = self.size() - abs(offset); 13 | if(offset >= 0 && offset < self.size()) { 14 | if(length < 0) length = self.size() - offset; 15 | if(length >= 0) { 16 | for(s32 index = offset; index < offset + length;) { 17 | auto byte = self.data()[index++]; 18 | if((byte & 0b111'00000) == 0b110'00000) index += 1; 19 | if((byte & 0b1111'0000) == 0b1110'0000) index += 2; 20 | if((byte & 0b11111'000) == 0b11110'000) index += 3; 21 | characters++; 22 | } 23 | } 24 | } 25 | return characters; 26 | } 27 | 28 | inline auto string::characters(s32 offset, s32 length) const -> u32 { 29 | return nall::characters(*this, offset, length); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /manual-tests/manual/portamento-without-instrument.mml: -------------------------------------------------------------------------------- 1 | #Title Portamento in subroutine without instrument 2 | 3 | ; Test instructions 4 | ; 5 | ; 1. Record tad-gui audio output 6 | ; 2. Use a spectrogram to verify the end of the pitch slide 7 | ; matches the sixteenth notes atter the portamentos. 8 | 9 | #ZenLen 192 10 | #Timer 64 11 | 12 | ; Doing the test with two samples with different tuning frequencies 13 | ; (500Hz and 666.66 Hz) 14 | @1 square gain I23 15 | @2 square48 gain I23 16 | 17 | 18 | !s1 {o1 a o5 c}2 c16 r8 {o5 c o3 c}2 c16 r8 19 | A @1 !s1 @2 !s1 r8 20 | 21 | !s2 {P$1000 d}4 d16 r8 {c P$2000}4 P$2000,16 r8 22 | A @1 !s2 @2 !s2 r8 23 | 24 | !s3 {cd}4 & d16 & {e}8 & e16 & {d}4,12 d16 r8 25 | A @1 !s3 @2 !s3 r8 26 | 27 | ; Test how driver handles a quotient of 0 28 | !s4 o3 {c d}1 d16 r8 29 | A @1 !s4 @2 !s4 r8 30 | 31 | ; Test maximum slide length 32 | !s5 o5 {c g}%257 g16 r8 33 | A @1 !s5 @2 !s5 r8 34 | 35 | ; Test a small value and maximum slide length 36 | !s6 o3 {c d}%257 d16 r8 37 | A @1 !s6 @2 !s6 r8 38 | 39 | ; Test a portamento slide with a calculated velocity > 255 40 | ; Both portamentos will fail this test and not reach the target note 41 | !s7 {o1c o6b}%30 o6b16 r8 42 | A @1 !s7 @2 !s7 r8 43 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/dsp/iir/one-pole.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //one-pole first-order IIR filter 4 | 5 | namespace nall::DSP::IIR { 6 | 7 | struct OnePole { 8 | enum class Type : u32 { 9 | LowPass, 10 | HighPass, 11 | }; 12 | 13 | auto reset(Type type, f64 cutoffFrequency, f64 samplingFrequency) -> void; 14 | auto process(f64 in) -> f64; //normalized sample (-1.0 to +1.0) 15 | 16 | private: 17 | Type type; 18 | f64 cutoffFrequency; 19 | f64 samplingFrequency; 20 | f64 a0, b1; //coefficients 21 | f64 z1; //first-order IIR 22 | }; 23 | 24 | inline auto OnePole::reset(Type type, f64 cutoffFrequency, f64 samplingFrequency) -> void { 25 | this->type = type; 26 | this->cutoffFrequency = cutoffFrequency; 27 | this->samplingFrequency = samplingFrequency; 28 | 29 | z1 = 0.0; 30 | f64 x = cos(2.0 * Math::Pi * cutoffFrequency / samplingFrequency); 31 | if(type == Type::LowPass) { 32 | b1 = +2.0 - x - sqrt((+2.0 - x) * (+2.0 - x) - 1); 33 | a0 = 1.0 - b1; 34 | } else { 35 | b1 = -2.0 - x + sqrt((-2.0 - x) * (-2.0 - x) - 1); 36 | a0 = 1.0 + b1; 37 | } 38 | } 39 | 40 | inline auto OnePole::process(f64 in) -> f64 { 41 | return z1 = in * a0 + z1 * b1; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/decode/base64.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall::Decode { 4 | 5 | inline auto Base64(const string& text) -> vector { 6 | static bool initialized = false; 7 | static u8 lookup[256] = {}; 8 | if(!initialized) { 9 | initialized = true; 10 | for(u32 n : range(26)) lookup['A' + n] = n; 11 | for(u32 n : range(26)) lookup['a' + n] = n + 26; 12 | for(u32 n : range(10)) lookup['0' + n] = n + 52; 13 | lookup['+'] = lookup['-'] = 62; 14 | lookup['/'] = lookup['_'] = 63; 15 | } 16 | 17 | vector result; 18 | u8 buffer = 0; 19 | u8 output = 0; 20 | for(u32 n : range(text.size())) { 21 | u8 buffer = lookup[text[n]]; 22 | 23 | switch(n & 3) { 24 | case 0: 25 | output = buffer << 2; 26 | break; 27 | 28 | case 1: 29 | result.append(output | buffer >> 4); 30 | output = (buffer & 15) << 4; 31 | break; 32 | 33 | case 2: 34 | result.append(output | buffer >> 2); 35 | output = (buffer & 3) << 6; 36 | break; 37 | 38 | case 3: 39 | result.append(output | buffer); 40 | break; 41 | } 42 | } 43 | 44 | if(text.size() & 3) result.append(output | buffer); 45 | return result; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/vector/specialization/u8.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template<> struct vector : vector_base { 6 | using type = vector; 7 | using vector_base::vector_base; 8 | 9 | template auto appendl(U value, u32 size) -> void { 10 | for(u32 byte : range(size)) append(u8(value >> byte * 8)); 11 | } 12 | 13 | template auto appendm(U value, u32 size) -> void { 14 | for(u32 byte : nall::reverse(range(size))) append(u8(value >> byte * 8)); 15 | } 16 | 17 | //note: string_view is not declared here yet ... 18 | auto appends(array_view memory) -> void { 19 | for(u8 byte : memory) append(byte); 20 | } 21 | 22 | template auto readl(s32 offset, u32 size) -> U { 23 | if(offset < 0) offset = this->size() - abs(offset); 24 | U value = 0; 25 | for(u32 byte : range(size)) value |= (U)operator[](offset + byte) << byte * 8; 26 | return value; 27 | } 28 | 29 | auto view(u32 offset, u32 length) -> array_view { 30 | #ifdef DEBUG 31 | struct out_of_bounds {}; 32 | if(offset + length >= size()) throw out_of_bounds{}; 33 | #endif 34 | return {data() + offset, length}; 35 | } 36 | }; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/string/eval/node.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall::Eval { 4 | 5 | struct Node { 6 | enum class Type : u32 { 7 | Null, 8 | Literal, 9 | Function, Subscript, Member, SuffixIncrement, SuffixDecrement, 10 | Reference, Dereference, LogicalNot, BitwiseNot, Positive, Negative, PrefixIncrement, PrefixDecrement, 11 | Multiply, Divide, Modulo, 12 | Add, Subtract, 13 | RotateLeft, RotateRight, ShiftLeft, ShiftRight, 14 | BitwiseAnd, BitwiseOr, BitwiseXor, 15 | Concatenate, 16 | Equal, NotEqual, LessThanEqual, GreaterThanEqual, LessThan, GreaterThan, 17 | LogicalAnd, LogicalOr, 18 | Coalesce, Condition, 19 | Assign, Create, //all assignment operators have the same precedence 20 | AssignMultiply, AssignDivide, AssignModulo, 21 | AssignAdd, AssignSubtract, 22 | AssignRotateLeft, AssignRotateRight, AssignShiftLeft, AssignShiftRight, 23 | AssignBitwiseAnd, AssignBitwiseOr, AssignBitwiseXor, 24 | AssignConcatenate, 25 | Separator, 26 | }; 27 | 28 | Type type; 29 | string literal; 30 | vector link; 31 | 32 | Node() : type(Type::Null) {} 33 | Node(Type type) : type(type) {} 34 | ~Node() { for(auto& node : link) delete node; } 35 | }; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/hash/crc16.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall::Hash { 6 | 7 | struct CRC16 : Hash { 8 | using Hash::input; 9 | 10 | CRC16(array_view buffer = {}) { 11 | reset(); 12 | input(buffer); 13 | } 14 | 15 | auto reset() -> void override { 16 | checksum = ~0; 17 | } 18 | 19 | auto input(u8 value) -> void override { 20 | checksum = (checksum >> 8) ^ table(checksum ^ value); 21 | } 22 | 23 | auto output() const -> vector override { 24 | vector result; 25 | for(auto n : reverse(range(2))) result.append(~checksum >> n * 8); 26 | return result; 27 | } 28 | 29 | auto value() const -> u16 { 30 | return ~checksum; 31 | } 32 | 33 | private: 34 | static auto table(u8 index) -> u16 { 35 | static u16 table[256] = {}; 36 | static bool initialized = false; 37 | 38 | if(!initialized) { 39 | initialized = true; 40 | for(auto index : range(256)) { 41 | u16 crc = index; 42 | for(auto bit : range(8)) { 43 | crc = (crc >> 1) ^ (crc & 1 ? 0x8408 : 0); 44 | } 45 | table[index] = crc; 46 | } 47 | } 48 | 49 | return table[index]; 50 | } 51 | 52 | u16 checksum = 0; 53 | }; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /manual-tests/manual/cut-portamento-unknown-velocity.mml: -------------------------------------------------------------------------------- 1 | #Title Cut portamento with unknown velocity 2 | 3 | ; The left and right channels should look the same in a spectrogram. 4 | 5 | #ZenLen 192 6 | #Timer 64 7 | 8 | @1 square gain I26 9 | 10 | A @1 px-64 11 | B @1 px+64 !s 12 | 13 | ; Cut with `Q` quantize 14 | A Q4 {ca}2 Q8 a16 r16 15 | !s Q4 {ca}2 Q8 a16 r16 16 | 17 | ; Tied portamento cut with `Q` quantize 18 | A Q2 {ca}4 ^4 Q8 a16 r16 19 | !s Q2 {ca}4 ^4 Q8 a16 r16 20 | 21 | ; Long tied portamento cut with `Q` quantize 22 | A Q6 {ca}4 ^%300 Q8 a16 r16 23 | !s Q6 {ca}4 ^%300 Q8 a16 r16 24 | 25 | ; Portamento note-pitch cut with `Q` quantize 26 | A Q2 {c P$3000}1,4 Q8 P$3000,16 r16 27 | !s Q2 {c P$3000}1,4 Q8 P$3000,16 r16 28 | 29 | ; Portamento cut with `q` early-release 30 | A q48 {ca}2 q0 a16 r16 31 | !s q48 {ca}2 q0 a16 r16 32 | 33 | ; Tied portamento cut with `q` early-release 34 | A q64 {ca}3 ^4 q0 a16 r16 35 | !s q64 {ca}3 ^4 q0 a16 r16 36 | 37 | ; Long Tied portamento cut with `q` early-release 38 | A q150,D10 {ca}%100,%50 ^%100 q0 a16 r16 39 | !s q150,D10 {ca}%100,%50 ^%100 q0 a16 r16 40 | 41 | ; Long Tied portamento cut with `q` early-release 42 | A q254,D10 {ca}%300,%150 ^%100 q0 a16 r16 43 | !s q254,D10 {ca}%300,%150 ^%100 q0 a16 r16 44 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/hash/crc32.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall::Hash { 6 | 7 | struct CRC32 : Hash { 8 | using Hash::input; 9 | 10 | CRC32(array_view buffer = {}) { 11 | reset(); 12 | input(buffer); 13 | } 14 | 15 | auto reset() -> void override { 16 | checksum = ~0; 17 | } 18 | 19 | auto input(u8 value) -> void override { 20 | checksum = (checksum >> 8) ^ table(checksum ^ value); 21 | } 22 | 23 | auto output() const -> vector override { 24 | vector result; 25 | for(auto n : reverse(range(4))) result.append(~checksum >> n * 8); 26 | return result; 27 | } 28 | 29 | auto value() const -> u32 { 30 | return ~checksum; 31 | } 32 | 33 | private: 34 | static auto table(u8 index) -> u32 { 35 | static u32 table[256] = {}; 36 | static bool initialized = false; 37 | 38 | if(!initialized) { 39 | initialized = true; 40 | for(auto index : range(256)) { 41 | u32 crc = index; 42 | for(auto bit : range(8)) { 43 | crc = (crc >> 1) ^ (crc & 1 ? 0xedb8'8320 : 0); 44 | } 45 | table[index] = crc; 46 | } 47 | } 48 | 49 | return table[index]; 50 | } 51 | 52 | u32 checksum = 0; 53 | }; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/decode/html.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall::Decode { 4 | 5 | inline auto HTML(const string& input) -> string { 6 | string output; 7 | for(u32 n = 0; n < input.size();) { 8 | if(input[n] == '&') { 9 | if(input(n + 1) == 'a' && input(n + 2) == 'm' && input(n + 3) == 'p' && input(n + 4) == ';') { 10 | output.append('&'); 11 | n += 5; 12 | continue; 13 | } 14 | if(input(n + 1) == 'l' && input(n + 2) == 't' && input(n + 3) == ';') { 15 | output.append('<'); 16 | n += 4; 17 | continue; 18 | } 19 | if(input(n + 1) == 'g' && input(n + 2) == 't' && input(n + 3) == ';') { 20 | output.append('>'); 21 | n += 4; 22 | continue; 23 | } 24 | if(input(n + 1) == 'q' && input(n + 2) == 'u' && input(n + 3) == 'o' && input(n + 4) == 't' && input(n + 5) == ';') { 25 | output.append('"'); 26 | n += 6; 27 | continue; 28 | } 29 | if(input(n + 1) == 'a' && input(n + 2) == 'p' && input(n + 3) == 'o' && input(n + 4) == 's' && input(n + 5) == ';') { 30 | output.append('\''); 31 | n += 6; 32 | continue; 33 | } 34 | } 35 | output.append(input[n++]); 36 | } 37 | return output; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/hash/crc64.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall::Hash { 6 | 7 | struct CRC64 : Hash { 8 | using Hash::input; 9 | 10 | CRC64(array_view buffer = {}) { 11 | reset(); 12 | input(buffer); 13 | } 14 | 15 | auto reset() -> void override { 16 | checksum = ~0; 17 | } 18 | 19 | auto input(u8 value) -> void override { 20 | checksum = (checksum >> 8) ^ table(checksum ^ value); 21 | } 22 | 23 | auto output() const -> vector override { 24 | vector result; 25 | for(auto n : reverse(range(8))) result.append(~checksum >> n * 8); 26 | return result; 27 | } 28 | 29 | auto value() const -> u64 { 30 | return ~checksum; 31 | } 32 | 33 | private: 34 | static auto table(u8 index) -> u64 { 35 | static u64 table[256] = {}; 36 | static bool initialized = false; 37 | 38 | if(!initialized) { 39 | initialized = true; 40 | for(auto index : range(256)) { 41 | u64 crc = index; 42 | for(auto bit : range(8)) { 43 | crc = (crc >> 1) ^ (crc & 1 ? 0xc96c'5795'd787'0f42 : 0); 44 | } 45 | table[index] = crc; 46 | } 47 | } 48 | 49 | return table[index]; 50 | } 51 | 52 | u64 checksum = 0; 53 | }; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/vector/access.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template auto vector::operator[](u64 offset) -> T& { 6 | #ifdef DEBUG 7 | struct out_of_bounds {}; 8 | if(offset >= size()) throw out_of_bounds{}; 9 | #endif 10 | return _pool[offset]; 11 | } 12 | 13 | template auto vector::operator[](u64 offset) const -> const T& { 14 | #ifdef DEBUG 15 | struct out_of_bounds {}; 16 | if(offset >= size()) throw out_of_bounds{}; 17 | #endif 18 | return _pool[offset]; 19 | } 20 | 21 | template auto vector::operator()(u64 offset) -> T& { 22 | while(offset >= size()) append(T()); 23 | return _pool[offset]; 24 | } 25 | 26 | template auto vector::operator()(u64 offset, const T& value) const -> const T& { 27 | if(offset >= size()) return value; 28 | return _pool[offset]; 29 | } 30 | 31 | template auto vector::left() -> T& { 32 | return _pool[0]; 33 | } 34 | 35 | template auto vector::left() const -> const T& { 36 | return _pool[0]; 37 | } 38 | 39 | template auto vector::right() -> T& { 40 | return _pool[_size - 1]; 41 | } 42 | 43 | template auto vector::right() const -> const T& { 44 | return _pool[_size - 1]; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /examples/mml/early-release.mml: -------------------------------------------------------------------------------- 1 | #Title q early-release examples 2 | 3 | ; 1 tick = 8ms 4 | #Timer 64 5 | #ZenLen 192 6 | 7 | @1 square adsr 10 2 4 5 8 | 9 | A @1 o4 10 | 11 | ; Early release 12 | A q10 13 | A c d d f2. c d d e2. r 14 | 15 | ; Early release with linear decrease GAIN release envelope 16 | A q10,D16 17 | A c d d f2. c d d e2. r 18 | 19 | ; Early release with exponential decrease GAIN release envelope 20 | A q8,E28 21 | A c d d f2. c d d e2. r 22 | 23 | ; If early release ticks > note ticks, the note will only be played for a single tick 24 | ; Using Fixed GAIN so the single tick is audible 25 | A G127 26 | A q254 27 | A c d d f2. c d d e2. r 28 | A @1 29 | 30 | 31 | ; Early release with minimum tick parameter 32 | ; (The note will be played for a minimum of 12 ticks before early-release) 33 | A q48,12 34 | A c d d f2. c d d e2. r 35 | 36 | 37 | ; Early release with minimum tick parameter and GAIN envelope 38 | ; (The notes will be played for a minimum of 24 ticks before early-release) 39 | A q48,24,D8 40 | A c d d f2. c d d e2. r 41 | 42 | 43 | ; The tick parameters are MML lengths when prefixed with `l` 44 | ; (`ql8,l32` == `q24,6` with a 192 ZenLen) 45 | A ql8,l32 46 | A c d d f2. c d d e2. r 47 | 48 | 49 | ; Disable early release 50 | A q0 51 | A c d d f2. c d d e2. r 52 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/dsp/counter.cpp: -------------------------------------------------------------------------------- 1 | //number of samples per counter event 2 | //all rates are evenly divisible by counter_range (0x7800, 30720, or 2048 * 5 * 3) 3 | //note that rate[0] is a special case, which never triggers 4 | 5 | const n16 DSP::CounterRate[32] = { 6 | 0, 2048, 1536, 7 | 1280, 1024, 768, 8 | 640, 512, 384, 9 | 320, 256, 192, 10 | 160, 128, 96, 11 | 80, 64, 48, 12 | 40, 32, 24, 13 | 20, 16, 12, 14 | 10, 8, 6, 15 | 5, 4, 3, 16 | 2, 17 | 1, 18 | }; 19 | 20 | //counter offset from zero 21 | //counters do not appear to be aligned at zero for all rates 22 | 23 | const n16 DSP::CounterOffset[32] = { 24 | 0, 0, 1040, 25 | 536, 0, 1040, 26 | 536, 0, 1040, 27 | 536, 0, 1040, 28 | 536, 0, 1040, 29 | 536, 0, 1040, 30 | 536, 0, 1040, 31 | 536, 0, 1040, 32 | 536, 0, 1040, 33 | 536, 0, 1040, 34 | 0, 35 | 0, 36 | }; 37 | 38 | inline auto DSP::counterTick() -> void { 39 | if(!clock.counter) clock.counter = 2048 * 5 * 3; //30720 (0x7800) 40 | clock.counter--; 41 | } 42 | 43 | //return true if counter event should trigger 44 | 45 | inline auto DSP::counterPoll(u32 rate) -> bool { 46 | if(rate == 0) return false; 47 | return (clock.counter + CounterOffset[rate]) % CounterRate[rate] == 0; 48 | } 49 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/decode/base.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall::Decode { 6 | 7 | template inline auto Base(const string& value) -> T { 8 | static const string format = 9 | Bits == 2 ? "01" 10 | : Bits == 8 ? "01234567" 11 | : Bits == 10 ? "0123456789" 12 | : Bits == 16 ? "0123456789abcdef" 13 | : Bits == 32 ? "0123456789abcdefghijklmnopqrstuv" 14 | : Bits == 34 ? "023456789abcdefghijkmnopqrstuvwxyz" //1l 15 | : Bits == 36 ? "0123456789abcdefghijklmnopqrstuvwxyz" 16 | : Bits == 57 ? "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" //01IOl 17 | : Bits == 62 ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 18 | : Bits == 64 ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{}" 19 | : Bits == 85 ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%()+,-.:;=@[]^_`{|}~" //\ "&'*/<>? 20 | : ""; 21 | static bool initialized = false; 22 | static u8 lookup[256] = {}; 23 | if(!initialized) { 24 | initialized = true; 25 | for(u32 n : range(format.size())) { 26 | lookup[format[n]] = n; 27 | } 28 | } 29 | 30 | T result = 0; 31 | for(auto byte : value) { 32 | result = result * Bits + lookup[byte]; 33 | } 34 | return result; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/literals.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | inline constexpr auto operator"" _Kibit(unsigned long long value) { return value * 1024 / 8; } 6 | inline constexpr auto operator"" _Mibit(unsigned long long value) { return value * 1024 * 1024 / 8; } 7 | inline constexpr auto operator"" _Gibit(unsigned long long value) { return value * 1024 * 1024 * 1024 / 8; } 8 | inline constexpr auto operator"" _Tibit(unsigned long long value) { return value * 1024 * 1024 * 1024 * 1024 / 8; } 9 | 10 | inline constexpr auto operator"" _KiB(unsigned long long value) { return value * 1024; } 11 | inline constexpr auto operator"" _MiB(unsigned long long value) { return value * 1024 * 1024; } 12 | inline constexpr auto operator"" _GiB(unsigned long long value) { return value * 1024 * 1024 * 1024; } 13 | inline constexpr auto operator"" _TiB(unsigned long long value) { return value * 1024 * 1024 * 1024 * 1024; } 14 | 15 | inline constexpr auto operator"" _KHz(unsigned long long value) { return value * 1000; } 16 | inline constexpr auto operator"" _MHz(unsigned long long value) { return value * 1000 * 1000; } 17 | inline constexpr auto operator"" _GHz(unsigned long long value) { return value * 1000 * 1000 * 1000; } 18 | inline constexpr auto operator"" _THz(unsigned long long value) { return value * 1000 * 1000 * 1000 * 1000; } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/encode/wav.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall::Encode { 4 | 5 | struct WAV { 6 | static auto stereo_16bit(const string& filename, array_view left, array_view right, u32 frequency) -> bool { 7 | if(left.size() != right.size()) return false; 8 | static u32 channels = 2; 9 | static u32 bits = 16; 10 | static u32 samples = left.size(); 11 | 12 | file_buffer fp; 13 | if(!fp.open(filename, file::mode::write)) return false; 14 | 15 | fp.write('R'); 16 | fp.write('I'); 17 | fp.write('F'); 18 | fp.write('F'); 19 | fp.writel(4 + (8 + 16) + (8 + samples * 4), 4); 20 | 21 | fp.write('W'); 22 | fp.write('A'); 23 | fp.write('V'); 24 | fp.write('E'); 25 | 26 | fp.write('f'); 27 | fp.write('m'); 28 | fp.write('t'); 29 | fp.write(' '); 30 | fp.writel(16, 4); 31 | fp.writel(1, 2); 32 | fp.writel(channels, 2); 33 | fp.writel(frequency, 4); 34 | fp.writel(frequency * channels * bits, 4); 35 | fp.writel(channels * bits, 2); 36 | fp.writel(bits, 2); 37 | 38 | fp.write('d'); 39 | fp.write('a'); 40 | fp.write('t'); 41 | fp.write('a'); 42 | fp.writel(samples * 4, 4); 43 | for(u32 sample : range(samples)) { 44 | fp.writel(left[sample], 2); 45 | fp.writel(right[sample], 2); 46 | } 47 | 48 | return true; 49 | } 50 | }; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/decode/url.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall::Decode { 6 | 7 | //returns empty string on malformed content 8 | inline auto URL(string_view input) -> string { 9 | string output; 10 | for(u32 n = 0; n < input.size();) { 11 | char c = input[n]; 12 | 13 | //unreserved characters 14 | if(c >= 'A' && c <= 'Z') { output.append(c); n++; continue; } 15 | if(c >= 'a' && c <= 'z') { output.append(c); n++; continue; } 16 | if(c >= '0' && c <= '9') { output.append(c); n++; continue; } 17 | if(c == '-' || c == '_' || c == '.' || c == '~') { output.append(c); n++; continue; } 18 | 19 | //special characters 20 | if(c == '+') { output.append(' '); n++; continue; } 21 | 22 | //reserved characters 23 | if(c != '%' || n + 2 >= input.size()) return ""; 24 | char hi = input[n + 1]; 25 | char lo = input[n + 2]; 26 | if(hi >= '0' && hi <= '9') hi -= '0'; 27 | else if(hi >= 'A' && hi <= 'F') hi -= 'A' - 10; 28 | else if(hi >= 'a' && hi <= 'f') hi -= 'a' - 10; 29 | else return ""; 30 | if(lo >= '0' && lo <= '9') lo -= '0'; 31 | else if(lo >= 'A' && lo <= 'F') lo -= 'A' - 10; 32 | else if(lo >= 'a' && lo <= 'f') lo -= 'a' - 10; 33 | else return ""; 34 | char byte = hi * 16 + lo; 35 | output.append(byte); 36 | n += 3; 37 | } 38 | return output; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/encode/base.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //required bytes: ceil(bits / log2(base)) 4 | //base57 => 128=22, 256=44, 512=88 5 | //base62 => 128=22, 256=43, 512=86 6 | //base64 => 128=22, 256=43, 512=86 7 | 8 | #include 9 | 10 | namespace nall::Encode { 11 | 12 | template inline auto Base(T value) -> string { 13 | static const string format = 14 | Bits == 2 ? "01" 15 | : Bits == 8 ? "01234567" 16 | : Bits == 10 ? "0123456789" 17 | : Bits == 16 ? "0123456789abcdef" 18 | : Bits == 32 ? "0123456789abcdefghijklmnopqrstuv" 19 | : Bits == 34 ? "023456789abcdefghijkmnopqrstuvwxyz" //1l 20 | : Bits == 36 ? "0123456789abcdefghijklmnopqrstuvwxyz" 21 | : Bits == 57 ? "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" //01IOl 22 | : Bits == 62 ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 23 | : Bits == 64 ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{}" 24 | : Bits == 85 ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%()+,-.:;=@[]^_`{|}~" //\ "&'*/<>? 25 | : ""; 26 | static const u32 size = ceil(sizeof(T) * 8 / log2(Bits)); 27 | 28 | string result; 29 | result.resize(size); 30 | char* data = result.get() + size; 31 | for(auto byte : result) { 32 | *--data = format[value % Bits]; 33 | value /= Bits; 34 | } 35 | return result; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/smp/memory.cpp: -------------------------------------------------------------------------------- 1 | inline auto SMP::readRAM(n16 address) -> n8 { 2 | if(address >= 0xffc0 && io.iplromEnable) return iplrom[address & 0x3f]; 3 | if(io.ramDisable) return 0x5a; //0xff on mini-SNES 4 | return dsp.apuram[address]; 5 | } 6 | 7 | inline auto SMP::writeRAM(n16 address, n8 data) -> void { 8 | //writes to $ffc0-$ffff always go to apuram, even if the iplrom is enabled 9 | if(io.ramWritable && !io.ramDisable) dsp.apuram[address] = data; 10 | } 11 | 12 | auto SMP::idle() -> void { 13 | wait(0); 14 | } 15 | 16 | auto SMP::read(n16 address) -> n8 { 17 | if((address & 0xfffc) == 0x00f4) { 18 | //reads from $00f4-$00f7 require more time than internal reads 19 | wait(1, address); 20 | n8 data = readRAM(address); 21 | if((address & 0xfff0) == 0x00f0) data = readIO(address); 22 | wait(1, address); 23 | return data; 24 | } else { 25 | wait(0, address); 26 | n8 data = readRAM(address); 27 | if((address & 0xfff0) == 0x00f0) data = readIO(address); 28 | return data; 29 | } 30 | } 31 | 32 | auto SMP::write(n16 address, n8 data) -> void { 33 | wait(0, address); 34 | writeRAM(address, data); //even IO writes affect underlying RAM 35 | if((address & 0xfff0) == 0x00f0) writeIO(address, data); 36 | } 37 | 38 | auto SMP::readDisassembler(n16 address) -> n8 { 39 | if((address & 0xfff0) == 0x00f0) return 0x00; 40 | return readRAM(address); 41 | } 42 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/hash/hash.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | //cannot use constructor inheritance due to needing to call virtual reset(); 8 | //instead, define a macro to reduce boilerplate code in every Hash subclass 9 | #define nallHash(Name) \ 10 | Name() { reset(); } \ 11 | Name(const void* data, u64 size) : Name() { input(data, size); } \ 12 | Name(const vector& data) : Name() { input(data); } \ 13 | Name(const string& data) : Name() { input(data); } \ 14 | using Hash::input; \ 15 | 16 | namespace nall::Hash { 17 | 18 | struct Hash { 19 | virtual auto reset() -> void = 0; 20 | virtual auto input(u8 data) -> void = 0; 21 | virtual auto output() const -> vector = 0; 22 | 23 | auto input(array_view data) -> void { 24 | for(auto byte : data) input(byte); 25 | } 26 | 27 | auto input(const void* data, u64 size) -> void { 28 | auto p = (const u8*)data; 29 | while(size--) input(*p++); 30 | } 31 | 32 | auto input(const vector& data) -> void { 33 | for(auto byte : data) input(byte); 34 | } 35 | 36 | auto input(const string& data) -> void { 37 | for(auto byte : data) input(byte); 38 | } 39 | 40 | auto digest() const -> string { 41 | string result; 42 | for(auto n : output()) result.append(hex(n, 2L)); 43 | return result; 44 | } 45 | }; 46 | 47 | } 48 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/memory.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace nall::memory { 4 | 5 | NALL_HEADER_INLINE auto map(u32 size, bool executable) -> void* { 6 | #if defined(API_WINDOWS) 7 | DWORD protect = executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; 8 | return VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, protect); 9 | #elif defined(API_POSIX) 10 | int prot = PROT_READ | PROT_WRITE; 11 | int flags = MAP_ANON | MAP_PRIVATE; 12 | if(executable) { 13 | prot |= PROT_EXEC; 14 | #if defined(PLATFORM_MACOS) 15 | flags |= MAP_JIT; 16 | #endif 17 | } 18 | return mmap(nullptr, size, prot, flags, -1, 0); 19 | #else 20 | return nullptr; 21 | #endif 22 | } 23 | 24 | NALL_HEADER_INLINE auto unmap(void* target, u32 size) -> void { 25 | #if defined(API_WINDOWS) 26 | VirtualFree(target, 0, MEM_RELEASE); 27 | #elif defined(API_POSIX) 28 | munmap(target, size); 29 | #endif 30 | } 31 | 32 | NALL_HEADER_INLINE auto protect(void* target, u32 size, bool executable) -> bool { 33 | #if defined(API_WINDOWS) 34 | DWORD protect = executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; 35 | DWORD oldProtect; 36 | return VirtualProtect(target, size, protect, &oldProtect); 37 | #elif defined(API_POSIX) 38 | int prot = PROT_READ | PROT_WRITE; 39 | if(executable) { 40 | prot |= PROT_EXEC; 41 | } 42 | return !mprotect(target, size, prot); 43 | #endif 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/vector/core.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template vector::vector(const initializer_list& values) { 6 | reserveRight(values.size()); 7 | for(auto& value : values) append(value); 8 | } 9 | 10 | template vector::vector(const vector& source) { 11 | operator=(source); 12 | } 13 | 14 | template vector::vector(vector&& source) { 15 | operator=(std::move(source)); 16 | } 17 | 18 | template vector::~vector() { 19 | reset(); 20 | } 21 | 22 | template vector::operator bool() const { 23 | return _size; 24 | } 25 | 26 | template vector::operator array_span() { 27 | return {data(), size()}; 28 | } 29 | 30 | template vector::operator array_view() const { 31 | return {data(), size()}; 32 | } 33 | 34 | template template auto vector::capacity() const -> u64 { 35 | return (_left + _size + _right) * sizeof(T) / sizeof(Cast); 36 | } 37 | 38 | template template auto vector::size() const -> u64 { 39 | return _size * sizeof(T) / sizeof(Cast); 40 | } 41 | 42 | template template auto vector::data() -> Cast* { 43 | return (Cast*)_pool; 44 | } 45 | 46 | template template auto vector::data() const -> const Cast* { 47 | return (const Cast*)_pool; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/random.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(PLATFORM_LINUX) && __has_include() 4 | #include 5 | #elif defined(PLATFORM_ANDROID) && __has_include() 6 | #include 7 | #elif defined(PLATFORM_WINDOWS) && __has_include() 8 | #include 9 | #else 10 | #include 11 | #endif 12 | 13 | namespace nall { 14 | 15 | NALL_HEADER_INLINE auto RNGBase::randomSeed() -> u256 { 16 | u256 seed = 0; 17 | #if defined(PLATFORM_BSD) || defined(PLATFORM_MACOS) 18 | for(u32 n : range(8)) seed = seed << 32 | (u32)arc4random(); 19 | #elif defined(PLATFORM_LINUX) && __has_include() 20 | getrandom(&seed, 32, GRND_NONBLOCK); 21 | #elif defined(PLATFORM_ANDROID) && __has_include() 22 | syscall(__NR_getrandom, &seed, 32, 0x0001); //GRND_NONBLOCK 23 | #elif defined(PLATFORM_WINDOWS) && __has_include() 24 | HCRYPTPROV provider; 25 | if(CryptAcquireContext(&provider, nullptr, MS_STRONG_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { 26 | CryptGenRandom(provider, 32, (BYTE*)&seed); 27 | CryptReleaseContext(provider, 0); 28 | } 29 | #else 30 | srand(time(nullptr)); 31 | for(u32 n : range(32)) seed = seed << 8 | (u8)rand(); 32 | if(auto fp = fopen("/dev/urandom", "rb")) { 33 | fread(&seed, 32, 1, fp); 34 | fclose(fp); 35 | } 36 | #endif 37 | return seed; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/string/convert.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | inline auto string::downcase() -> string& { 6 | char* p = get(); 7 | for(u32 n = 0; n < size(); n++) { 8 | if(p[n] >= 'A' && p[n] <= 'Z') p[n] += 0x20; 9 | } 10 | return *this; 11 | } 12 | 13 | inline auto string::qdowncase() -> string& { 14 | char* p = get(); 15 | for(u32 n = 0, quoted = 0; n < size(); n++) { 16 | if(p[n] == '\"') quoted ^= 1; 17 | if(!quoted && p[n] >= 'A' && p[n] <= 'Z') p[n] += 0x20; 18 | } 19 | return *this; 20 | } 21 | 22 | inline auto string::upcase() -> string& { 23 | char* p = get(); 24 | for(u32 n = 0; n < size(); n++) { 25 | if(p[n] >= 'a' && p[n] <= 'z') p[n] -= 0x20; 26 | } 27 | return *this; 28 | } 29 | 30 | inline auto string::qupcase() -> string& { 31 | char* p = get(); 32 | for(u32 n = 0, quoted = 0; n < size(); n++) { 33 | if(p[n] == '\"') quoted ^= 1; 34 | if(!quoted && p[n] >= 'a' && p[n] <= 'z') p[n] -= 0x20; 35 | } 36 | return *this; 37 | } 38 | 39 | inline auto string::transform(string_view from, string_view to) -> string& { 40 | if(from.size() != to.size() || from.size() == 0) return *this; //patterns must be the same length 41 | char* p = get(); 42 | for(u32 n = 0; n < size(); n++) { 43 | for(u32 s = 0; s < from.size(); s++) { 44 | if(p[n] == from[s]) { 45 | p[n] = to[s]; 46 | break; 47 | } 48 | } 49 | } 50 | return *this; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/encode/rle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall::Encode { 4 | 5 | template //S = word size; M = match length 6 | inline auto RLE(array_view input) -> vector { 7 | vector output; 8 | for(u32 byte : range(8)) output.append(input.size() >> byte * 8); 9 | 10 | u32 base = 0; 11 | u32 skip = 0; 12 | 13 | auto load = [&](u32 offset) -> u8 { 14 | return input(offset); 15 | }; 16 | 17 | auto read = [&](u32 offset) -> u64 { 18 | u64 value = 0; 19 | for(u32 byte : range(S)) value |= load(offset + byte) << byte * 8; 20 | return value; 21 | }; 22 | 23 | auto write = [&](u64 value) -> void { 24 | for(u32 byte : range(S)) output.append(value >> byte * 8); 25 | }; 26 | 27 | auto flush = [&] { 28 | output.append(skip - 1); 29 | do { 30 | write(read(base)); 31 | base += S; 32 | } while(--skip); 33 | }; 34 | 35 | while(base + S * skip < input.size()) { 36 | u32 same = 1; 37 | for(u32 offset = base + S * (skip + 1); offset < input.size(); offset += S) { 38 | if(read(offset) != read(base + S * skip)) break; 39 | if(++same == 127 + M) break; 40 | } 41 | 42 | if(same < M) { 43 | if(++skip == 128) flush(); 44 | } else { 45 | if(skip) flush(); 46 | output.append(128 | same - M); 47 | write(read(base)); 48 | base += S * same; 49 | } 50 | } 51 | if(skip) flush(); 52 | 53 | return output; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/thread.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace nall { 4 | 5 | #if defined(API_WINDOWS) 6 | 7 | NALL_HEADER_INLINE auto WINAPI _threadCallback(void* parameter) -> DWORD { 8 | auto context = (thread::context*)parameter; 9 | context->callback(context->parameter); 10 | delete context; 11 | return 0; 12 | } 13 | 14 | NALL_HEADER_INLINE auto thread::close() -> void { 15 | if(handle) { 16 | CloseHandle(handle); 17 | handle = 0; 18 | } 19 | } 20 | 21 | NALL_HEADER_INLINE auto thread::join() -> void { 22 | if(handle) { 23 | //wait until the thread has finished executing ... 24 | WaitForSingleObject(handle, INFINITE); 25 | CloseHandle(handle); 26 | handle = 0; 27 | } 28 | } 29 | 30 | NALL_HEADER_INLINE auto thread::create(const function& callback, uintptr parameter, u32 stacksize) -> thread { 31 | thread instance; 32 | 33 | auto context = new thread::context; 34 | context->callback = callback; 35 | context->parameter = parameter; 36 | 37 | instance.handle = CreateThread(nullptr, stacksize, _threadCallback, (void*)context, 0, nullptr); 38 | return instance; 39 | } 40 | 41 | NALL_HEADER_INLINE auto thread::detach() -> void { 42 | //Windows threads do not use this concept: 43 | //~thread() frees resources via CloseHandle() 44 | //thread continues to run even after handle is closed 45 | } 46 | 47 | NALL_HEADER_INLINE auto thread::exit() -> void { 48 | ExitThread(0); 49 | } 50 | 51 | #endif 52 | 53 | } 54 | -------------------------------------------------------------------------------- /audio-driver/asar-api/test/GNUmakefile: -------------------------------------------------------------------------------- 1 | 2 | MAKEFLAGS += --no-builtin-rules --warn-undefined-variables 3 | .DELETE_ON_ERROR: 4 | .SUFFIXES: 5 | 6 | 7 | ASAR ?= asar 8 | 64TASS ?= 64tass 9 | 10 | 11 | .PHONY: test asar 64tass clean 12 | 13 | test: asar 64tass 14 | diff --report-identical-files out/asar-lorom.bin out/64tass-lorom.bin 15 | diff --report-identical-files out/asar-hirom.bin out/64tass-hirom.bin 16 | 17 | 18 | 19 | asar: out/asar-lorom.bin out/asar-hirom.bin 20 | 21 | ASAR_INCLUDES := ../tad-variables.inc ../tad-constants.inc ../tad-process.inc ../tad-control.inc 22 | 23 | out/asar-lorom.bin: main.asm $(ASAR_INCLUDES) 24 | $(ASAR) -werror --no-title-check --symbols=wla -DLOROM '$<' '$@' 25 | 26 | out/asar-hirom.bin: main.asm $(ASAR_INCLUDES) 27 | $(ASAR) -werror --no-title-check --symbols=wla -DHIROM '$<' '$@' 28 | 29 | 30 | 31 | 64tass: out/64tass-lorom.bin out/64tass-hirom.bin 32 | 33 | 64TASS_INCLUDES := ../../64tass-api/tad-code.inc ../../64tass-api/tad-process.inc ../../64tass-api/tad-lowram.inc ../../64tass-api/tad-zeropage.inc 34 | 35 | out/64tass-lorom.bin: ../../64tass-api/test/64tass/main.asm $(64TASS_INCLUDES) 36 | $(64TASS) -a -x -X --nostart -Wall -C -DLOROM=1 -o '$@' '$<' 37 | 38 | out/64tass-hirom.bin: ../../64tass-api/test/64tass/main.asm $(64TASS_INCLUDES) 39 | $(64TASS) -a -x -X --nostart -Wall -C -DHIROM=1 -o '$@' '$<' 40 | 41 | 42 | 43 | clean: 44 | $(RM) out/asar-lorom.bin out/asar-lorom.sym out/asar-hirom.bin out/asar-hirom.sym 45 | $(RM) out/64tass-lorom.bin out/64tass-hirom.bin 46 | 47 | -------------------------------------------------------------------------------- /audio-driver/ca65-api/tests/_common/hirom256k.cfg: -------------------------------------------------------------------------------- 1 | # HIROM config for a 256 KiB (2 Mbit) sfc file 2 | 3 | MEMORY { 4 | ZEROPAGE: start = $000000, size = $100; 5 | STACK: start = $000100, size = $100, define = yes; 6 | BSS: start = $000200, size = $1e00; 7 | WRAM7E: start = $7e2000, size = $e000; 8 | WRAM7F: start = $7f0000, size =$10000; 9 | 10 | ROM0_C0: start = $c00000, size = $8000, type = ro, file = %O, fill=yes, fillval=$ff; 11 | ROM0_80: start = $808000, size = $8000, type = ro, file = %O, fill=yes, fillval=$ff; 12 | ROM1: start = $c10000, size = $10000, type = ro, file = %O, fill=yes, fillval=$ff; 13 | ROM2: start = $c20000, size = $10000, type = ro, file = %O, fill=yes, fillval=$ff; 14 | ROM3: start = $c30000, size = $10000, type = ro, file = %O, fill=yes, fillval=$ff; 15 | } 16 | 17 | SEGMENTS { 18 | CODE: load = ROM0_80, type = ro; 19 | SNESHEADER: load = ROM0_80, type = ro, start = $80ffb0; 20 | CODE1: load = ROM0_C0, type = ro, optional=yes; 21 | 22 | RODATA: load = ROM1, type = ro, optional=yes; 23 | RODATA1: load = ROM1, type = ro, optional=yes; 24 | RODATA2: load = ROM2, type = ro, optional=yes; 25 | RODATA3: load = ROM3, type = ro, optional=yes; 26 | 27 | ZEROPAGE: load = ZEROPAGE, type = zp; 28 | BSS: load = BSS, type = bss; 29 | WRAM7E: load = WRAM7E, type = bss, optional=yes; 30 | WRAM7F: load = WRAM7F, type = bss, optional=yes; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /crates/compiler/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Audio Driver compiler 2 | 3 | // SPDX-FileCopyrightText: © 2023 Marcus Rowe 4 | // 5 | // SPDX-License-Identifier: MIT 6 | 7 | #![forbid(unsafe_code)] 8 | 9 | mod bytecode; 10 | mod command_compiler; 11 | mod file_pos; 12 | mod value_newtypes; 13 | 14 | pub mod bytecode_assembler; 15 | pub mod bytecode_interpreter; 16 | pub mod common_audio_data; 17 | pub mod data; 18 | pub mod driver_constants; 19 | pub mod echo; 20 | pub mod envelope; 21 | pub mod errors; 22 | pub mod export; 23 | pub mod identifier; 24 | pub mod invert_flags; 25 | pub mod mml; 26 | pub mod notes; 27 | pub mod path; 28 | pub mod pitch_table; 29 | pub mod samples; 30 | pub mod sfx_file; 31 | pub mod songs; 32 | pub mod sound_effects; 33 | pub mod spc_file_export; 34 | pub mod subroutines; 35 | pub mod tad_apu; 36 | pub mod time; 37 | 38 | pub use bytecode::opcodes; 39 | 40 | pub use bytecode::{Pan, Transpose}; 41 | pub use file_pos::{FilePos, FilePosRange}; 42 | 43 | pub use value_newtypes::{I8WithByteHexValueNewType, SignedValueNewType, UnsignedValueNewType}; 44 | 45 | pub mod audio_driver { 46 | // SPDX-SnippetBegin 47 | 48 | // SDPX—SnippetName: SPC700 Audio Driver 49 | // SPDX-SnippetCopyrightText: © 2023 Marcus Rowe 50 | // SPDX-License-Identifier: Zlib 51 | pub const LOADER: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/loader.bin")); 52 | pub const AUDIO_DRIVER: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/audio-driver.bin")); 53 | // SPDX-SnippetEnd 54 | } 55 | -------------------------------------------------------------------------------- /manual-tests/mml/key-signature.mml: -------------------------------------------------------------------------------- 1 | #Title Key Signature Test 2 | #Tempo 120 3 | 4 | ; Sets the default key signature for all channels and subroutines 5 | ; This is the equivalent to adding `_{}` to the start of every channel and subroutine. 6 | ; Multiple key signature commands can be seperated by commas. 7 | #KeySignature -bea ; E Flat Major 8 | 9 | @1 square 10 | 11 | A @1 12 | 13 | ; E Flat Major scale (from #KeySignature header) 14 | A o4 efgab>cde r 15 | A _{=abcdefg} 16 | 17 | ; A major scale 18 | A _{+fcg} o4 ab>cdefga r 19 | A _{=fcg} 20 | 21 | ; B♭ major scale 22 | A _{-be} o4 b>cdefgab r 23 | A _{=be} 24 | 25 | ; Sharp & Flat signature 26 | A _{+cfg} _{-be} o4 cdefgab>c r 27 | A _{=abcdefg} 28 | 29 | ; Sharps and flats are relative to the key signature 30 | A _{+c} o4 c-- c- c c+ c++ r 31 | A _{=c} o4 c-- c- c c+ c++ r 32 | 33 | ; a `=` on a pitch will ignore the key signature 34 | ; (from mml-syntax.mml) 35 | A _{+fcg} g= ; plays g natural 36 | A _{+fcg} g=- g=+ ; plays g- then g+ 37 | A _{=fcg} r 38 | 39 | ; key signature and channel transpose are independent settings 40 | ; (tests tad-gui statusbar) 41 | A _{+fc} _M+2 c __M-3 _{-de} _{=c} d 42 | A _{=abcdefg} _M0 r 43 | 44 | ; Subroutines and channels have independent key signatures 45 | 46 | ; The following plays `fb-c r8 f+bc+` 47 | !s1 fbc r8 ; f is sharp from #KeySignature value 48 | A _{+fc} !s1 fbc 49 | A _{=fc} r 50 | 51 | ; The following plays `f+bc+ r8 fb-c` 52 | !s2 _{=abcdefg} ; reset #KeySignature value 53 | !s2 _{+fc} fbc r8 54 | A _{-be} !s2 fbc 55 | A _{=be} r 56 | 57 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/adaptive-array.hpp: -------------------------------------------------------------------------------- 1 | //deprecated 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace nall { 9 | 10 | template 11 | struct adaptive_array { 12 | auto capacity() const -> u32 { return Capacity; } 13 | auto size() const -> u32 { return _size; } 14 | 15 | auto reset() -> void { 16 | for(u32 n : range(_size)) _pool.t[n].~T(); 17 | _size = 0; 18 | } 19 | 20 | auto operator[](u32 index) -> T& { 21 | #ifdef DEBUG 22 | struct out_of_bounds {}; 23 | if(index >= Capacity) throw out_of_bounds{}; 24 | #endif 25 | return _pool.t[index]; 26 | } 27 | 28 | auto operator[](u32 index) const -> const T& { 29 | #ifdef DEBUG 30 | struct out_of_bounds {}; 31 | if(index >= Capacity) throw out_of_bounds{}; 32 | #endif 33 | return _pool.t[index]; 34 | } 35 | 36 | auto append() -> T& { 37 | new(_pool.t + _size) T; 38 | return _pool.t[_size++]; 39 | } 40 | 41 | auto append(const T& value) -> void { 42 | new(_pool.t + _size++) T(value); 43 | } 44 | 45 | auto append(T&& value) -> void { 46 | new(_pool.t + _size++) T(std::move(value)); 47 | } 48 | 49 | auto begin() { return &_pool.t[0]; } 50 | auto end() { return &_pool.t[_size]; } 51 | 52 | auto begin() const { return &_pool.t[0]; } 53 | auto end() const { return &_pool.t[_size]; } 54 | 55 | private: 56 | union U { 57 | U() {} 58 | ~U() {} 59 | T t[Capacity]; 60 | } _pool; 61 | u32 _size = 0; 62 | }; 63 | 64 | } 65 | -------------------------------------------------------------------------------- /crates/tad-gui/src/licenses_dialog.rs: -------------------------------------------------------------------------------- 1 | //! A small modal dialogs that displays the program licensing text 2 | 3 | // SPDX-FileCopyrightText: © 2023 Marcus Rowe 4 | // 5 | // SPDX-License-Identifier: MIT 6 | 7 | extern crate fltk; 8 | use fltk::misc::HelpView; 9 | use fltk::prelude::{GroupExt, WidgetBase, WidgetExt, WindowExt}; 10 | use fltk::window::Window; 11 | 12 | const LICENSES_SHORT_HTML: &str = include_str!(concat!(env!("OUT_DIR"), "/licenses-short.html")); 13 | 14 | struct LicensesDialogContents { 15 | window: Window, 16 | view: HelpView, 17 | } 18 | 19 | pub struct LicensesDialog { 20 | dialog: Option, 21 | } 22 | 23 | impl LicensesDialog { 24 | pub fn new() -> Self { 25 | Self { dialog: None } 26 | } 27 | 28 | pub fn show(&mut self) { 29 | match &mut self.dialog { 30 | Some(d) => d.show(), 31 | None => self.dialog = Some(LicensesDialogContents::new()), 32 | } 33 | } 34 | } 35 | 36 | impl LicensesDialogContents { 37 | fn new() -> Self { 38 | let mut window = Window::default().with_size(780, 480).with_label("Licences"); 39 | window.make_resizable(true); 40 | 41 | let mut view = HelpView::default_fill(); 42 | view.set_text_size(16); 43 | view.set_value(LICENSES_SHORT_HTML); 44 | 45 | window.end(); 46 | 47 | window.make_modal(true); 48 | window.show(); 49 | 50 | Self { window, view } 51 | } 52 | 53 | fn show(&mut self) { 54 | self.view.set_top_line(0); 55 | self.window.show(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/primitives/real.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template struct Real { 6 | static_assert(Precision == 32 || Precision == 64); 7 | static constexpr auto bits() -> u32 { return Precision; } 8 | using ftype = 9 | conditional_t>; 12 | 13 | Real() : data(0.0) {} 14 | template Real(Real value) : data((ftype)value) {} 15 | template Real(const T& value) : data((ftype)value) {} 16 | explicit Real(const char* value) : data((ftype)toReal(value)) {} 17 | 18 | operator ftype() const { return data; } 19 | 20 | auto operator++(s32) { auto value = *this; ++data; return value; } 21 | auto operator--(s32) { auto value = *this; --data; return value; } 22 | 23 | auto& operator++() { data++; return *this; } 24 | auto& operator--() { data--; return *this; } 25 | 26 | template auto& operator =(const T& value) { data = value; return *this; } 27 | template auto& operator*=(const T& value) { data = data * value; return *this; } 28 | template auto& operator/=(const T& value) { data = data / value; return *this; } 29 | template auto& operator%=(const T& value) { data = data % value; return *this; } 30 | template auto& operator+=(const T& value) { data = data + value; return *this; } 31 | template auto& operator-=(const T& value) { data = data - value; return *this; } 32 | 33 | auto serialize(serializer& s) { s(data); } 34 | 35 | private: 36 | ftype data; 37 | }; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /audio-driver/64tass-api/test/GNUmakefile: -------------------------------------------------------------------------------- 1 | 2 | MAKEFLAGS += --no-builtin-rules --warn-undefined-variables 3 | .SUFFIXES: 4 | 5 | 6 | AS65 ?= ca65 7 | LD65 ?= ld65 8 | 64TASS ?= 64tass 9 | 10 | 11 | .PHONY: test 64tass ca65 clean 12 | 13 | test: 64tass ca65 14 | diff --report-identical-files out/64tass-lorom.bin out/ca65-lorom.bin 15 | diff --report-identical-files out/64tass-hirom.bin out/ca65-hirom.bin 16 | 17 | 18 | 19 | 64tass: out/64tass-lorom.bin out/64tass-hirom.bin 20 | 21 | 64TASS_API := ../tad-code.inc ../tad-process.inc ../tad-zeropage.inc ../tad-lowram.inc 22 | 23 | out/64tass-lorom.bin: 64tass/main.asm $(64TASS_API) 24 | $(64TASS) -a -x -X --nostart -Wall -C -DLOROM=1 --verbose-list -L $(basename $@).txt -o $@ 64tass/main.asm 25 | 26 | out/64tass-hirom.bin: 64tass/main.asm $(64TASS_API) 27 | $(64TASS) -a -x -X --nostart -Wall -C -DHIROM=1 --verbose-list -L $(basename $@).txt -o $@ 64tass/main.asm 28 | 29 | 30 | 31 | CA65_BINARIES := out/ca65-lorom.bin out/ca65-hirom.bin 32 | CA65_OBJ := out/ca65-lorom.o out/ca65-hirom.o out/ca65-main.o 33 | 34 | ca65: $(CA65_BINARIES) 35 | 36 | out/ca65-%.bin: out/ca65-%.o out/ca65-main.o ca65/minimal.cfg 37 | $(LD65) -o $@ -C ca65/minimal.cfg $< out/ca65-main.o 38 | 39 | out/ca65-lorom.o: ../../ca65-api/tad-audio.s 40 | $(AS65) -g -DLOROM -o $@ $< 41 | 42 | out/ca65-hirom.o: ../../ca65-api/tad-audio.s 43 | $(AS65) -g -DHIROM -o $@ $< 44 | 45 | out/ca65-main.o: ca65/main.s 46 | $(AS65) -g -o $@ $< 47 | 48 | 49 | 50 | clean: 51 | $(RM) out/64tass-lorom.bin out/64tass-hirom.bin out/64tass-lorom.txt out/64tass-hirom.txt 52 | $(RM) $(CA65_BINARIES) $(CA65_OBJ) 53 | 54 | -------------------------------------------------------------------------------- /docs/special-thanks.md: -------------------------------------------------------------------------------- 1 | Special thanks 2 | ============== 3 | I would like to thank the following people, whose creations or assistance have helped me create the 4 | Terrific Audio Driver: 5 | 6 | * KungFuFurby - for being a great sounding board and beta testing my audio driver. 7 | * Artem V. Ageev - for setting up GitHub CI 8 | * Near - for creating [ares](https://ares-emu.net). 9 | * Sour - for creating [Mesen](http://mesen.ca/). 10 | * Anomie - for writing *Anomie's S-DSP Doc*, an in-depth technical document on the S-DSP. 11 | * eggboycolor - for creating the [wiz programming language](https://github.com/wiz-lang/wiz). 12 | * krom (Peter Lemon) - for writing [a few SPC700 demos](https://github.com/PeterLemon/SNES/tree/master/SPC700), 13 | which helped me to learn SPC700 assembly and provided a nice example for how to access the S-DSP registers. 14 | * Douglas Fraker - for [creating and releasing a lot of free BRR samples](https://nesdoug.com/2022/01/27/why-b21-cents/#free-samples) 15 | * The people on the SNES Development Discord who have helped and offered suggestions when I wanted feedback or got stuck. 16 | * Ian Karlsson - for creating [mmlgui](https://github.com/superctr/mmlgui), whose note tracking 17 | feature was the deciding factor for choosing MML over writing a custom tracker. 18 | * M.Kajihara - for creating the Professional Music Driver (PMD). 19 | * Blaze and Pigu - for translating the Professional Music Driver (PMD) manual. 20 | * The rust team - for creating rust. 21 | * All of the creators and maintainers of the [third party libraries](licenses-short.md#third-party-licenses) 22 | used by the Terrific Audio Driver. 23 | 24 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/traits.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | //pull all type traits used by nall from std namespace into nall namespace 8 | //this removes the requirement to prefix type traits with std:: within nall 9 | 10 | namespace nall { 11 | using std::add_const; 12 | using std::conditional; 13 | using std::conditional_t; 14 | using std::decay; 15 | using std::declval; 16 | using std::enable_if; 17 | using std::enable_if_t; 18 | using std::false_type; 19 | using std::is_floating_point; 20 | using std::is_floating_point_v; 21 | using std::initializer_list; 22 | using std::is_array; 23 | using std::is_array_v; 24 | using std::is_base_of; 25 | using std::is_base_of_v; 26 | using std::is_function; 27 | using std::is_integral; 28 | using std::is_integral_v; 29 | using std::is_pointer; 30 | using std::is_pointer_v; 31 | using std::is_same; 32 | using std::is_same_v; 33 | using std::is_signed; 34 | using std::is_signed_v; 35 | using std::is_unsigned; 36 | using std::is_unsigned_v; 37 | using std::nullptr_t; 38 | using std::remove_extent; 39 | using std::remove_extent_t; 40 | using std::remove_reference; 41 | using std::remove_reference_t; 42 | using std::swap; 43 | using std::true_type; 44 | } 45 | 46 | namespace std { 47 | #if defined(__SIZEOF_INT128__) 48 | template<> struct is_integral : true_type {}; 49 | template<> struct is_integral : true_type {}; 50 | template<> struct is_signed : true_type {}; 51 | template<> struct is_unsigned : true_type {}; 52 | #endif 53 | } 54 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/queue/spsc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //single-producer, single-consumer lockless queue 4 | //includes await functions for spin-loops 5 | 6 | namespace nall { 7 | 8 | template struct queue_spsc; 9 | 10 | template 11 | struct queue_spsc { 12 | auto flush() -> void { 13 | _read = 0; 14 | _write = 2 * Size; 15 | } 16 | 17 | auto size() const -> u32 { 18 | return (_write - _read) % (2 * Size); 19 | } 20 | 21 | auto empty() const -> bool { 22 | return size() == 0; 23 | } 24 | 25 | auto full() const -> bool { 26 | return size() == Size; 27 | } 28 | 29 | auto read() -> maybe { 30 | if(empty()) return nothing; 31 | auto value = _data[_read % Size]; 32 | _read = _read + 1 < 2 * Size ? _read + 1 : 0; 33 | return value; 34 | } 35 | 36 | auto write(const T& value) -> bool { 37 | if(full()) return false; 38 | _data[_write % Size] = value; 39 | _write = _write + 1 < 4 * Size ? _write + 1 : 2 * Size; 40 | return true; 41 | } 42 | 43 | auto await_empty() -> void { 44 | while(!empty()) spinloop(); 45 | } 46 | 47 | auto await_read() -> T { 48 | while(empty()) spinloop(); 49 | auto value = _data[_read % Size]; 50 | _read = _read + 1 < 2 * Size ? _read + 1 : 0; 51 | return value; 52 | } 53 | 54 | auto await_write(const T& value) -> void { 55 | while(full()) spinloop(); 56 | _data[_write % Size] = value; 57 | _write = _write + 1 < 4 * Size ? _write + 1 : 2 * Size; 58 | } 59 | 60 | private: 61 | T _data[Size]; 62 | std::atomic _read = 0; 63 | std::atomic _write = 2 * Size; 64 | }; 65 | 66 | } 67 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/elliptic-curve/curve25519.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined(EC_REFERENCE) 4 | #include 5 | #else 6 | #include 7 | #endif 8 | 9 | namespace nall::EllipticCurve { 10 | 11 | struct Curve25519 { 12 | auto sharedKey(u256 secretKey, u256 basepoint = 9) const -> u256 { 13 | secretKey &= (1_u256 << 254) - 8; 14 | secretKey |= (1_u256 << 254); 15 | basepoint &= ~0_u256 >> 1; 16 | 17 | point p = scalarMultiply(basepoint % P, secretKey); 18 | field k = p.x * reciprocal(p.z); 19 | return k(); 20 | } 21 | 22 | private: 23 | using field = Modulo25519; 24 | struct point { field x, z; }; 25 | const BarrettReduction<256> P = BarrettReduction<256>{EllipticCurve::P}; 26 | 27 | auto montgomeryDouble(point p) const -> point { 28 | field a = square(p.x + p.z); 29 | field b = square(p.x - p.z); 30 | field c = a - b; 31 | field d = a + c * 121665; 32 | return {a * b, c * d}; 33 | } 34 | 35 | auto montgomeryAdd(point p, point q, field b) const -> point { 36 | return { 37 | square(p.x * q.x - p.z * q.z), 38 | square(p.x * q.z - p.z * q.x) * b 39 | }; 40 | } 41 | 42 | auto scalarMultiply(field b, u256 exponent) const -> point { 43 | point p{1, 0}, q{b, 1}; 44 | for(u32 bit : reverse(range(255))) { 45 | bool condition = exponent >> bit & 1; 46 | cswap(condition, p.x, q.x); 47 | cswap(condition, p.z, q.z); 48 | q = montgomeryAdd(p, q, b); 49 | p = montgomeryDouble(p); 50 | cswap(condition, p.x, q.x); 51 | cswap(condition, p.z, q.z); 52 | } 53 | return p; 54 | } 55 | }; 56 | 57 | } 58 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/string/vector.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template inline auto vector::append(const string& data, P&&... p) -> type& { 6 | vector_base::append(data); 7 | append(std::forward

(p)...); 8 | return *this; 9 | } 10 | 11 | inline auto vector::append() -> type& { 12 | return *this; 13 | } 14 | 15 | inline auto vector::isort() -> type& { 16 | sort([](const string& x, const string& y) { 17 | return memory::icompare(x.data(), x.size(), y.data(), y.size()) < 0; 18 | }); 19 | return *this; 20 | } 21 | 22 | inline auto vector::find(string_view source) const -> maybe { 23 | for(u32 n = 0; n < size(); n++) { 24 | if(operator[](n).equals(source)) return n; 25 | } 26 | return {}; 27 | } 28 | 29 | inline auto vector::ifind(string_view source) const -> maybe { 30 | for(u32 n = 0; n < size(); n++) { 31 | if(operator[](n).iequals(source)) return n; 32 | } 33 | return {}; 34 | } 35 | 36 | inline auto vector::match(string_view pattern) const -> vector { 37 | vector result; 38 | for(u32 n = 0; n < size(); n++) { 39 | if(operator[](n).match(pattern)) result.append(operator[](n)); 40 | } 41 | return result; 42 | } 43 | 44 | inline auto vector::merge(string_view separator) const -> string { 45 | string output; 46 | for(u32 n = 0; n < size(); n++) { 47 | output.append(operator[](n)); 48 | if(n < size() - 1) output.append(separator.data()); 49 | } 50 | return output; 51 | } 52 | 53 | inline auto vector::strip() -> type& { 54 | for(u32 n = 0; n < size(); n++) { 55 | operator[](n).strip(); 56 | } 57 | return *this; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/vfs/file.hpp: -------------------------------------------------------------------------------- 1 | namespace nall::vfs { 2 | 3 | struct file : node { 4 | virtual auto readable() const -> bool { return true; } 5 | virtual auto writable() const -> bool { return false; } 6 | 7 | virtual auto data() const -> const u8* = 0; 8 | virtual auto data() -> u8* = 0; 9 | virtual auto size() const -> u64 = 0; 10 | virtual auto offset() const -> u64 = 0; 11 | virtual auto resize(u64 size) -> bool = 0; 12 | 13 | virtual auto seek(s64 offset, index = index::absolute) -> void = 0; 14 | virtual auto read() -> u8 = 0; 15 | virtual auto write(u8 data) -> void = 0; 16 | virtual auto flush() -> void {} 17 | 18 | auto end() const -> bool { 19 | return offset() >= size(); 20 | } 21 | 22 | auto read(array_span span) -> void { 23 | while(span) *span++ = read(); 24 | } 25 | 26 | auto readl(u32 bytes) -> u64 { 27 | u64 data = 0; 28 | for(auto n : range(bytes)) data |= (u64)read() << n * 8; 29 | return data; 30 | } 31 | 32 | auto readm(u32 bytes) -> u64 { 33 | u64 data = 0; 34 | for(auto n : range(bytes)) data = data << 8 | read(); 35 | return data; 36 | } 37 | 38 | auto reads() -> string { 39 | seek(0); 40 | string s; 41 | s.resize(size()); 42 | read(s); 43 | return s; 44 | } 45 | 46 | auto write(array_view view) -> void { 47 | while(view) write(*view++); 48 | } 49 | 50 | auto writel(u64 data, u32 bytes) -> void { 51 | for(auto n : range(bytes)) write(data), data >>= 8; 52 | } 53 | 54 | auto writem(u64 data, u32 bytes) -> void { 55 | for(auto n : reverse(range(bytes))) write(data >> n * 8); 56 | } 57 | 58 | auto writes(const string& s) -> void { 59 | write(s); 60 | } 61 | }; 62 | 63 | } 64 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/image/multifactor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | inline multiFactorImage::multiFactorImage(const multiFactorImage& source) { 6 | (*this) = source; 7 | } 8 | 9 | inline multiFactorImage::multiFactorImage(multiFactorImage&& source) { 10 | operator=(std::forward(source)); 11 | } 12 | 13 | inline multiFactorImage::multiFactorImage(const image& lowDPI, const image& highDPI) { 14 | (*(image*)this) = lowDPI; 15 | _highDPI = highDPI; 16 | } 17 | 18 | inline multiFactorImage::multiFactorImage(const image& source) { 19 | (*(image*)this) = source; 20 | } 21 | 22 | inline multiFactorImage::multiFactorImage(image&& source) { 23 | operator=(std::forward(source)); 24 | } 25 | 26 | inline multiFactorImage::multiFactorImage() { 27 | } 28 | 29 | inline multiFactorImage::~multiFactorImage() { 30 | } 31 | 32 | inline auto multiFactorImage::operator=(const multiFactorImage& source) -> multiFactorImage& { 33 | if(this == &source) return *this; 34 | 35 | (*(image*)this) = source; 36 | _highDPI = source._highDPI; 37 | 38 | return *this; 39 | } 40 | 41 | inline auto multiFactorImage::operator=(multiFactorImage&& source) -> multiFactorImage& { 42 | if(this == &source) return *this; 43 | 44 | (*(image*)this) = source; 45 | _highDPI = source._highDPI; 46 | 47 | return *this; 48 | } 49 | 50 | inline auto multiFactorImage::operator==(const multiFactorImage& source) const -> bool { 51 | if((const image&)*this != (const image&)source) return false; 52 | return _highDPI != source._highDPI; 53 | } 54 | 55 | inline auto multiFactorImage::operator!=(const multiFactorImage& source) const -> bool { 56 | return !operator==(source); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/vfs/node.hpp: -------------------------------------------------------------------------------- 1 | namespace nall::vfs { 2 | 3 | enum class mode : u32 { read, write }; 4 | static constexpr auto read = mode::read; 5 | static constexpr auto write = mode::write; 6 | 7 | enum class index : u32 { absolute, relative }; 8 | static constexpr auto absolute = index::absolute; 9 | static constexpr auto relative = index::relative; 10 | 11 | struct node { 12 | virtual ~node() = default; 13 | 14 | auto isFile() const -> bool; 15 | auto isDirectory() const -> bool; 16 | 17 | auto name() const -> string { return _name; } 18 | auto setName(const string& name) -> void { _name = name; } 19 | 20 | template 21 | auto attribute(const string& name) const -> T { 22 | if(auto attribute = _attributes.find(name)) { 23 | if(attribute->value.is()) return attribute->value.get(); 24 | } 25 | return {}; 26 | } 27 | 28 | template 29 | auto hasAttribute(const string& name) const -> bool { 30 | if(auto attribute = _attributes.find(name)) { 31 | if(attribute->value.is()) return true; 32 | } 33 | return false; 34 | } 35 | 36 | template 37 | auto setAttribute(const string& name, const U& value = {}) -> void { 38 | if constexpr(is_same_v && !is_same_v) return setAttribute(name, string{value}); 39 | if(auto attribute = _attributes.find(name)) { 40 | if((const T&)value) attribute->value = (const T&)value; 41 | else _attributes.remove(*attribute); 42 | } else { 43 | if((const T&)value) _attributes.insert({name, (const T&)value}); 44 | } 45 | } 46 | 47 | protected: 48 | string _name; 49 | set _attributes; 50 | }; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/vfs/disk.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall::vfs { 6 | 7 | struct disk : file { 8 | static auto open(string location_, mode mode_) -> shared_pointer { 9 | auto instance = shared_pointer{new disk}; 10 | if(!instance->_open(location_, mode_)) return {}; 11 | return instance; 12 | } 13 | 14 | auto writable() const -> bool override { return _writable; } 15 | auto data() const -> const u8* override { return _data; } 16 | auto data() -> u8* override { return _data; } 17 | auto size() const -> u64 override { return _size; } 18 | auto offset() const -> u64 override { return _offset; } 19 | 20 | auto resize(u64 size) -> bool override { 21 | return false; //todo 22 | } 23 | 24 | auto seek(s64 offset, index mode = index::absolute) -> void override { 25 | if(mode == index::absolute) _offset = (u64)offset; 26 | if(mode == index::relative) _offset += (s64)offset; 27 | } 28 | 29 | auto read() -> u8 override { 30 | if(_offset >= _size) return 0x00; 31 | return _data[_offset++]; 32 | } 33 | 34 | auto write(u8 data) -> void override { 35 | if(_offset >= _size) return; 36 | _data[_offset++] = data; 37 | } 38 | 39 | private: 40 | disk() = default; 41 | disk(const disk&) = delete; 42 | auto operator=(const disk&) -> disk& = delete; 43 | 44 | auto _open(string location_, mode mode_) -> bool { 45 | if(!_fp.open(location_, (u32)mode_)) return false; 46 | _data = _fp.data(); 47 | _size = _fp.size(); 48 | _writable = mode_ == mode::write; 49 | return true; 50 | } 51 | 52 | file_map _fp; 53 | u8* _data = nullptr; 54 | u64 _size = 0; 55 | u64 _offset = 0; 56 | bool _writable = false; 57 | }; 58 | 59 | } 60 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/dsp/gaussian.cpp: -------------------------------------------------------------------------------- 1 | auto DSP::gaussianConstructTable() -> void { 2 | f64 table[512]; 3 | for(u32 n : range(512)) { 4 | f64 k = 0.5 + n; 5 | f64 s = (sin(Math::Pi * k * 1.280 / 1024)); 6 | f64 t = (cos(Math::Pi * k * 2.000 / 1023) - 1) * 0.50; 7 | f64 u = (cos(Math::Pi * k * 4.000 / 1023) - 1) * 0.08; 8 | f64 r = s * (t + u + 1.0) / k; 9 | table[511 - n] = r; 10 | } 11 | for(u32 phase : range(128)) { 12 | f64 sum = 0.0; 13 | sum += table[phase + 0]; 14 | sum += table[phase + 256]; 15 | sum += table[511 - phase]; 16 | sum += table[255 - phase]; 17 | f64 scale = 2048.0 / sum; 18 | gaussianTable[phase + 0] = i16(table[phase + 0] * scale + 0.5); 19 | gaussianTable[phase + 256] = i16(table[phase + 256] * scale + 0.5); 20 | gaussianTable[511 - phase] = i16(table[511 - phase] * scale + 0.5); 21 | gaussianTable[255 - phase] = i16(table[255 - phase] * scale + 0.5); 22 | } 23 | } 24 | 25 | auto DSP::gaussianInterpolate(const Voice& v) -> s32 { 26 | //make pointers into gaussian table based on fractional position between samples 27 | n8 offset = v.gaussianOffset >> 4; 28 | const i16* forward = gaussianTable + 255 - offset; 29 | const i16* reverse = gaussianTable + offset; //mirror left half of gaussian table 30 | 31 | offset = (v.bufferOffset + (v.gaussianOffset >> 12)) % 12; 32 | s32 output; 33 | output = forward[ 0] * v.buffer[offset] >> 11; if(++offset >= 12) offset = 0; 34 | output += forward[256] * v.buffer[offset] >> 11; if(++offset >= 12) offset = 0; 35 | output += reverse[256] * v.buffer[offset] >> 11; if(++offset >= 12) offset = 0; 36 | output = i16(output); 37 | output += reverse[ 0] * v.buffer[offset] >> 11; 38 | return sclamp<16>(output) & ~1; 39 | } 40 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/shvc-sound-emu.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace nall; 14 | using namespace nall::primitives; 15 | 16 | #include "types.hpp" 17 | 18 | #include "sample-buffer.hpp" 19 | 20 | #include "spc700/spc700.hpp" 21 | #include "dsp/dsp.hpp" 22 | #include "smp/smp.hpp" 23 | 24 | namespace shvc_sound_emu { 25 | 26 | struct ResetRegisters; 27 | 28 | struct ShvcSoundEmu { 29 | constexpr static uint32_t AUDIO_BUFFER_SAMPLES = 256; 30 | constexpr static uint32_t AUDIO_BUFFER_SIZE = AUDIO_BUFFER_SAMPLES * 2; 31 | 32 | ShvcSoundEmu(const std::array& iplrom); 33 | ~ShvcSoundEmu(); 34 | 35 | auto reset(ResetRegisters r) -> void; 36 | 37 | auto iplrom() const -> const std::array&; 38 | auto iplrom_mut () -> std::array&; 39 | 40 | auto apuram() const -> const std::array&; 41 | auto apuram_mut () -> std::array&; 42 | 43 | auto dsp_registers() const -> const std::array&; 44 | 45 | auto write_dsp_register(uint8_t addr, uint8_t value) -> void; 46 | auto write_smp_register(uint8_t addr, uint8_t value) -> void; 47 | 48 | auto read_io_ports() const -> std::array; 49 | auto write_io_ports(std::array ports) -> void; 50 | 51 | auto program_counter() const -> uint16_t; 52 | 53 | auto emulate() -> const std::array&; 54 | 55 | private: 56 | SMP smp; 57 | }; 58 | 59 | auto new_emulator(const std::array& iplrom) -> std::unique_ptr; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/path.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall::Path { 6 | 7 | inline auto active() -> string { 8 | char path[PATH_MAX] = ""; 9 | (void)getcwd(path, PATH_MAX); 10 | string result = path; 11 | if(!result) result = "."; 12 | result.transform("\\", "/"); 13 | if(!result.endsWith("/")) result.append("/"); 14 | return result; 15 | } 16 | 17 | inline auto real(string_view name) -> string { 18 | string result; 19 | char path[PATH_MAX] = ""; 20 | if(::realpath(name, path)) result = Location::path(string{path}.transform("\\", "/")); 21 | if(!result) return active(); 22 | result.transform("\\", "/"); 23 | if(!result.endsWith("/")) result.append("/"); 24 | return result; 25 | } 26 | 27 | auto program() -> string; 28 | 29 | // / 30 | // c:/ 31 | auto root() -> string; 32 | 33 | // /home/username/ 34 | // c:/users/username/ 35 | auto user() -> string; 36 | 37 | // /home/username/Desktop/ 38 | // c:/users/username/Desktop/ 39 | auto desktop(string_view name = {}) -> string; 40 | 41 | //todo: MacOS uses the same location for userData() and userSettings() 42 | //... is there a better option here? 43 | 44 | // /home/username/.config/ 45 | // ~/Library/Application Support/ 46 | // c:/users/username/appdata/roaming/ 47 | auto userSettings() -> string; 48 | 49 | // /home/username/.local/share/ 50 | // ~/Library/Application Support/ 51 | // c:/users/username/appdata/local/ 52 | auto userData() -> string; 53 | 54 | // /usr/share 55 | // /Library/Application Support/ 56 | // c:/ProgramData/ 57 | auto sharedData() -> string; 58 | 59 | // /tmp 60 | // c:/users/username/AppData/Local/Temp/ 61 | auto temporary() -> string; 62 | 63 | } 64 | 65 | #if defined(NALL_HEADER_ONLY) 66 | #include 67 | #endif 68 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/encode/bmp.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall::Encode { 4 | 5 | struct BMP { 6 | static auto create(const string& filename, const void* data, u32 pitch, u32 width, u32 height, bool alpha) -> bool { 7 | auto fp = file::open(filename, file::mode::write); 8 | if(!fp) return false; 9 | 10 | u32 bitsPerPixel = alpha ? 32 : 24; 11 | u32 bytesPerPixel = bitsPerPixel / 8; 12 | u32 alignedWidth = width * bytesPerPixel; 13 | u32 paddingLength = 0; 14 | u32 imageSize = alignedWidth * height; 15 | u32 fileSize = 0x36 + imageSize; 16 | while(alignedWidth % 4) alignedWidth++, paddingLength++; 17 | 18 | fp.writel(0x4d42, 2); //signature 19 | fp.writel(fileSize, 4); //file size 20 | fp.writel(0, 2); //reserved 21 | fp.writel(0, 2); //reserved 22 | fp.writel(0x36, 4); //offset 23 | 24 | fp.writel(40, 4); //DIB size 25 | fp.writel(width, 4); //width 26 | fp.writel(-height, 4); //height 27 | fp.writel(1, 2); //color planes 28 | fp.writel(bitsPerPixel, 2); //bits per pixel 29 | fp.writel(0, 4); //compression method (BI_RGB) 30 | fp.writel(imageSize, 4); //image data size 31 | fp.writel(3780, 4); //horizontal resolution 32 | fp.writel(3780, 4); //vertical resolution 33 | fp.writel(0, 4); //palette size 34 | fp.writel(0, 4); //important color count 35 | 36 | pitch >>= 2; 37 | for(auto y : range(height)) { 38 | auto p = (const u32*)data + y * pitch; 39 | for(auto x : range(width)) fp.writel(*p++, bytesPerPixel); 40 | if(paddingLength) fp.writel(0, paddingLength); 41 | } 42 | 43 | return true; 44 | } 45 | }; 46 | 47 | } 48 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/windows/utf8.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace nall { 6 | //UTF-8 to UTF-16 7 | struct utf16_t { 8 | utf16_t(const char* s = "") { operator=(s); } 9 | ~utf16_t() { reset(); } 10 | 11 | utf16_t(const utf16_t&) = delete; 12 | auto operator=(const utf16_t&) -> utf16_t& = delete; 13 | 14 | auto operator=(const char* s) -> utf16_t&; 15 | 16 | operator wchar_t*() { return buffer; } 17 | operator const wchar_t*() const { return buffer; } 18 | 19 | auto reset() -> void { 20 | delete[] buffer; 21 | length = 0; 22 | } 23 | 24 | auto data() -> wchar_t* { return buffer; } 25 | auto data() const -> const wchar_t* { return buffer; } 26 | 27 | auto size() const -> u32 { return length; } 28 | 29 | private: 30 | wchar_t* buffer = nullptr; 31 | u32 length = 0; 32 | }; 33 | 34 | //UTF-16 to UTF-8 35 | struct utf8_t { 36 | utf8_t(const wchar_t* s = L"") { operator=(s); } 37 | ~utf8_t() { reset(); } 38 | 39 | utf8_t(const utf8_t&) = delete; 40 | auto operator=(const utf8_t&) -> utf8_t& = delete; 41 | 42 | auto operator=(const wchar_t* s) -> utf8_t&; 43 | 44 | auto reset() -> void { 45 | delete[] buffer; 46 | length = 0; 47 | } 48 | 49 | operator char*() { return buffer; } 50 | operator const char*() const { return buffer; } 51 | 52 | auto data() -> char* { return buffer; } 53 | auto data() const -> const char* { return buffer; } 54 | 55 | auto size() const -> u32 { return length; } 56 | 57 | private: 58 | char* buffer = nullptr; 59 | u32 length = 0; 60 | }; 61 | 62 | auto utf8_arguments(int& argc, char**& argv) -> void; 63 | 64 | } 65 | 66 | #if defined(NALL_HEADER_ONLY) 67 | #include 68 | #endif 69 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/interpolation.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | struct Interpolation { 6 | static inline auto Nearest(f64 mu, f64 a, f64 b, f64 c, f64 d) -> f64 { 7 | return (mu <= 0.5 ? b : c); 8 | } 9 | 10 | static inline auto Sublinear(f64 mu, f64 a, f64 b, f64 c, f64 d) -> f64 { 11 | mu = ((mu - 0.5) * 2.0) + 0.5; 12 | if(mu < 0) mu = 0; 13 | if(mu > 1) mu = 1; 14 | return b * (1.0 - mu) + c * mu; 15 | } 16 | 17 | static inline auto Linear(f64 mu, f64 a, f64 b, f64 c, f64 d) -> f64 { 18 | return b * (1.0 - mu) + c * mu; 19 | } 20 | 21 | static inline auto Cosine(f64 mu, f64 a, f64 b, f64 c, f64 d) -> f64 { 22 | mu = (1.0 - cos(mu * Math::Pi)) / 2.0; 23 | return b * (1.0 - mu) + c * mu; 24 | } 25 | 26 | static inline auto Cubic(f64 mu, f64 a, f64 b, f64 c, f64 d) -> f64 { 27 | f64 A = d - c - a + b; 28 | f64 B = a - b - A; 29 | f64 C = c - a; 30 | f64 D = b; 31 | return A * (mu * mu * mu) + B * (mu * mu) + C * mu + D; 32 | } 33 | 34 | static inline auto Hermite(f64 mu1, f64 a, f64 b, f64 c, f64 d) -> f64 { 35 | const f64 tension = 0.0; //-1 = low, 0 = normal, +1 = high 36 | const f64 bias = 0.0; //-1 = left, 0 = even, +1 = right 37 | f64 mu2, mu3, m0, m1, a0, a1, a2, a3; 38 | 39 | mu2 = mu1 * mu1; 40 | mu3 = mu2 * mu1; 41 | 42 | m0 = (b - a) * (1.0 + bias) * (1.0 - tension) / 2.0; 43 | m0 += (c - b) * (1.0 - bias) * (1.0 - tension) / 2.0; 44 | m1 = (c - b) * (1.0 + bias) * (1.0 - tension) / 2.0; 45 | m1 += (d - c) * (1.0 - bias) * (1.0 - tension) / 2.0; 46 | 47 | a0 = +2 * mu3 - 3 * mu2 + 1; 48 | a1 = mu3 - 2 * mu2 + mu1; 49 | a2 = mu3 - mu2; 50 | a3 = -2 * mu3 + 3 * mu2; 51 | 52 | return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c); 53 | } 54 | }; 55 | 56 | } 57 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/vector/utility.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template auto vector::fill(const T& value) -> void { 6 | for(u64 n : range(size())) _pool[n] = value; 7 | } 8 | 9 | template auto vector::sort(const function& comparator) -> void { 10 | nall::sort(_pool, _size, comparator); 11 | } 12 | 13 | template auto vector::reverse() -> void { 14 | vector reversed; 15 | for(u64 n : range(size())) reversed.prepend(_pool[n]); 16 | operator=(std::move(reversed)); 17 | } 18 | 19 | template auto vector::find(const function& comparator) -> maybe { 20 | for(u64 n : range(size())) if(comparator(_pool[n])) return n; 21 | return nothing; 22 | } 23 | 24 | template auto vector::find(const T& value) const -> maybe { 25 | for(u64 n : range(size())) if(_pool[n] == value) return n; 26 | return nothing; 27 | } 28 | 29 | template auto vector::findSorted(const T& value) const -> maybe { 30 | s64 l = 0, r = size() - 1; 31 | while(l <= r) { 32 | s64 m = l + (r - l >> 1); 33 | if(value == _pool[m]) return m; 34 | value < _pool[m] ? r = m - 1 : l = m + 1; 35 | } 36 | return nothing; 37 | } 38 | 39 | template auto vector::contains(const T& value) const -> bool { 40 | for(const auto &v : *this) { 41 | if(v == value) return true; 42 | } 43 | return false; 44 | } 45 | 46 | template auto vector::foreach(const function& callback) -> void { 47 | for(u64 n : range(size())) callback(_pool[n]); 48 | } 49 | 50 | template auto vector::foreach(const function& callback) -> void { 51 | for(u64 n : range(size())) callback(n, _pool[n]); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/recompiler/generic/constants.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //{ 4 | enum set_flags { 5 | set_z = SLJIT_SET_Z, 6 | set_ult = SLJIT_SET_LESS, 7 | set_uge = SLJIT_SET_GREATER_EQUAL, 8 | set_ugt = SLJIT_SET_GREATER, 9 | set_ule = SLJIT_SET_LESS_EQUAL, 10 | set_slt = SLJIT_SET_SIG_LESS, 11 | set_sge = SLJIT_SET_SIG_GREATER_EQUAL, 12 | set_sgt = SLJIT_SET_SIG_GREATER, 13 | set_sle = SLJIT_SET_SIG_LESS_EQUAL, 14 | set_o = SLJIT_SET_OVERFLOW, 15 | set_c = SLJIT_SET_CARRY, 16 | }; 17 | 18 | enum flags { 19 | flag_eq = SLJIT_EQUAL, 20 | flag_z = flag_eq, 21 | flag_ne = SLJIT_NOT_EQUAL, 22 | flag_nz = flag_ne, 23 | flag_ult = SLJIT_LESS, 24 | flag_uge = SLJIT_GREATER_EQUAL, 25 | flag_ugt = SLJIT_GREATER, 26 | flag_ule = SLJIT_LESS_EQUAL, 27 | flag_slt = SLJIT_SIG_LESS, 28 | flag_sge = SLJIT_SIG_GREATER_EQUAL, 29 | flag_sgt = SLJIT_SIG_GREATER, 30 | flag_sle = SLJIT_SIG_LESS_EQUAL, 31 | flag_o = SLJIT_OVERFLOW, 32 | flag_no = SLJIT_NOT_OVERFLOW, 33 | flag_c = SLJIT_CARRY, 34 | flag_nc = SLJIT_NOT_CARRY, 35 | }; 36 | 37 | struct op_base { 38 | op_base(sljit_s32 f, sljit_sw s) : fst(f), snd(s) {} 39 | sljit_s32 fst; 40 | sljit_sw snd; 41 | }; 42 | 43 | struct imm : public op_base { 44 | explicit imm(sljit_sw immediate) : op_base(SLJIT_IMM, immediate) {} 45 | }; 46 | 47 | struct reg : public op_base { 48 | explicit reg(sljit_s32 index) : op_base(SLJIT_R(index), 0) {} 49 | }; 50 | 51 | struct sreg : public op_base { 52 | explicit sreg(sljit_s32 index) : op_base(SLJIT_S(index), 0) {} 53 | }; 54 | 55 | struct mem : public op_base { 56 | mem(sreg base, sljit_sw offset) : op_base(SLJIT_MEM1(base.fst), offset) {} 57 | }; 58 | 59 | struct unused { 60 | unused() {} 61 | }; 62 | //}; 63 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/http/server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct sockaddr_in; 7 | struct sockaddr_in6; 8 | 9 | namespace nall::HTTP { 10 | 11 | struct Server : Role, service { 12 | auto open(u16 port = 8080, const string& serviceName = "", const string& command = "") -> bool; 13 | auto main(const function& function = {}) -> void; 14 | auto scan() -> string; 15 | auto close() -> void; 16 | ~Server() { close(); } 17 | 18 | private: 19 | function callback; 20 | std::atomic connections{0}; 21 | 22 | s32 fd4 = -1; 23 | s32 fd6 = -1; 24 | u64 addrin4_storage[16] = {0}; //sizeof(sockaddr_storage) = 128 25 | u64 addrin6_storage[16] = {0}; 26 | sockaddr_in& addrin4 = (sockaddr_in&)addrin4_storage; 27 | sockaddr_in6& addrin6 = (sockaddr_in6&)addrin6_storage; 28 | 29 | auto ipv4() const -> bool { return fd4 >= 0; } 30 | auto ipv6() const -> bool { return fd6 >= 0; } 31 | 32 | auto ipv4_close() -> void { if(fd4 >= 0) ::close(fd4); fd4 = -1; } 33 | auto ipv6_close() -> void { if(fd6 >= 0) ::close(fd6); fd6 = -1; } 34 | 35 | auto ipv4_scan() -> bool; 36 | auto ipv6_scan() -> bool; 37 | }; 38 | 39 | inline auto Server::main(const function& function) -> void { 40 | callback = function; 41 | } 42 | 43 | inline auto Server::scan() -> string { 44 | if(auto command = service::receive()) return command; 45 | if(connections >= settings.connectionLimit) return "busy"; 46 | if(ipv4() && ipv4_scan()) return "ok"; 47 | if(ipv6() && ipv6_scan()) return "ok"; 48 | return "idle"; 49 | } 50 | 51 | inline auto Server::close() -> void { 52 | ipv4_close(); 53 | ipv6_close(); 54 | } 55 | 56 | } 57 | 58 | #if defined(NALL_HEADER_ONLY) 59 | #include 60 | #endif 61 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "crates/brr", 6 | "crates/compiler", 7 | "crates/shvc-sound-emu", 8 | "crates/tad-emu", 9 | 10 | "crates/tad-compiler", 11 | "crates/tad-gui", 12 | "crates/wav2brr", 13 | ] 14 | 15 | 16 | [profile.release] 17 | strip = true 18 | opt-level = "z" 19 | lto = true 20 | 21 | [profile.release.package.brr] 22 | opt-level = 3 23 | 24 | [profile.release.package.compiler] 25 | opt-level = 3 26 | 27 | [profile.release.package.shvc-sound-emu] 28 | opt-level = 3 29 | 30 | 31 | [workspace.package] 32 | rust-version = "1.75" 33 | 34 | version = "0.2.0-beta.2" 35 | 36 | publish = false 37 | 38 | 39 | [workspace.dependencies] 40 | # Local library crates 41 | brr = { path = "./crates/brr", version = "=0.0.0" } 42 | compiler = { path = "./crates/compiler", version = "=0.0.0", features = [] } 43 | shvc-sound-emu = { path = "./crates/shvc-sound-emu", version = "=0.0.0" } 44 | tad-emu = { path = "./crates/tad-emu", version = "=0.0.0" } 45 | 46 | # Local binary crates 47 | tad-compiler = { path = "./crates/tad-compiler", version = "=0.0.0" } 48 | wav2brr = { path = "./crates/wav2brr", version = "=0.0.0" } 49 | 50 | # External crates 51 | clap = { version="4.5.16", features=[ "derive" ] } 52 | serde = { version="1.0.164", features=[ "derive" ] } 53 | serde_json = { version="1.0.97" } 54 | relative-path = { version="2.0.1", features=[ "serde" ] } 55 | 56 | sdl2 = { version="0.38.0", features=[] } 57 | spectrum-analyzer = { version="1.6.0" } 58 | 59 | fltk = { version="1.5.0", features = ["use-ninja"] } 60 | 61 | cxx = { version="1.0.107" } 62 | 63 | # External build dependencies 64 | # 65 | # Chose markdown as it has a security section in its readme file 66 | markdown = { version="1.0.0" } 67 | 68 | regex = { version="1.10.2" } 69 | 70 | cxx-build = { version="1.0.107" } 71 | 72 | 73 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/dsp/brr.cpp: -------------------------------------------------------------------------------- 1 | auto DSP::brrDecode(Voice& v) -> void { 2 | //brr._byte = apuram[v.brrAddress + v.brrOffset] cached from previous clock cycle 3 | s32 nybbles = brr._byte << 8 | apuram[n16(v.brrAddress + v.brrOffset + 1)]; 4 | 5 | const s32 filter = brr._header.bit(2,3); 6 | const s32 scale = brr._header.bit(4,7); 7 | 8 | //decode four samples 9 | for(u32 n : range(4)) { 10 | //bits 12-15 = current nybble; sign extend, then shift right to 4-bit precision 11 | //result: s = 4-bit sign-extended sample value 12 | s32 s = (i16)nybbles >> 12; 13 | nybbles <<= 4; //slide nybble so that on next loop iteration, bits 12-15 = current nybble 14 | 15 | if(scale <= 12) { 16 | s <<= scale; 17 | s >>= 1; 18 | } else { 19 | s &= ~0x7ff; 20 | } 21 | 22 | //apply IIR filter (2 is the most commonly used) 23 | s32 offset = v.bufferOffset; 24 | if(--offset < 0) offset = 11; const s32 p1 = v.buffer[offset]; 25 | if(--offset < 0) offset = 11; const s32 p2 = v.buffer[offset] >> 1; 26 | 27 | switch(filter) { 28 | case 0: 29 | break; 30 | 31 | case 1: 32 | //s += p1 * 0.46875 33 | s += p1 >> 1; 34 | s += (-p1) >> 5; 35 | break; 36 | 37 | case 2: 38 | //s += p1 * 0.953125 - p2 * 0.46875 39 | s += p1; 40 | s -= p2; 41 | s += p2 >> 4; 42 | s += (p1 * -3) >> 6; 43 | break; 44 | 45 | case 3: 46 | //s += p1 * 0.8984375 - p2 * 0.40625 47 | s += p1; 48 | s -= p2; 49 | s += (p1 * -13) >> 7; 50 | s += (p2 * 3) >> 4; 51 | break; 52 | } 53 | 54 | //adjust and write sample (mirror the written sample for wrapping) 55 | s = sclamp<16>(s); 56 | s = (i16)(s << 1); 57 | v.buffer[v.bufferOffset] = s; 58 | if(++v.bufferOffset >= 12) v.bufferOffset = 0; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/string/split.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template 6 | inline auto vector::_split(string_view source, string_view find, long limit) -> type& { 7 | reset(); 8 | if(limit <= 0 || find.size() == 0) return *this; 9 | 10 | const char* p = source.data(); 11 | s32 size = source.size(); 12 | s32 base = 0; 13 | s32 matches = 0; 14 | 15 | for(s32 n = 0, quoted = 0; n <= size - (s32)find.size();) { 16 | if constexpr(Quoted) { 17 | if(quoted && p[n] == '\\') { n += 2; continue; } 18 | if(p[n] == '\'' && quoted != 2) { quoted ^= 1; n++; continue; } 19 | if(p[n] == '\"' && quoted != 1) { quoted ^= 2; n++; continue; } 20 | if(quoted) { n++; continue; } 21 | } 22 | if(string::_compare(p + n, size - n, find.data(), find.size())) { n++; continue; } 23 | if(matches >= limit) break; 24 | 25 | string& s = operator()(matches); 26 | s.resize(n - base); 27 | memory::copy(s.get(), p + base, n - base); 28 | 29 | n += find.size(); 30 | base = n; 31 | matches++; 32 | } 33 | 34 | string& s = operator()(matches); 35 | s.resize(size - base); 36 | memory::copy(s.get(), p + base, size - base); 37 | 38 | return *this; 39 | } 40 | 41 | inline auto string::split(string_view on, long limit) const -> vector { return vector()._split<0, 0>(*this, on, limit); } 42 | inline auto string::isplit(string_view on, long limit) const -> vector { return vector()._split<1, 0>(*this, on, limit); } 43 | inline auto string::qsplit(string_view on, long limit) const -> vector { return vector()._split<0, 1>(*this, on, limit); } 44 | inline auto string::iqsplit(string_view on, long limit) const -> vector { return vector()._split<1, 1>(*this, on, limit); } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /audio-driver/pvsneslib-api/api-tests/audio-data.asm: -------------------------------------------------------------------------------- 1 | ;; Audio driver binaries and dummy audio data 2 | 3 | 4 | .include "hdr.asm" 5 | 6 | .if defined(LOROM) 7 | BANK_SIZE = $8000 8 | .elif defined(HIROM) 9 | BANK_SIZE = $10000 10 | .else 11 | .fail "Unknown memory map" 12 | .endif 13 | 14 | 15 | .export Tad_Loader_SIZE, Tad_AudioDriver_SIZE 16 | 17 | .section "Tad_AudioDriverBin" SUPERFREE 18 | Tad_Loader_Bin: .incbin "../../loader.bin" 19 | Tad_AudioDriver_Bin: .incbin "../../audio-driver.bin" 20 | 21 | Tad_Loader_SIZE = _sizeof_Tad_Loader_Bin 22 | Tad_AudioDriver_SIZE = _sizeof_Tad_AudioDriver_Bin 23 | .ends 24 | 25 | 26 | ; Sizes MUST match `tad-tests.c` 27 | COMMON_AUDIO_DATA_SIZE = 3000 28 | SONG_DATA_SIZE = 2000 29 | 30 | _COMMON_AUDIO_DATA_PART1_SIZE = 1500 31 | _COMMON_AUDIO_DATA_PART2_SIZE = COMMON_AUDIO_DATA_SIZE - _COMMON_AUDIO_DATA_PART1_SIZE 32 | 33 | _SONG_DATA_PART1_SIZE = 999 34 | _SONG_DATA_PART2_SIZE = SONG_DATA_SIZE - _SONG_DATA_PART1_SIZE 35 | 36 | .section "AudioData_CAD_1" FORCE PRIORITY 1000 BANK 1 ORG (BANK_SIZE - _COMMON_AUDIO_DATA_PART1_SIZE) 37 | DummyCommonAudioData_Part1: 38 | .dsb _COMMON_AUDIO_DATA_PART1_SIZE, $01 39 | DummyCommonAudioData_Part1End: 40 | .ends 41 | 42 | .section "AudioData_CAD_2" FORCE PRIORITY 1000 BANK 2 ORG 0 43 | DummyCommonAudioData_Part2: 44 | .dsb _COMMON_AUDIO_DATA_PART2_SIZE, $01 45 | DummyCommonAudioData_Part2End: 46 | .ends 47 | 48 | .section "AudioData_SD_1" FORCE PRIORITY 1000 BANK 2 ORG (BANK_SIZE - _SONG_DATA_PART1_SIZE) 49 | DummySongData_Part1: 50 | .dsb _SONG_DATA_PART1_SIZE, $02 51 | DummySongData_Part1End: 52 | .ends 53 | 54 | .section "AudioData_SD_2" FORCE PRIORITY 1000 BANK 3 ORG 0 55 | DummySongData_Part2: 56 | .dsb _SONG_DATA_PART2_SIZE, $02 57 | DummySongData_Part2End: 58 | .ends 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /crates/tad-gui/src/monitor_timer.rs: -------------------------------------------------------------------------------- 1 | //! Audio Driver Timer 2 | 3 | // SPDX-FileCopyrightText: © 2023 Marcus Rowe 4 | // 5 | // SPDX-License-Identifier: MIT 6 | 7 | use crate::GuiMessage; 8 | 9 | use std::cell::RefCell; 10 | use std::rc::Rc; 11 | 12 | extern crate fltk; 13 | use fltk::app; 14 | 15 | // The Audio Driver updates the AudioMonitorData 15.6 times per second (32040 / 2048) 16 | const DEFAULT_TIMEOUT: f64 = 1.0 / 30.0; 17 | 18 | pub struct MonitorTimer { 19 | state: Rc>, 20 | } 21 | 22 | pub struct TimerState { 23 | sender: app::Sender, 24 | timeout: f64, 25 | active: bool, 26 | } 27 | 28 | impl MonitorTimer { 29 | pub fn new(sender: app::Sender) -> Self { 30 | Self { 31 | state: Rc::new(RefCell::new(TimerState { 32 | sender, 33 | timeout: DEFAULT_TIMEOUT, 34 | active: false, 35 | })), 36 | } 37 | } 38 | 39 | pub fn start(&mut self) { 40 | let mut state = self.state.borrow_mut(); 41 | 42 | if !state.active { 43 | state.active = true; 44 | 45 | app::add_timeout3(state.timeout, { 46 | let cloned_state = self.state.clone(); 47 | move |handle| { 48 | if let Ok(s) = cloned_state.try_borrow() { 49 | match s.active { 50 | true => app::repeat_timeout3(s.timeout, handle), 51 | false => app::remove_timeout3(handle), 52 | } 53 | 54 | s.sender.send(GuiMessage::SongMonitorTimeout); 55 | } 56 | } 57 | }); 58 | } 59 | } 60 | 61 | pub fn stop(&mut self) { 62 | self.state.borrow_mut().active = false; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/arithmetic/unsigned.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace nall { 4 | 5 | template::value>> 6 | inline auto upper(T value) -> T { 7 | return value >> sizeof(T) * 4; 8 | } 9 | 10 | template::value>> 11 | inline auto lower(T value) -> T { 12 | static const T Mask = ~T(0) >> sizeof(T) * 4; 13 | return value & Mask; 14 | } 15 | 16 | template::value>, enable_if_t::value>> 17 | inline auto mul(T lhs, U rhs) -> uintmax { 18 | return lhs * rhs; 19 | } 20 | 21 | template::value>> 22 | inline auto square(T value) -> uintmax { 23 | return value * value; 24 | } 25 | 26 | template 27 | inline auto rol(T lhs, U rhs, enable_if_t::value>* = 0) -> T { 28 | return lhs << rhs | lhs >> sizeof(T) * 8 - rhs; 29 | } 30 | 31 | template 32 | inline auto ror(T lhs, U rhs, enable_if_t::value>* = 0) -> T { 33 | return lhs >> rhs | lhs << sizeof(T) * 8 - rhs; 34 | } 35 | 36 | #if defined(__SIZEOF_INT128__) 37 | inline auto operator"" _u128(const char* s) -> u128 { 38 | u128 p = 0; 39 | if(s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { 40 | s += 2; 41 | while(*s) { 42 | auto c = *s++; 43 | if(c == '\''); 44 | else if(c >= '0' && c <= '9') p = (p << 4) + (c - '0'); 45 | else if(c >= 'a' && c <= 'f') p = (p << 4) + (c - 'a' + 10); 46 | else if(c >= 'A' && c <= 'F') p = (p << 4) + (c - 'A' + 10); 47 | else break; 48 | } 49 | } else { 50 | while(*s) { 51 | auto c = *s++; 52 | if(c == '\''); 53 | else if(c >= '0' && c <= '9') p = (p << 3) + (p << 1) + (c - '0'); 54 | else break; 55 | } 56 | } 57 | return p; 58 | } 59 | #endif 60 | 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build 3 | 4 | on: 5 | push: 6 | branches: 7 | - "**" 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | build: 18 | runs-on: ${{ matrix.os }} 19 | timeout-minutes: 120 20 | strategy: 21 | matrix: 22 | os: 23 | - ubuntu-latest 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | with: 28 | submodules: 'true' 29 | - name: Build 30 | shell: bash 31 | run: | 32 | function log 33 | { 34 | declare -rAi TAG=( 35 | [error]=31 36 | [info]=32 37 | [audit]=33 38 | ) 39 | printf '%(%y-%m-%d_%T)T\x1b[%dm\t%s:\t%b\x1b[0m\n' -1 "${TAG[${1,,:?}]}" "${1^^}" "${2:?}" 1>&2 40 | if [[ ${1} == 'error' ]]; then 41 | return 1 42 | fi 43 | } 44 | export -f log 45 | if [[ ${RUNNER_OS} == "Linux" ]]; then 46 | log 'info' 'Download dep' 47 | sudo apt-get update 48 | sudo apt-get install -y ninja-build lib{x11,xext,xft,xinerama,xcursor,xrender,xfixes,pango1.0,gl1-mesa,glu1-mesa,sdl2}-dev 49 | log 'info' 'Build Wiz' 50 | make -j -C wiz 51 | log 'info' 'Cargo Fmt' 52 | cargo fmt --check --all 53 | log 'info' 'Cargo Clippy' 54 | cargo clippy --quiet 55 | log 'info' 'Cargo Test' 56 | cargo test --quiet 57 | cargo run --example test_bc_interpreter examples/example-project.terrificaudio 58 | cargo run --example test_bc_interpreter manual-tests/manual-tests.terrificaudio 59 | log 'info' 'Cargo Build' 60 | cargo build --quiet 61 | fi > /dev/null 62 | -------------------------------------------------------------------------------- /crates/shvc-sound-emu/src/nall/cd/edc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //error detection code 4 | 5 | namespace nall::CD::EDC { 6 | 7 | //polynomial(x) = (x^16 + x^15 + x^2 + 1) * (x^16 + x^2 + x + 1) 8 | inline auto polynomial(u8 x) -> u32 { 9 | static u32 lookup[256]{}; 10 | static bool once = false; 11 | if(!once) { once = true; 12 | for(u32 n : range(256)) { 13 | u32 edc = n; 14 | for(u32 b : range(8)) edc = edc >> 1 ^ (edc & 1 ? 0xd8018001 : 0); 15 | lookup[n] = edc; 16 | } 17 | } 18 | return lookup[x]; 19 | } 20 | 21 | // 22 | 23 | inline auto create(array_view input) -> u32 { 24 | u32 sum = 0; 25 | for(auto& byte : input) sum = sum >> 8 ^ polynomial(sum ^ byte); 26 | return sum; 27 | } 28 | 29 | inline auto create(array_view input, array_span output) -> bool { 30 | if(output.size() != 4) return false; 31 | auto sum = create(input); 32 | output[0] = sum >> 0; 33 | output[1] = sum >> 8; 34 | output[2] = sum >> 16; 35 | output[3] = sum >> 24; 36 | return true; 37 | } 38 | 39 | inline auto createMode1(array_span sector) -> bool { 40 | if(sector.size() != 2352) return false; 41 | return create({sector.data(), 2064}, {sector.data() + 2064, 4}); 42 | } 43 | 44 | // 45 | 46 | inline auto verify(array_view input, u32 edc) -> bool { 47 | return edc == create(input); 48 | } 49 | 50 | inline auto verify(array_view input, array_view compare) -> bool { 51 | if(compare.size() != 4) return false; 52 | auto sum = create(input); 53 | if(compare[0] != u8(sum >> 0)) return false; 54 | if(compare[1] != u8(sum >> 8)) return false; 55 | if(compare[2] != u8(sum >> 16)) return false; 56 | if(compare[3] != u8(sum >> 24)) return false; 57 | return true; 58 | } 59 | 60 | inline auto verifyMode1(array_view sector) -> bool { 61 | if(sector.size() != 2352) return false; 62 | return verify({sector.data(), 2064}, {sector.data() + 2064, 4}); 63 | } 64 | 65 | } 66 | --------------------------------------------------------------------------------