mvp (minimum vibrational production)

This commit is contained in:
mehbark 2025-10-10 23:54:46 -04:00
commit 3aab7d93a3
4 changed files with 123 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

16
Cargo.lock generated Normal file
View file

@ -0,0 +1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "hound"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
[[package]]
name = "rpip7"
version = "0.1.0"
dependencies = [
"hound",
]

7
Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[package]
name = "rpip7"
version = "0.1.0"
edition = "2024"
[dependencies]
hound = "3.5.1"

99
src/main.rs Normal file
View file

@ -0,0 +1,99 @@
#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
clippy::cast_precision_loss
)]
use std::{
env,
f32::consts::PI,
io::{self, Read},
num::NonZero,
};
#[derive(Debug)]
struct Note {
freq: u16,
beats: NonZero<u16>,
}
impl Note {
fn rest() -> Self {
Self {
freq: 0,
beats: NonZero::<u16>::MIN,
}
}
}
struct Parser<'a> {
src: &'a [u8],
}
impl<'a> Parser<'a> {
fn new(src: &'a [u8]) -> Self {
Self { src }
}
}
impl Iterator for Parser<'_> {
type Item = Note;
fn next(&mut self) -> Option<Self::Item> {
let first = *self.src.first()?;
self.src = &self.src[1..];
if first == b' ' {
return Some(Note::rest());
}
let Some(n) = b"1234567drmfsltDRMSFLT".iter().position(|b| *b == first) else {
// skip invalid byte
return self.next();
};
let a4_pos = 12.;
let n = n as f32 - a4_pos;
let freq = 440. * 2.0f32.powf(n / 12.);
let freq = freq.floor() as u16;
let beats = self.src.iter().take_while(|b| **b == b'-').count();
let beats = NonZero::<u16>::new(beats as u16 + 1).unwrap();
Some(Note { freq, beats })
}
}
fn main() {
let beats_per_minute: f32 = env::args()
.nth(1)
.expect("give me bpm")
.parse()
.expect("bpm is a number please");
let beats_per_second = beats_per_minute / 60.;
let output_path = env::args().nth(2).expect("give me a path");
let mut src = String::new();
io::stdin().read_to_string(&mut src).unwrap();
let spec = hound::WavSpec {
channels: 1,
sample_rate: 44100,
bits_per_sample: 16,
sample_format: hound::SampleFormat::Int,
};
let mut writer = hound::WavWriter::create(output_path, spec).unwrap();
for note in Parser::new(src.as_bytes()) {
let freq = f32::from(note.freq);
let amplitude = f32::from(i16::MAX);
let samples = (spec.sample_rate * u32::from(note.beats.get())) / beats_per_second as u32;
for t in (0..samples).map(|x| x as f32 / (spec.sample_rate as f32)) {
let sample = (t * freq * 2.0 * PI).sin();
writer.write_sample((sample * amplitude) as i16).unwrap();
}
}
}