# chiptunomatic
The core synthesis library. Turns any byte sequence into a deterministic chiptune: the same file name and byte length always produce the same music.
The library is `no_std` + `alloc` with an optional `std` layer, so it works on native targets and in WebAssembly (`wasm32-unknown-unknown`).
## Adding the dependency
```toml
[dependencies]
chiptunomatic = "0.3"
```
For WebAssembly in a JS host, enable the `js` feature so `getrandom` uses its JS backend:
```toml
[dependencies]
chiptunomatic = { version = "0.3", default-features = false, features = ["js", "std"] }
```
## Quick start
```rust
use chiptunomatic::{random::StdRandom, Chiptunomatic};
// Build an instance with all built-in modes and a seeded RNG.
let mut chip = Chiptunomatic::default()
.with_default_plugins()
.with_random(Box::new(StdRandom::new()));
// Load metadata from a file (derives seed from filename + byte length).
let meta = chip.load_song_metadata_from_path("my_file.bin")?;
println!("BPM: {} duration: {}", meta.bpm, meta.total_duration_str);
// Collect every mixed sample into a Vec<Sample>.
let samples: Vec<_> = chip.samples();
for s in &samples {
// s.value is the mixed mono f32 in roughly [-1, 1].
// s.stems gives per-stem (voice, square, triangle, noise) floats.
}
```
## Metadata
`SongMetadata` is derived deterministically from a seed (typically the file name) and a byte length. It carries all musical decisions for the track.
```rust
// Non-mutating: compute without changing the instance state.
let meta = chip.song_metadata_from_path("file.bin")?;
// Mutating: compute and install (required before synthesis).
let meta = chip.load_song_metadata_from_path("file.bin")?;
// From a string name + known byte count — works on wasm32 too.
let meta = chip.load_song_metadata_from_string("my_song", 102_400)?;
// From raw seed bytes.
let meta = chip.load_song_metadata_from_seed(b"custom_seed", 102_400)?;
```
Key fields on `SongMetadata`:
| `bpm` | `i32` | Beats per minute |
| `root_semitone` | `u8` | Root note (0 = C) |
| `chord_description` | `String` | Human-readable chord loop (e.g. `"C – Eb – G – Eb"`) |
| `total_beats` | `u64` | Total number of beats |
| `total_duration` | `f64` | Duration in seconds |
| `data_byte_len` | `u64` | Byte length used as seed |
## Streaming synthesis (`SampleReader`)
For large files, generate samples on the fly from any `std::io::Read` source without loading everything into memory first:
```rust
use std::fs::File;
use chiptunomatic::{random::StdRandom, Chiptunomatic, Sample};
let mut chip = Chiptunomatic::default()
.with_default_plugins()
.with_random(Box::new(StdRandom::new()));
chip.load_song_metadata_from_path("big_file.bin")?;
let file = File::open("big_file.bin")?;
let mut reader = chip.sample_reader(file);
let mut buf = [Sample::default(); 1024];
loop {
let n = reader.next_buf(&mut buf)?;
if n == 0 {
break; // end of stream
}
for sample in &buf[..n] {
// process sample.value (mixed mono f32)
}
}
```
`SampleReader` also implements `Iterator<Item = std::io::Result<Sample>>`.
## Mixer and stem control
Each track has four stems — **voice**, **square** (melody), **triangle** (bass), **noise** (drums) — plus a master output. All are controlled through `MixerConfig`:
```rust
use chiptunomatic::{MasterOutput, MixerConfig, StemOutput};
chip.mixer_mut().set_config(MixerConfig {
master_output: MasterOutput {
volume: 0.8,
muted: false,
},
voice_output: StemOutput { volume: 1.0, muted: false, solo: false },
square_output: StemOutput { volume: 1.0, muted: false, solo: true }, // melody only
triangle_output: StemOutput { volume: 1.0, muted: true, solo: false }, // bass muted
noise_output: StemOutput { volume: 1.0, muted: false, solo: false },
});
```
Multiple solos are additive: every stem whose `solo` flag is `true` is heard; the rest are silenced.
`Sample::value` is the normalised, mixed mono f32. `Sample::stems` exposes the raw per-stem floats before mixing, useful for visualisation or custom mixing.
## Modes
Each mode produces a different musical style. The default instance has only the `chiptune` mode. Call `.with_default_plugins()` to register all built-in modes:
```rust
// List available modes.
let modes = chip.modes(); // &["chiptune", "rock", "metal", ...]
// Switch mode (resets internal generator state automatically).
chip.set_mode(&"rock".to_string())?;
```
| `chiptune` | Classic 8-bit square / triangle / noise |
| `rock` | Power chords, overdriven bass, loud kit |
| `metal` | Hard-clipped power chords, double-kick patterns |
| `rap` | Boom-bap FM piano, punchy sine bass |
| `trap` | FM bell melody, 808 pitch-sweep bass, stutter hi-hats |
| `toy` | FM kalimba tines with octave shimmer |
| `samba` | Reedy FM melody, surdo on beats 2 & 4, teleco-teco tamborim |
| `koto` | Karplus-Strong plucked strings, Hirajoshi pentatonic |
## Custom mode (Plugin)
Implement `Plugin` to register a completely custom mode:
```rust
use chiptunomatic::plugin::Plugin;
#[derive(Debug, Clone)]
struct MyPlugin;
impl Plugin for MyPlugin {
fn mode(&self) -> &'static str { "my_mode" }
// … override tempo_from_seed, chord_progression_from_seed, drum_pattern_from_seed,
// melody_sample, bass_sample, voice_sample as needed.
}
chip.register_plugin(Box::new(MyPlugin));
chip.set_mode(&"my_mode".to_string())?;
```
## Custom random
Provide any `Random` implementation to control how stochastic elements (drum fills, slight pitch variation, etc.) are generated. Use `NoRandom` (the default) for fully deterministic, RNG-free output:
```rust
use chiptunomatic::random::{NoRandom, StdRandom};
// Fully deterministic — no random calls.
let chip = Chiptunomatic::default();
// Seeded standard RNG — deterministic but with musical variation.
let chip = Chiptunomatic::default()
.with_random(Box::new(StdRandom::new()));
```
## WAV export
With the `wav` feature (included in `default`), convert any file directly to a WAV on native targets:
```rust
chip.file_to_wav("input.bin", "output.wav")?;
```
This calls `load_song_metadata_from_path` and streams synthesis internally; no large intermediate buffer is allocated.
## Feature flags
| `std` | yes | Enables `std`-dependent APIs (`SampleReader`, path helpers, `StdRandom`) |
| `wav` | yes | Enables `file_to_wav` via the `hound` crate (requires `std`) |
| `arpeggio` | yes | Enables the `Arpeggiator` for chord note expansion |
| `js` | no | Wires `getrandom`'s JS backend for `wasm32-unknown-unknown` in a JS host |
## Constants
```rust
use chiptunomatic::constants::SAMPLE_RATE; // 44_100 u32
```
All synthesis runs at 44 100 Hz mono. `Sample::value` is a normalised `f32` in roughly `[-1, 1]` — scale to `i16` with `(value * 32767.0) as i16` for 16-bit PCM.