// Copyright (C) 2024 Tomasz Kramkowski // 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, 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"); } }