A kernel is three F# functions
init, trig and render over Frac, Acc and Long: fixed-point types with the DSP's own rounding and saturation. Test it, step through it in a debugger, write a WAV in one call.
v1 F# → DSP56300 · for MDS authors
Prototype a Machinedrum machine as ordinary F#, audition and render it, then compile the same code to DSP56300 assembly and an MDS package, checked word for word against the prototype.
Hear the idea first. Count the cycles later. Writing a machine for the Machinedrum's DSP usually starts and ends in hand-written assembly: write it, assemble it with asm56300, pack it, send it over SysEx, and listen. The MDS standard asks you to measure each machine's worst-case cycles before you declare them. fsdsp56k shortens that loop.
init, trig and render over Frac, Acc and Long: fixed-point types with the DSP's own rounding and saturation. Test it, step through it in a debugger, write a WAV in one call.
fsdsp56k allows you to audition and render your kernels: play them live, retrigger them, sweep their parameters, and see every call's cycles against the per-track limit.
The same functions compile to DSP56300 assembly, packed as an MDS package for X.20, the MDX custom firmware. Output and state are graded word for word against the prototype.
Passes trim reloads and spills, keep hot values in registers and pack parallel moves. See line by line which F# became which instruction, and where the cycles go.
Natively on .NET, and through Fable as JavaScript and Rust, native or WebAssembly, down to a browser AudioWorklet. We have used both, checked word for word.
A DSL writes DSP56300 programs as F# values: a routine is a function you can parameterise, unroll with a loop and share between machines. It prints as Motorola syntax.
Plain F#, the DSP's arithmetic. The sine generator from Justin Mammarella's MDS examples (jmamma/MDS, sine.asm56k), ported. It runs as .NET code, and compiled it gives the same words as the original package.
module samples.kernels.Sine
open dsp56k.kernel
open dsp56k.kernel.Units
type Params =
{ pitch : Frac<Hz> // the host's pitch increment
amp : Frac<ratio> } // unsigned Q23
type State =
{ mutable phase : Long // table index . fraction
mutable ampPrev : Frac } // the amplitude the last block ended on
let init (_: Params) (s: State) =
s.phase <- Long.zero
s.ampPrev <- Frac.one
let trig (_: Params) (s: State) =
s.phase <- Long.zero
let render (p: Params) (s: State) (out: Output) =
let step = Acc.ofFrac (plain p.pitch) >>> 6
let ramp = (Acc.ofFrac (plain p.amp) - Acc.ofFrac s.ampPrev) >>> 5
let mutable gain = Acc.ofFrac s.ampPrev
let mutable phase = Acc.ofLong s.phase
for _ in 0 .. 31 do
gain <- gain + ramp
let v = Table.read Rom.sine (Acc.hi phase)
out.Write(Acc.sat ((v * Acc.hi gain) >>> 2))
phase <- Acc.andHi 0x7fff (phase + step)
s.phase <- Long.ofAcc phase
s.ampPrev <- plain p.amp
Three functions become the three entry points. render writes one 32-frame block.
Records of fixed-point fields, laid out in the track's workset. [<XMemory>] puts a field in X.
A voice record read when compiling picks the code, so one engine yields several kernels, each holding only its own code.
Output and state compared word for word with the .NET run and with the shipped machine, with every call's cycles against the limit.
Hear it here, in four runtimes. Pick a kernel and a runtime, then audition it live or render its phrase. The same F# runs as JavaScript and as Rust compiled to wasm; the DSP56300 runtime is The Usual Suspects' dsp56300 emulator, built for the browser, running the compiled kernel's program. Each runtime is checked block by block against the .NET reference.
dotnet fsi build.fsx serve, then open localhost:8056.
Pad, space or T, or a MIDI note triggers the kernel.
Drop a .mds or a .syx anywhere on the playground. It is read here, in your browser, and never uploaded.
No captured sessions in this copy: dotnet fsi build.fsx terminal-capture.
One tool, from source to SysEx. DSP56300 commands work on any code for the chip. The Machinedrum's live under machinedrum, or md. run, stress and audition take an MDS package, a SysEx dump, or an F# kernel as a project, a script or one file.
--profile hot spots.--trig-every.From clone to a WAV in five lines. You need the .NET SDK and a Rust toolchain (for the native DSP56300 oracle and emulator).
$ git clone https://github.com/smoothdeveloper/fsdsp56k
$ cd fsdsp56k
$ dotnet tool restore && dotnet paket install
$ (cd native && cargo build --release)
$ dotnet run --project src/dsp56k.tool -- run tests/dsp56k.tests/Kernels/Sine.fs --wav sine.wav
Standing on other people's work.
asm56300, whose dialect our assembler follows.run and every grade.