pub mod audio;
pub mod cache;
pub mod composition;
pub mod consts;
pub mod engine;
pub mod error;
#[cfg(feature = "gpu")]
pub mod gpu;
pub mod instruments;
pub mod live_coding;
pub mod midi;
pub mod sequences;
pub mod synthesis;
pub mod templates;
pub mod theory;
pub mod track;
#[doc(hidden)]
pub use inventory;
#[derive(Debug)]
pub struct RegisteredSample {
pub path: &'static str,
}
inventory::collect!(RegisteredSample);
pub fn validate_all_samples() -> error::Result<()> {
let mut missing = Vec::new();
for sample in inventory::iter::<RegisteredSample> {
if !std::path::Path::new(sample.path).exists() {
missing.push(sample.path);
}
}
if !missing.is_empty() {
eprintln!("ERROR: Missing {} sample(s) at startup:", missing.len());
for path in &missing {
eprintln!(" - {}", path);
}
return Err(error::TunesError::SampleNotFound(format!(
"{} sample file(s) not found",
missing.len()
)));
}
let total = inventory::iter::<RegisteredSample>().count();
if total > 0 {
eprintln!("✓ All {} sample(s) validated successfully", total);
}
Ok(())
}
#[macro_export]
macro_rules! play_sample {
($engine:expr, $path:literal) => {{
const SAMPLE_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/", $path);
$crate::inventory::submit! {
$crate::RegisteredSample { path: SAMPLE_PATH }
}
$engine.play_sample(SAMPLE_PATH)
}};
}
pub mod prelude {
pub use crate::play_sample;
pub use crate::validate_all_samples;
pub use crate::composition::{Composition, DrumGrid, DrumType, Tempo};
pub use crate::engine::{AudioEngine, SamplePlaybackBuilder, SoundId};
pub use crate::track::Mixer;
pub use crate::error::{Result, TunesError};
pub use crate::consts::*;
pub use crate::theory::{
ChordPattern, KeyMode, KeyRoot, KeySignature, ProgressionType, ScalePattern, chord,
progression, scale, transpose, transpose_sequence,
};
pub use crate::instruments::Instrument;
pub use crate::synthesis::effects::*;
pub use crate::synthesis::{Filter, FilterType};
pub use crate::synthesis::{
AdditiveSynth, Envelope, FMParams, FilterEnvelope, GranularParams, KarplusStrong,
NoiseType, Partial, Sample, SampleSlice, Waveform, Wavetable,
};
pub use crate::synthesis::{
BlueNoise, BrownNoise, GreenNoise, NoiseGenerator, PerlinNoise, PinkNoise, WhiteNoise,
};
pub use crate::synthesis::{EQBand, EQPreset, ParametricEQ};
pub use crate::synthesis::{
AttenuationModel, ListenerConfig, SpatialParams, SpatialPosition, SpatialResult, Vec3,
};
pub use crate::synthesis::{LFO, ModRoute, ModTarget};
pub use crate::sequences::{
golden_ratio, golden_ratio_rhythm, golden_sections, harmonic_series,
};
pub use crate::synthesis::{Automation, Interpolation};
pub use crate::theory::{
EDO12, EDO19, EDO24, EDO31, EDO53, Edo, cents_to_ratio, freq_from_cents, half_flat,
half_sharp, just_major_scale, just_minor_scale, just_ratio, just_scale, pythagorean_scale,
quarter_flat, quarter_sharp, ratio_to_cents,
};
pub use crate::midi::{
drum_type_to_midi_note, frequency_to_midi_note, midi_note_to_drum_type,
midi_note_to_frequency,
};
pub use crate::audio::LiveInput;
}
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn run_web_demo() -> std::result::Result<(), JsValue> {
use crate::prelude::*;
use std::sync::Mutex;
console_error_panic_hook::set_once();
web_sys::console::log_1(&"Initializing Tunes audio engine...".into());
let engine = Box::leak(Box::new(AudioEngine::new()
.map_err(|e| JsValue::from_str(&format!("Failed to create audio engine: {}", e)))?));
web_sys::console::log_1(&"Audio engine created successfully!".into());
let mut comp = Composition::new(Tempo::new(120.0));
web_sys::console::log_1(&"Creating melody...".into());
comp.instrument("piano", &Instrument::electric_piano())
.notes(&[C4, E4, G4, C5], 0.5)
.notes(&[C5, G4, E4, C4], 0.5);
web_sys::console::log_1(&"Created composition with piano melody".into());
let mixer = comp.into_mixer();
let id = engine.play_mixer_realtime(&mixer)
.map_err(|e| JsValue::from_str(&format!("Failed to play mixer: {}", e)))?;
web_sys::console::log_1(&format!("Playing mixer with ID: {}", id).into());
web_sys::console::log_1(&"Tunes web demo complete! You should hear a C major arpeggio.".into());
Ok(())
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub struct WebPiano {
engine: std::cell::RefCell<Option<&'static engine::AudioEngine>>,
current_octave: i32,
current_instrument: u8,
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
impl WebPiano {
#[wasm_bindgen(constructor)]
pub fn new() -> std::result::Result<WebPiano, JsValue> {
console_error_panic_hook::set_once();
web_sys::console::log_1(&"WebPiano ready! Click a key to start.".into());
Ok(WebPiano {
engine: std::cell::RefCell::new(None),
current_octave: 4,
current_instrument: 0,
})
}
fn get_or_init_engine(&self) -> std::result::Result<&'static engine::AudioEngine, JsValue> {
let mut engine_ref = self.engine.borrow_mut();
if engine_ref.is_none() {
web_sys::console::log_1(&"Initializing audio engine...".into());
let engine = Box::leak(Box::new(
engine::AudioEngine::new()
.map_err(|e| JsValue::from_str(&format!("Failed to create audio engine: {}", e)))?,
));
web_sys::console::log_1(&"Audio engine ready!".into());
*engine_ref = Some(engine);
}
Ok(engine_ref.unwrap())
}
pub fn play_note(&self, semitone: u8) -> std::result::Result<(), JsValue> {
self.play_note_in_octave(semitone, self.current_octave)
}
pub fn play_note_in_octave(&self, semitone: u8, octave: i32) -> std::result::Result<(), JsValue> {
use crate::consts::*;
let engine = self.get_or_init_engine()?;
let notes_oct2: [f32; 12] = [C2, CS2, D2, DS2, E2, F2, FS2, G2, GS2, A2, AS2, B2];
let notes_oct3: [f32; 12] = [C3, CS3, D3, DS3, E3, F3, FS3, G3, GS3, A3, AS3, B3];
let notes_oct4: [f32; 12] = [C4, CS4, D4, DS4, E4, F4, FS4, G4, GS4, A4, AS4, B4];
let notes_oct5: [f32; 12] = [C5, CS5, D5, DS5, E5, F5, FS5, G5, GS5, A5, AS5, B5];
let notes_oct6: [f32; 12] = [C6, CS6, D6, DS6, E6, F6, FS6, G6, GS6, A6, AS6, B6];
let frequency = match octave {
2 => notes_oct2[semitone as usize % 12],
3 => notes_oct3[semitone as usize % 12],
4 => notes_oct4[semitone as usize % 12],
5 => notes_oct5[semitone as usize % 12],
6 => notes_oct6[semitone as usize % 12],
_ => notes_oct4[semitone as usize % 12],
};
let instrument = self.get_instrument();
let mut comp = composition::Composition::new(composition::Tempo::new(120.0));
comp.instrument("piano", &instrument)
.notes(&[frequency], 0.5);
let mixer = comp.into_mixer();
engine
.play_mixer_realtime(&mixer)
.map_err(|e| JsValue::from_str(&format!("Failed to play note: {}", e)))?;
Ok(())
}
pub fn set_octave(&mut self, octave: i32) {
self.current_octave = octave.clamp(2, 6);
}
pub fn get_octave(&self) -> i32 {
self.current_octave
}
pub fn set_instrument(&mut self, index: u8) {
self.current_instrument = index.min(7);
}
pub fn get_instrument_name(&self, index: u8) -> String {
match index {
0 => "Acoustic Piano".to_string(),
1 => "Electric Piano".to_string(),
2 => "Stage 73 Rhodes".to_string(),
3 => "Wurlitzer".to_string(),
4 => "Hammond Organ".to_string(),
5 => "Church Organ".to_string(),
6 => "Clavinet".to_string(),
7 => "Harpsichord".to_string(),
_ => "Unknown".to_string(),
}
}
fn get_instrument(&self) -> instruments::Instrument {
match self.current_instrument {
0 => instruments::Instrument::acoustic_piano(),
1 => instruments::Instrument::electric_piano(),
2 => instruments::Instrument::stage_73(),
3 => instruments::Instrument::wurlitzer(),
4 => instruments::Instrument::hammond_organ(),
5 => instruments::Instrument::church_organ(),
6 => instruments::Instrument::clavinet(),
7 => instruments::Instrument::harpsichord(),
_ => instruments::Instrument::acoustic_piano(),
}
}
}