mvp (minimum vibrational production)
This commit is contained in:
commit
3aab7d93a3
4 changed files with 123 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
16
Cargo.lock
generated
Normal file
16
Cargo.lock
generated
Normal 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
7
Cargo.toml
Normal 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
99
src/main.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue