Initial commit
This commit is contained in:
commit
d0d66c4e86
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 = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "wav"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"paste",
|
||||
]
|
||||
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "wav"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
paste = "1.0.15"
|
||||
BIN
output.wav
Normal file
BIN
output.wav
Normal file
Binary file not shown.
42
src/lib.rs
Normal file
42
src/lib.rs
Normal file
@ -0,0 +1,42 @@
|
||||
pub mod note;
|
||||
pub mod piece;
|
||||
pub mod wav;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufWriter};
|
||||
|
||||
use note::notes::*;
|
||||
use piece::{NoteEvent, Piece};
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn main() -> io::Result<()> {
|
||||
let mut piece = Piece::new(120);
|
||||
let mut time = 0.0;
|
||||
|
||||
for _ in 0..4 {
|
||||
piece.notes.push(NoteEvent::new(FS3, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(FS3, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(CS4, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(FS3, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(D4, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(FS3, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(CS4, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(FS3, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(B4, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(A4, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(GS3, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(A4, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(B4, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(A4, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(GS3, time, 0.5)); time += 0.5;
|
||||
piece.notes.push(NoteEvent::new(E3, time, 0.5)); time += 0.5;
|
||||
}
|
||||
|
||||
piece.notes.push(NoteEvent::new(FS3, time, 0.5));
|
||||
|
||||
let file = File::create("output.wav")?;
|
||||
let mut writer = BufWriter::new(file);
|
||||
piece.generate_wav(&mut writer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
5
src/main.rs
Normal file
5
src/main.rs
Normal file
@ -0,0 +1,5 @@
|
||||
fn main() {
|
||||
if let Err(e) = wav::main() {
|
||||
eprintln!("error: {}", e);
|
||||
}
|
||||
}
|
||||
102
src/note.rs
Normal file
102
src/note.rs
Normal file
@ -0,0 +1,102 @@
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Key {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub fn semitones(self) -> i32 {
|
||||
match self {
|
||||
Key::A => 0,
|
||||
Key::B => 2,
|
||||
Key::C => 3,
|
||||
Key::D => 5,
|
||||
Key::E => 7,
|
||||
Key::F => 8,
|
||||
Key::G => 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Modifier {
|
||||
Flat,
|
||||
Natural,
|
||||
Sharp,
|
||||
}
|
||||
|
||||
impl Modifier {
|
||||
pub fn semitones(self) -> i32 {
|
||||
match self {
|
||||
Modifier::Flat => -1,
|
||||
Modifier::Natural => 0,
|
||||
Modifier::Sharp => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Note {
|
||||
pub key: Key,
|
||||
pub modifier: Modifier,
|
||||
pub octave: i32,
|
||||
}
|
||||
|
||||
impl Note {
|
||||
pub const fn new(key: Key, modifier: Modifier, octave: i32) -> Note {
|
||||
Note {
|
||||
key,
|
||||
modifier,
|
||||
octave,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Note {
|
||||
pub fn freq(self) -> f64 {
|
||||
let semitones = self.key.semitones() + self.modifier.semitones();
|
||||
let n = semitones + ((self.octave as i32 - 4) * 12);
|
||||
440.0 * 2f64.powf(n as f64 / 12.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub mod notes {
|
||||
use crate::note::{Key, Modifier, Note};
|
||||
use paste::paste;
|
||||
|
||||
macro_rules! notes_octave {
|
||||
($oct:expr) => {
|
||||
paste! {
|
||||
pub const [<C $oct>]: Note = Note::new(Key::C, Modifier::Natural, $oct);
|
||||
pub const [<CS $oct>]: Note = Note::new(Key::C, Modifier::Sharp, $oct);
|
||||
pub const [<D $oct>]: Note = Note::new(Key::D, Modifier::Natural, $oct);
|
||||
pub const [<DS $oct>]: Note = Note::new(Key::D, Modifier::Sharp, $oct);
|
||||
pub const [<E $oct>]: Note = Note::new(Key::E, Modifier::Natural, $oct);
|
||||
pub const [<F $oct>]: Note = Note::new(Key::F, Modifier::Natural, $oct);
|
||||
pub const [<FS $oct>]: Note = Note::new(Key::F, Modifier::Sharp, $oct);
|
||||
pub const [<G $oct>]: Note = Note::new(Key::G, Modifier::Natural, $oct);
|
||||
pub const [<GS $oct>]: Note = Note::new(Key::G, Modifier::Sharp, $oct);
|
||||
pub const [<A $oct>]: Note = Note::new(Key::A, Modifier::Natural, $oct);
|
||||
pub const [<AS $oct>]: Note = Note::new(Key::A, Modifier::Sharp, $oct);
|
||||
pub const [<B $oct>]: Note = Note::new(Key::B, Modifier::Natural, $oct);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
notes_octave!(0);
|
||||
notes_octave!(1);
|
||||
notes_octave!(2);
|
||||
notes_octave!(3);
|
||||
notes_octave!(4);
|
||||
notes_octave!(5);
|
||||
notes_octave!(6);
|
||||
notes_octave!(7);
|
||||
notes_octave!(8);
|
||||
notes_octave!(9);
|
||||
}
|
||||
74
src/piece.rs
Normal file
74
src/piece.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use std::io::{self, Write};
|
||||
|
||||
use crate::note::*;
|
||||
use crate::wav;
|
||||
|
||||
pub struct NoteEvent {
|
||||
pub note: Note,
|
||||
pub start: f64,
|
||||
pub duration: f64,
|
||||
}
|
||||
|
||||
impl NoteEvent {
|
||||
pub fn new(note: Note, start: f64, duration: f64) -> NoteEvent {
|
||||
NoteEvent {
|
||||
note,
|
||||
start,
|
||||
duration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Piece {
|
||||
pub notes: Vec<NoteEvent>,
|
||||
pub bpm: u32,
|
||||
}
|
||||
|
||||
impl Piece {
|
||||
pub fn new(bpm: u32) -> Piece {
|
||||
Piece { bpm, notes: vec![] }
|
||||
}
|
||||
|
||||
pub fn generate_wav<W: Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||
let amplitude = 0.1;
|
||||
let seconds_per_beat = 60.0 / self.bpm as f64;
|
||||
let params = wav::Params::new();
|
||||
|
||||
let max_beat = self
|
||||
.notes
|
||||
.iter()
|
||||
.map(|x| x.start + x.duration)
|
||||
.max_by(|x, y| x.total_cmp(y))
|
||||
.unwrap_or(0.0);
|
||||
|
||||
let total_samples =
|
||||
(max_beat * seconds_per_beat * params.sampling_rate as f64).ceil() as u32;
|
||||
|
||||
let data_size = total_samples * params.channels as u32 * params.bytes_per_sample as u32;
|
||||
let total_size = 44 + data_size;
|
||||
|
||||
wav::write_header(total_size, ¶ms, writer)?;
|
||||
|
||||
writer.write_all(b"data")?;
|
||||
writer.write_all(&data_size.to_le_bytes())?;
|
||||
|
||||
for i in 0..total_samples {
|
||||
let t = i as f64 / params.sampling_rate as f64;
|
||||
let mut value: f64 = 0.0;
|
||||
for event in &self.notes {
|
||||
let start_time = event.start * seconds_per_beat;
|
||||
let end_time = start_time + event.duration * seconds_per_beat;
|
||||
if t >= start_time && t < end_time {
|
||||
let freq = event.note.freq();
|
||||
let sin = (std::f64::consts::TAU * freq * (t - start_time)).sin();
|
||||
value += if sin >= 0.0 { 1.0 } else { -1.0 };
|
||||
}
|
||||
}
|
||||
|
||||
let sample = (value * (i16::MAX as f64) * amplitude).round() as i16;
|
||||
writer.write_all(&sample.to_le_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
59
src/wav.rs
Normal file
59
src/wav.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub struct Params {
|
||||
pub sampling_rate: u32,
|
||||
pub channels: u16,
|
||||
pub bytes_per_sample: u16,
|
||||
}
|
||||
|
||||
impl Params {
|
||||
pub fn new() -> Params {
|
||||
Params {
|
||||
sampling_rate: 44100,
|
||||
channels: 1,
|
||||
bytes_per_sample: 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_header<W: Write>(total_size: u32, p: &Params, writer: &mut W) -> io::Result<()> {
|
||||
// Premier bloc : déclaration du fichier
|
||||
// FileTypeBlocID (4 octets) : Constante « RIFF » (0x52,0x49,0x46,0x46)
|
||||
writer.write_all(b"RIFF")?;
|
||||
|
||||
// FileSize (4 octets) : Taille du fichier moins 8 octets
|
||||
writer.write_all(&(total_size - 8).to_le_bytes())?;
|
||||
|
||||
// FileSize (4 octets) : Taille du fichier moins 8 octets
|
||||
writer.write_all(b"WAVE")?;
|
||||
|
||||
// Deuxième bloc : format audio
|
||||
// FormatBlocID (4 octets) : Identifiant « fmt␣ » (0x66,0x6D,0x74,0x20)
|
||||
writer.write_all(b"fmt ")?;
|
||||
|
||||
// BlocSize (4 octets) : Nombre d'octets du bloc moins 8 octets, soit ici 16 octets (0x10)
|
||||
writer.write_all(&16_u32.to_le_bytes())?;
|
||||
|
||||
// AudioFormat (2 octets) : Format du stockage dans le fichier (1: PCM entier, 3: PCM flottant, 65534: WAVE_FORMAT_EXTENSIBLE)
|
||||
writer.write_all(&1_u16.to_le_bytes())?;
|
||||
|
||||
// NbrCanaux (2 octets) : Nombre de canaux (de 1 à 6, cf. ci-dessous)
|
||||
writer.write_all(&p.channels.to_le_bytes())?;
|
||||
|
||||
// Frequence (4 octets) : Fréquence d'échantillonnage (en hertz) [Valeurs standardisées : 11 025, 22 050, 44 100 et éventuellement 48 000 et 96 000]
|
||||
writer.write_all(&p.sampling_rate.to_le_bytes())?;
|
||||
|
||||
// BytePerSec (4 octets) : Nombre d'octets à lire par seconde (c.-à-d., Frequence * BytePerBloc).
|
||||
let byte_per_sec = p.sampling_rate * p.channels as u32 * p.bytes_per_sample as u32;
|
||||
writer.write_all(&byte_per_sec.to_le_bytes())?;
|
||||
|
||||
// BytePerBloc (2 octets) : Nombre d'octets par bloc d'échantillonnage (c.-à-d., tous canaux confondus : NbrCanaux * BitsPerSample/8).
|
||||
let byte_per_block = p.channels * p.bytes_per_sample;
|
||||
writer.write_all(&byte_per_block.to_le_bytes())?;
|
||||
|
||||
// BitsPerSample (2 octets) : Nombre de bits utilisés pour le codage de chaque échantillon (8, 16, 24)
|
||||
let bits_per_sample = p.bytes_per_sample * 8;
|
||||
writer.write_all(&bits_per_sample.to_le_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user