Repository navigation
CI release build, NEON performance, Force fixes, hardware-accurate sound, 30 presets - #1
Merged
Merged
Conversation
…g-checked The plugin catalog lists only plugins that need glibc 2.32 or less (they then load on MPC OS 2.x too); Ubuntu 24.04's cross toolchain made the .so need 2.38 (the C23 __isoc23_sscanf). The workflow, as PolyForce's, builds the device .so in arm32v7/gcc:11-bullseye (GCC 11, glibc 2.31) under QEMU, profile-guided, runs the suite against the objects it is linked from, packages, and checks the zip with the catalog's own catalog_check.py; the sanitizer suite runs on x86. A vX.Y.Z tag publishes a GitHub release (a prerelease for 0.x: the catalog's beta channel). Makefile: ARM_PREFIX (empty for a native ARM build) and ARM_RUN (qemu-arm; empty natively) replace ARM_TOOL and the hard-coded qemu-arm; local defaults are unchanged. The PGO trainer is linked dynamically (static against glibc 2.31, std::thread fails before glibc 2.34). Co-Authored-By: Claude Opus 5.5 <[email protected]>
… and idle Measured on the Force (Cortex-A17 at 1.8 GHz), profile-guided: Init on one note 3.64% -> 2.67% of the block (p99 4.09% -> 3.00%), the heavy Duo patch 3.70% -> 2.87%, idle 0.38% -> 0.17%. The voice was queueing divisions behind VFP's one unpipelined divider (18 cycles each, one every 14): 10 per ladder tick, 14 in the audio loop's body, one in every oscillator tick. - dsp/simd.h: four floats at a time, NEON on the device and GCC's generic vectors elsewhere (the x86 tests run the same arithmetic): reciprocal estimates refined by Newton-Raphson steps, and four-lane exp2Fast / tanFast / tanhXdX with the scalar versions' polynomials. - The ladder: the four stages' nonlinear gains and 1 / (1 + f t) in vector lanes; the loop solved as y_k = al_k + be_k y3 by a short scalar chain and closed with one division; the stages' inputs, outputs and integrators in one vector step each. 162 -> 103 ns a tick. (An all-NEON prefix scan was slower: on this core a q-register op issues every 2 cycles, and the serial part is quicker in VFP.) - The oscillators' advance() takes the stretch's length instead of dividing for it, its rare divisions are reciprocals, and it is inlined; the shape's tri/pulse checks are flags. - renderRun in two passes: the envelopes and the cutoff coefficients (four at a time) for the run first, then the audio; a still wave knob's shape is worked out once per run. - control(): no powf / log2f (log2Fast, the cutoff note cached), multiplications for the divisions, exp glide by exp2Fast; Multidrive's in and out gains ramp separately. - Silent: only time moves on, on the same control grid (block sizes still never change the sound); the values snap to their targets when a note wakes the voice. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…mode, seeds On the Force every value MPC sets is the value it last read back plus its step (PolyForce's first device run found both of these there; SubForce had the same code): - Buttons fired only on a rising edge and waited for a release. A tap toggles the read-back, and a button always reads back 0, so no release ever comes: INIT, SAVE, RANDOM, PREV/NEXT and the browser's arrows worked once per instance. Each 1 is now a press. - The preset stepper measured a turn's detents from MPC's previous value, which the last detent had moved by one item (under kQuant): a Q-Link or wheel turn moved one preset and stalled. Each detent is now measured from the plugin's own value. The test host taps and turns the way the Force does (the old surface code fails 10 checks), and its clock can run a turn's events a few ms apart (sft::Turn). Engine: Multi trigger restarted the envelopes when a key was released back to one still held (a trill attacked twice, a Duo pair's other key re-attacked on every lift); it retriggers on keys struck now. Plugin: - Poly aftertouch (what the pads send) on a sounding key counts as pressure; CC 121 resets bend, wheel, pressure and pedal. - Flush-to-zero covers the engine only: the song position, automation and display callbacks run in MPC's own FP mode. - Every instance seeds its own random numbers (noise, drift, S&H, RANDOM): two layered instances played the same noise. SF_FIXED_SEED for the tests and demos. - Diagnostics: with /tmp/subforce.trace present every setParameter is logged to /tmp/subforce.log (PolyForce's plugin/trace.*). - sfbench checks dlsym; plugin-package warns when the .so needs glibc over the catalog's 2.32. Tests: S&H must change between cycles and hold within one, Smooth must move and glide monotonically (both passed for a stuck source). Docs: the Force's input rules, diagnostics, libstdc++, the trigger and pedal behaviour, the new MIDI messages. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ise; 30 more presets
Where the Sub 37 / Subsequent 37 manuals say how the panel behaves, SubForce now does the same:
- Feedback: the mixer's own output back into its feedback channel (the manual's block diagram:
a mixer-only loop before the filter), through that channel's overload (a cubic clipper),
AC coupled at 150 Hz and band-limited at 7 kHz. Tuned on renders: +1 dB and more drive at half
the knob, unity loop gain at 85%, grit, then the chaos of an overdriven loop (+6 dB, upper
harmonics +9 dB); no subharmonic motorboating. It was the post-VCA output (the Minimoog trick).
- Resonance: self-oscillation past 70% of the knob ("settings above 7"), 4.6 at full as before.
- Multidrive's second stage is asymmetric: tube-like even harmonics in the middle of its range
(a triangle's 2nd harmonic at -20 dB at 50%), subtle low, toward symmetric hard clipping at
full.
- Envelopes: the attack is linear (the Sub 37's default), and Loop runs delay, attack, hold,
decay and release ("will loop continuously"); with sustain at 0 it is the D-A-H-D cycle it was.
- Noise colour: white -> pink (Kellet's economy filter at 88.2 kHz, the Sub 37's noise, the new
default) -> dark, the same loudness at every colour (from the filters' responses), a 30 Hz
high-pass against rumble.
Noise and the feedback loop only run while their level is up (the heavy bench patch pays for
them, Init doesn't).
Presets: the factory presets' resonance is converted to keep their ladder feedback; Growl,
Screamer and Noise Sweep retuned for the new loop and noise (median change of the 27 renders:
1.0 dB per third octave). 30 new ones from Sub 37 practice and Stephan Bodzin's documented
use of it (rolling 16th basslines, fifth basslines, legato smears, resonant sequences, acid,
big leads, Duo pads, risers), two new categories (Sequence, Pad) with demo phrases; all 57
level-matched to -18 LUFS.
Tests: the resonance edge (65% silent, 76% sings), bass loss under it, Multidrive's even
harmonics, the feedback's level and grit, the noise colours within 0.3 dB, the loop, the linear
attack. Docs: the user guide (and a melodic techno section), performance, architecture, roadmap.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…views For the first public preview (0.0.1, the plugin catalog's beta channel): - CHANGELOG.md; the release workflow takes the tag's "## X.Y.Z" section as the release notes (the catalog shows them) and fails before publishing if there is none. - docs/img: the pages rendered offline from the skin (OSC, FILTER, AMP, MOD, KEYS); FILTER heads the README, the user guide shows them all. - README: what has been checked on a Force (MPC OS 3.9), MPC OS 3.x as the requirement (release builds use glibc up to 2.27 but need GCC 11's libstdc++; 2.x untested), installing from Releases, where presets live, uninstalling, the changelog, the status. - Docs reviewed against the code: the 0.x version rule (param_compat stays 0 while the list may change; append-only from v0.1), how CI releases from tags, binary compatibility, the test and demo-phrase tables, the status line, the browser (FAVORITES, RECENT, RND, the internal drive), MIDI channels, figures (+6 dB feedback; the subharmonic check now in the suite: -101 dB). - The product names of the original stay out of the docs and comments, as the project's decision says; "the original" is defined once and used throughout. - The feedback loop's coefficient is worked out once, in the constructor. - Vendored release.py, a marked local patch: INSTALL.md no longer puts a slash after user-data files. The manifest's about/requires text updated. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's in it
Release build in CI (
.github/workflows/build.yml, as PolyForce's): the device.sois built inarm32v7/gcc:11-bullseye(GCC 11, glibc 2.31) under QEMU, profile-guided, with the test suite run against the objects it is linked from; the sanitizer suite on x86; the zip checked with the plugin catalog'scatalog_check.pyand kept as an artifact. AvX.Y.Ztag publishes a GitHub release (a prerelease while 0.x). The release.soneeds glibc 2.27.Performance on the Force (
make bench-device, % of the 2902 µs block, PGO):NEON ladder (four stages in vector lanes, the loop solved as an affine chain with one division), no divisions in the oscillators, four-lane cutoff coefficients, no libm in the control step, a cheaper idle path that keeps the control grid (block sizes still never change the sound).
Fixes: buttons and the preset stepper on the Force (each tap is a press; detents measured from the plugin's own value — PolyForce's device-run rules), Multi trigger on key releases, poly aftertouch, CC 121, flush-to-zero scoped to the engine, per-instance random seeds,
/tmp/subforce.tracediagnostics, test host taps and turns like the Force.The hardware's behaviour (from its manuals): the mixer's own feedback loop (was post-VCA), resonance self-oscillating past 70%, asymmetric Multidrive, linear attack, Loop through the release, white → pink → dark noise (pink default). Factory presets converted to keep their character (median change 1 dB per third octave).
Presets: 30 new (57 in 7 categories, new Sequence and Pad), many for melodic techno; all level-matched to −18 LUFS; a melodic techno section in the user guide.
Checked
make testandmake test-arm: 468 passed (x86 ASan/UBSan, and ARM under qemu)OK: subforce 0.0.1 (0 warnings)🤖 Generated with Claude Code