diff options
Diffstat (limited to 'beeps.rs')
-rw-r--r-- | beeps.rs | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/beeps.rs b/beeps.rs new file mode 100644 index 0000000..f3c3ac8 --- /dev/null +++ b/beeps.rs @@ -0,0 +1,135 @@ +// Copyright (C) 2024 Tomasz Kramkowski <tomasz@kramkow.ski> +// SPDX-License-Identifier: GPL-3.0-or-later + +use std::{ + env, + f64::consts, + io::{self, Write}, + process, +}; +type Float = f64; + +const RATE: f64 = 8000.0; + +#[derive(Debug, Copy, Clone)] +struct Beep { + frequency: Float, + volume: Float, +} + +impl Beep { + fn new(frequency: Float, volume: Float) -> Self { + Self { frequency, volume } + } + + fn sample_at(&self, time: Float) -> Float { + Float::sin(time * consts::TAU * self.frequency) * self.volume + } +} + +#[derive(Copy, Clone)] +struct Beat<'a> { + beep: Beep, + start: Float, + duration: Float, + interval: Float, + mask: &'a dyn Fn(Float) -> bool, +} + +impl<'a> Beat<'a> { + fn new( + beep: Beep, + start: Float, + duration: Float, + interval: Option<Float>, + mask: Option<&'a dyn Fn(Float) -> bool>, + ) -> Self { + Self { + beep, + start, + duration, + interval: interval.unwrap_or(Float::INFINITY), + mask: mask.unwrap_or(&|_| true), + } + } + + fn rel_time(&self, time: Float) -> Float { + (time - self.start) % self.interval + } + + fn plays_now(&self, time: Float) -> bool { + (self.mask)(time) && time > self.start && self.rel_time(time) < self.duration + } + + fn sample_at(&self, time: Float) -> Float { + self.beep.sample_at(self.rel_time(time)) + } +} + +fn sample_at(beats: &[Beat<'_>], time: Float) -> Float { + let mut sample = 0.0; + + for beat in beats { + if beat.plays_now(time) { + sample += beat.sample_at(time) + } + } + + sample +} + +fn main() { + let mut args = env::args(); + // This is bugged, argv[0] is empty if you exec with an empty or no argv + let argv0 = args.next().expect("No argv0"); + let (Some(duration), None) = (args.next(), args.next()) else { + eprintln!("Usage: {argv0} duration"); + process::exit(1); + }; + let duration: Float = duration.parse().expect("Invalid duration"); + let mut stdout = io::stdout(); + + let endstop = |s| s < duration - 1.0; + + let beats = [ + Beat::new(Beep::new(500.0, 0.5), 0.0, 0.1, Some(30.0), Some(&endstop)), + Beat::new(Beep::new(500.0, 0.5), 14.9, 0.1, Some(30.0), Some(&endstop)), + Beat::new(Beep::new(600.0, 0.5), 15.0, 0.1, Some(30.0), Some(&endstop)), + Beat::new(Beep::new(600.0, 0.5), -0.1, 0.1, Some(30.0), Some(&endstop)), + Beat::new( + Beep::new(300.0, 0.3), + 1.45, + 0.1, + Some(3.0), + Some(&|s| (s / 15.0) as u64 % 2 == 0), + ), + Beat::new( + Beep::new(1000.0, 0.4), + 299.95, + 0.05, + Some(300.0), + Some(&endstop), + ), + Beat::new( + Beep::new(1000.0, 0.4), + 300.05, + 0.05, + Some(300.0), + Some(&endstop), + ), + Beat::new(Beep::new(400.0, 0.5), duration + 0.0, 0.4, None, None), + Beat::new(Beep::new(500.0, 0.5), duration + 0.45, 0.4, None, None), + Beat::new(Beep::new(600.0, 0.5), duration + 0.9, 0.4, None, None), + ]; + + let start = -0.1; + let end = duration + 1.4; + + for sample_index in 0..((RATE * (end - start)) as u64) { + let time = start + sample_index as Float / RATE; + let sample = sample_at(&beats, time); + stdout + .write_all(&[((sample + 1.0) / 2.0 * 256.0).floor().clamp(0.0, 255.0) as u8]) + .expect("Could not write sample"); + } +} |