Created
March 8, 2025 01:53
-
-
Save routevegetable/cf8dea9ee9a6a00ff64c2c49085fb6df to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::{process::Output, time}; | |
use timebase::{Event, Frame}; | |
use tinyaudio::prelude::*; | |
mod timebase; | |
#[derive(Copy, Clone)] | |
struct Note { | |
khz: f32 | |
} | |
impl Note { | |
const fn up_octave(self) -> Self { | |
Self{khz: self.khz * 2.0} | |
} | |
const fn down_octave(self) -> Self { | |
Self{khz: self.khz * 0.5} | |
} | |
const fn up_fifth(self) -> Self { | |
Self{khz: self.khz * 1.498307} | |
} | |
} | |
const C4: Note = Note{khz: 0.26163}; | |
const D4: Note = Note{khz: 0.29366}; | |
const E4: Note = Note{khz: 0.32963}; | |
const F4: Note = Note{khz: 0.34923}; | |
const G4: Note = Note{khz: 0.392}; | |
const A4: Note = Note{khz: 0.44}; | |
const B4: Note = Note{khz: 0.49388}; | |
const MSEC: i32 = 10; | |
const SEC: i32 = 10000; | |
const HNDRDUSEC: i32 = 10; | |
const fn period(khz: f32) -> i32 { | |
(MSEC as f32 / khz) as i32 | |
} | |
struct Piano<'a> { | |
frame: &'a Frame, | |
output: f32, | |
} | |
impl <'a> Piano<'a> { | |
fn new(frame: &'a Frame) -> Self { | |
Self { | |
frame, | |
output: 0.0, | |
} | |
} | |
fn key(&mut self, ev: Event, khz: f32) { | |
// Fundamental | |
let tone = self.frame.timebase(timebase::TimebaseMode::Repeat, period(khz), ev).circle().sin(); | |
// Low harmonic | |
let tone2 = self.frame.timebase(timebase::TimebaseMode::Repeat, period(khz / 2.0), ev).circle().sin() / 3.0; | |
// Sustain | |
let ramp = self.frame.timebase(timebase::TimebaseMode::OneShot, SEC, ev); | |
self.output += (tone + tone2) * ramp.scale(0.15, 0.0); | |
} | |
fn out(&self) -> f32 { | |
self.output | |
} | |
} | |
fn audio(f: &Frame) -> f32 { | |
let mut piano = Piano::new(f); | |
let tune = f.timebase(timebase::TimebaseMode::Repeat, 8*SEC, Event::zero()); | |
let notes = [ | |
C4, E4, G4, C4, F4, E4, D4, C4.up_octave() | |
]; | |
let long_notes = notes.repeat(4).into_iter().zip([ | |
true, false, true, true, false, true, true, false, true, true, false, true, true, true, true, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, | |
]).map(|(note, up)| if up {note.up_fifth()} else {note}); | |
for (ev, note) in tune.seq::<32>().iter().zip(long_notes) { | |
piano.key(*ev, note.khz); | |
} | |
piano.out() | |
} | |
fn main() { | |
let _device = run_output_device(OutputDeviceParameters{ | |
sample_rate:10000, | |
channels_count:1, | |
channel_sample_count:1000 | |
}, { | |
let mut clock = 0i32; | |
move |data| { | |
for frame in data { | |
clock = clock + period(10.0); | |
*frame = audio(&Frame::new(clock)); | |
} | |
}}).unwrap(); | |
std::thread::sleep(std::time::Duration::from_secs(16)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment