endless-rs 0.2.0

A Rust SDK for the Polyend Endless programmable effects pedal
Documentation
  • Coverage
  • 39.02%
    16 out of 41 items documented0 out of 1 items with examples
  • Size
  • Source code size: 31.3 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 265.5 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 1s Average build duration of successful builds.
  • all releases: 5s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • ceejbot/endless-rs
    1 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • ceejbot

endless-rs

A Rust SDK for the Polyend Endless effects pedal. Write custom audio effects in Rust instead of C++. The crate provides a safe Patch trait and a macro that generates all the ABI glue needed to produce .endl patch binaries.

The official C/C++ SDK is here on GitHub for comparison.

Requirements

  • a stable Rust, edition 2024
  • the required ARM target: thumbv7em-none-eabihf
  • the GNU ARM toolchain: arm-none-eabi-objcopy (for producing .endl binaries)
rustup target add thumbv7em-none-eabihf
brew install arm-none-eabi-gcc    # macOS; use your package manager elsewhere
just setup                        # if you use just: installs both of the above plus dev tools

See examples/bitcrush/ for a port of the official SDK's bitcrush effect. If you can build and flash this successfully, you're in business.

The Patch trait

Write effects by defining a struct to hold any data you need for state, then implementing the Patch trait for your struct type. All functions defined by Patch have default no-op implementations. Override only what you need.

Method Called
init(&mut self) once when patch loads
set_working_buffer(&mut self, &'static mut [f32]) firmware provides external RAM buffer (once)
process_audio(&mut self, &mut [f32], &mut [f32]) -> f32 for every audio frame (48 kHz, stereo)
param_metadata(&self, ParamId) -> ParameterMetadata when firmware queries knob ranges
set_param(&mut self, ParamId, f32) when user turns a knob (audio thread)
handle_action(&mut self, ActionId) when the foot switch is pressed or held
state_led_color(&self) -> Color the firmware polls LED color

Constraints

Endless effects are #![no_std] code that runs on an ARM Cortex-M7. Keep in mind the limitations.

  • No heap allocation. Use static storage or the working buffer.
  • Single-precision float only. Use f32 everywhere; f64 operations are software-emulated and slow.
  • Keep process_audio fast. If it takes too long, audio frames drop.
  • Output samples in (-1.0, 1.0) to avoid digital clipping.
  • For math functions like powf or sinf, add the libm crate.

Write your own Endless effect

Here's another full example of making a new binary crate that depends on endless-rs:

# Cargo.toml
[package]
name = "hello-world"
version = "0.1.0"
edition = "2024"

[[bin]]
name = "hello-world"
path = "src/main.rs"

[dependencies]
endless-rs = "0.2"

[profile.release]
opt-level = "s"
lto = true
strip = "symbols"

Add a .cargo/config.toml to target the Endless hardware. The endless-rs build script puts its linker script on the search path, so -Tpatch_imx.ld resolves wherever the crate lives:

[build]
target = "thumbv7em-none-eabihf"

[target.thumbv7em-none-eabihf]
rustflags = [
  "-C", "link-arg=-Tpatch_imx.ld",
  "-C", "link-arg=--defsym=PATCH_LOAD_ADDR=0x80000000",
]

And now we write the hello world of DSP effects: gain!

#![no_std]
#![no_main]

use endless_rs::{ParamId, Patch, endless_patch};

struct HelloWorld {
    gain: f32,
}

impl Patch for HelloWorld {
    fn process_audio(&mut self, left: &mut [f32], right: &mut [f32]) -> f32 {
        for (l, r) in left.iter_mut().zip(right.iter_mut()) {
            *l *= self.gain;
            *r *= self.gain;
        }
        0.0
    }

    fn set_param(&mut self, param: ParamId, value: f32) {
        if param == ParamId::Left {
            self.gain = value;
        }
    }
}

endless_patch!(HelloWorld, HelloWorld { gain: 0.5 });

Build the Rust binary, then run the ARM tool that turns it into a flashable artifact:

cargo build --release
arm-none-eabi-objcopy -O binary \
    target/thumbv7em-none-eabihf/release/hello-world \
    hello-world.endl

Connect the Endless pedal to your computer over USB C, and copy the .endl file to the mounted Endless drive. If all goes well, the pedal's LED turns yellow, then flashes green, then becomes a steady blue, indicating that the effect is active. If the pedal LED goes red, the flash failed.

Developing this crate

The crate itself has no unit tests: the ABI glue only exists once the endless_patch! macro is expanded, and that happens in the example. just ci runs every check, including building the example for the ARM target and verifying the emitted patch header byte for byte against the ABI.

just ci      # fmt check, clippy, doctests, and the ARM build + header check of the example
just endl    # build the bitcrush example into a timestamped .endl file
just inspect # dump the example's ELF sections

License

MIT