use std::time::{Duration, Instant};
use anyhow::{Result, anyhow};
use synthie::audio::engine::setup_audio;
use synthie::params::{ArpMode, ArpParams, AudioEvent, ChannelNo, DrumHit, MidiNote, SynthParams};
use synthie::presets::sid::default_patches;
const BPM: f32 = 140.0;
const ARP_RATE: f32 = BPM * 4.0 / 60.0;
const ARP_GATE: f32 = 0.75;
const PHRASE_BEATS: f32 = 8.0;
const CH_ARP: ChannelNo = ChannelNo(0);
const CH_BASS: ChannelNo = ChannelNo(1);
fn beats(n: f32) -> Duration {
Duration::from_secs_f32(n * 60.0 / BPM)
}
struct Section {
label: &'static str,
mode: ArpMode,
notes: [MidiNote; 4],
bass: MidiNote,
}
static SECTIONS: &[Section] = &[
Section {
label: "Up — Am7 (A3 C4 E4 G4)",
mode: ArpMode::Up,
notes: [MidiNote(57), MidiNote(60), MidiNote(64), MidiNote(67)],
bass: MidiNote(45), },
Section {
label: "Down — Dm7 (D4 F4 A4 C5)",
mode: ArpMode::Down,
notes: [MidiNote(62), MidiNote(65), MidiNote(69), MidiNote(72)],
bass: MidiNote(50), },
Section {
label: "UpDown — Em7 (E4 G4 B4 D5)",
mode: ArpMode::UpDown,
notes: [MidiNote(64), MidiNote(67), MidiNote(71), MidiNote(74)],
bass: MidiNote(52), },
Section {
label: "Random — Am7 (A3 E4 A4 C5)",
mode: ArpMode::Random,
notes: [MidiNote(57), MidiNote(64), MidiNote(69), MidiNote(72)],
bass: MidiNote(45), },
];
fn main() -> Result<()> {
let (_stream, event_tx, _scope_rx) = setup_audio()?;
let patches = default_patches();
let find = |name: &str| -> Result<SynthParams> {
patches
.iter()
.find(|p| p.name == name)
.ok_or_else(|| anyhow!("preset '{name}' not found"))
.map(|p| p.params.clone())
};
let arp_lead = find("Arp Lead")?;
let c64_bass = find("C64 Bass")?;
event_tx.send(AudioEvent::LoadPatchChannel(CH_BASS, Box::new(c64_bass)))?;
println!("=== Arpeggiator Demo ===");
println!("{BPM} BPM | 16th-note rate ({ARP_RATE:.1} Hz) | 2 bars per section\n");
for section in SECTIONS {
println!(" {}", section.label);
let mut params = arp_lead.clone();
params.arp = ArpParams {
enabled: false,
rate: ARP_RATE,
gate: ARP_GATE,
mode: section.mode,
..ArpParams::default()
};
event_tx.send(AudioEvent::LoadPatchChannel(CH_ARP, Box::new(params)))?;
event_tx.send(AudioEvent::ArpSetNotes(CH_ARP, section.notes, 4))?;
event_tx.send(AudioEvent::ArpEnabled(CH_ARP, true))?;
let started = Instant::now();
event_tx.send(AudioEvent::NoteOnChannel(CH_BASS, section.bass))?;
let mut drum_events: Vec<(Duration, DrumHit)> = Vec::new();
for bar in 0..2u8 {
let bar_start = f32::from(bar) * 4.0;
for beat in 0u8..4 {
drum_events.push((beats(bar_start + f32::from(beat)), DrumHit::Kick));
}
for eighth in 0u8..8 {
drum_events.push((
beats(bar_start + f32::from(eighth) * 0.5),
DrumHit::HiHatClosed,
));
}
}
drum_events.sort_by_key(|e| e.0);
for (t, hit) in &drum_events {
let deadline = started + *t;
let now = Instant::now();
if deadline > now {
std::thread::sleep(deadline.duration_since(now));
}
event_tx.send(AudioEvent::Drum(*hit))?;
}
event_tx.send(AudioEvent::NoteOffChannel(CH_BASS, section.bass))?;
let deadline = started + beats(PHRASE_BEATS);
let now = Instant::now();
if deadline > now {
std::thread::sleep(deadline.duration_since(now));
}
}
println!();
std::thread::sleep(beats(1.0));
event_tx.send(AudioEvent::Panic)?;
std::thread::sleep(Duration::from_millis(150));
Ok(())
}