WAV
WAV files made and read: tones, melodies and silence for alerts and tests, joined end to end, and a file's length, peak and loudness.
Version 1.0.0 · 5 functions · 1 crate
Functions
What use wav.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use wav_tone, wav_melody from wav (Quillo 0.6).
wav_tone(path: str, frequency: float, seconds: float, volume: float) → anyor an errorA WAV of one tone at frequency Hz (440 is the A above middle C) for seconds (up to 600), at volume 0 to 1: { duration, sample_rate, samples }. 44.1 kHz, 16-bit, mono.
wav_melody(path: str, notes: list) → anyor an errorA WAV of notes played one after another, each { frequency, seconds, volume } (frequency 0 for a rest; volume 0.5 when left out): a doorbell, a beep pattern. At most 10 minutes in all.
wav_silence(path: str, seconds: float) → anyor an errorA WAV of silence for seconds (up to 600), to pad or space out other sounds.
wav_info(path: str) → anyor an errorWhat a WAV file holds: { duration, sample_rate, channels, bits, samples, peak, rms }. duration in seconds; peak and rms (average loudness) from 0 to 1, 1 being the loudest a sample can be: a peak at 1 means it clips.
wav_join(paths: list, to: str) → floator an errorWAV files joined end to end into to; they must share a sample rate, channels and sample size. The length in seconds.
Get it
$ quillo install wav # wav.qwer into this folder$ quillo install wav --example # and its example, wav.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/wav.qwer$ curl -O https://quillolang.org/plugins/files/wav.qweThen, in your program, all of it or only what you call:
use wav.qweruse wav_tone, wav_melody from wav // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
hound = "3.5"Example
// A doorbell sound for a shop's web app, made once and served, with its details.//// quillo run wav.qwe then open http://localhost:8080/doorbell.wavuse "wav.qwer"fn doorbell() = wav_melody("data/sounds/ding.wav", [ { frequency: 659.25, seconds: 0.35, volume: 0.6 }, { frequency: 0, seconds: 0.05 }, { frequency: 523.25, seconds: 0.6, volume: 0.6 },])route GET /doorbell.wav { doorbell()? file("data/sounds/ding.wav")}route GET /sounds { doorbell()? wav_tone("data/sounds/beep.wav", 880.0, 0.2, 0.5)? wav_silence("data/sounds/gap.wav", 0.5)? let seconds = wav_join(["data/sounds/beep.wav", "data/sounds/gap.wav", "data/sounds/beep.wav"], "data/sounds/beep-beep.wav")? { doorbell: wav_info("data/sounds/ding.wav")?, beep_beep_seconds: seconds }}$ quillo run wav.qwe$ curl 'localhost:8080/sounds'$ curl 'localhost:8080/doorbell.wav'Source
//! WAV sound files made and read: tones, melodies and silence for alerts and tests, joined end to end, and a file's length, peak and loudness.//! version: 1.0.0//!//! [dependencies]//! hound = "3.5"use serde_json::{json, Value};const RATE: u32 = 44_100;const MAX_SECONDS: f64 = 600.0;fn spec() -> hound::WavSpec { hound::WavSpec { channels: 1, sample_rate: RATE, bits_per_sample: 16, sample_format: hound::SampleFormat::Int }}fn writer(path: &str, spec: hound::WavSpec) -> Result<hound::WavWriter<std::io::BufWriter<std::fs::File>>, String> { if let Some(dir) = std::path::Path::new(path).parent().filter(|d| !d.as_os_str().is_empty()) { std::fs::create_dir_all(dir).map_err(|e| format!("{}: {}", dir.display(), e))?; } hound::WavWriter::create(path, spec).map_err(|e| format!("{}: {}", path, e))}// A note of `freq` Hz (0 for a rest) for `secs`, faded in and out over 5 ms so it does not click.fn note<W: std::io::Write + std::io::Seek>(w: &mut hound::WavWriter<W>, freq: f64, secs: f64, volume: f64) -> Result<u64, String> { let n = (secs.clamp(0.0, MAX_SECONDS) * RATE as f64) as u64; let fade = (0.005 * RATE as f64) as u64; let vol = volume.clamp(0.0, 1.0); for i in 0..n { let t = i as f64 / RATE as f64; let env = (i.min(n - i) as f64 / fade.max(1) as f64).min(1.0); let v = if freq > 0.0 { (t * freq * std::f64::consts::TAU).sin() * vol * env } else { 0.0 }; w.write_sample((v * i16::MAX as f64) as i16).map_err(|e| e.to_string())?; } Ok(n)}fn done<W: std::io::Write + std::io::Seek>(w: hound::WavWriter<W>, samples: u64) -> Result<Value, String> { w.finalize().map_err(|e| e.to_string())?; Ok(json!({ "duration": samples as f64 / RATE as f64, "sample_rate": RATE, "samples": samples }))}/// A WAV of one tone at `frequency` Hz (440 is the A above middle C) for `seconds` (up to 600), at `volume` 0 to 1: `{ duration, sample_rate, samples }`./// 44.1 kHz, 16-bit, mono.pub fn wav_tone(path: &str, frequency: f64, seconds: f64, volume: f64) -> Result<Value, String> { let mut w = writer(path, spec())?; let n = note(&mut w, frequency, seconds, volume)?; done(w, n)}/// A WAV of notes played one after another, each `{ frequency, seconds, volume }` (frequency 0 for a rest; volume 0.5 when left out): a doorbell, a beep pattern./// At most 10 minutes in all.pub fn wav_melody(path: &str, notes: Vec<serde_json::Value>) -> Result<Value, String> { let total: f64 = notes.iter().map(|n| n["seconds"].as_f64().unwrap_or(0.0).max(0.0)).sum(); if total > MAX_SECONDS { return Err("longer than 10 minutes".into()); } let mut w = writer(path, spec())?; let mut n = 0; for x in ¬es { n += note(&mut w, x["frequency"].as_f64().unwrap_or(0.0), x["seconds"].as_f64().unwrap_or(0.0), x["volume"].as_f64().unwrap_or(0.5))?; } done(w, n)}/// A WAV of silence for `seconds` (up to 600), to pad or space out other sounds.pub fn wav_silence(path: &str, seconds: f64) -> Result<Value, String> { let mut w = writer(path, spec())?; let n = note(&mut w, 0.0, seconds, 0.0)?; done(w, n)}/// What a WAV file holds: `{ duration, sample_rate, channels, bits, samples, peak, rms }`. `duration` in seconds; `peak` and `rms` (average loudness) from 0 to 1,/// 1 being the loudest a sample can be: a peak at 1 means it clips.pub fn wav_info(path: &str) -> Result<Value, String> { let mut r = hound::WavReader::open(path).map_err(|e| format!("{}: {}", path, e))?; let s = r.spec(); let frames = r.duration() as u64; let (mut peak, mut sum, mut count) = (0f64, 0f64, 0u64); let full = if s.sample_format == hound::SampleFormat::Float { 1.0 } else { (1u64 << (s.bits_per_sample.clamp(1, 32) - 1)) as f64 }; let mut add = |v: f64| { let v = (v / full).abs(); peak = peak.max(v); sum += v * v; count += 1; }; match s.sample_format { hound::SampleFormat::Float => for x in r.samples::<f32>() { add(x.map_err(|e| e.to_string())? as f64) }, hound::SampleFormat::Int => for x in r.samples::<i32>() { add(x.map_err(|e| e.to_string())? as f64) }, } let rms = if count > 0 { (sum / count as f64).sqrt() } else { 0.0 }; let round = |x: f64| (x * 10000.0).round() / 10000.0; Ok(json!({ "duration": round(frames as f64 / s.sample_rate as f64), "sample_rate": s.sample_rate, "channels": s.channels, "bits": s.bits_per_sample, "samples": frames, "peak": round(peak.min(1.0)), "rms": round(rms.min(1.0)), }))}/// WAV files joined end to end into `to`; they must share a sample rate, channels and sample size. The length in seconds.pub fn wav_join(paths: Vec<String>, to: &str) -> Result<f64, String> { if paths.is_empty() || paths.len() > 1000 { return Err("give 1 to 1,000 files".into()); } let first = hound::WavReader::open(&paths[0]).map_err(|e| format!("{}: {}", paths[0], e))?.spec(); let mut w = writer(to, first)?; let mut frames = 0u64; for p in &paths { let mut r = hound::WavReader::open(p).map_err(|e| format!("{}: {}", p, e))?; if r.spec() != first { return Err(format!("{}: not the same format as {} (rate, channels or bits)", p, paths[0])); } frames += r.duration() as u64; if frames as f64 / first.sample_rate as f64 > 3.0 * 3600.0 { return Err("longer than 3 hours".into()); } match first.sample_format { hound::SampleFormat::Float => for x in r.samples::<f32>() { w.write_sample(x.map_err(|e| e.to_string())?).map_err(|e| e.to_string())? }, hound::SampleFormat::Int => for x in r.samples::<i32>() { w.write_sample(x.map_err(|e| e.to_string())?).map_err(|e| e.to_string())? }, } } w.finalize().map_err(|e| e.to_string())?; Ok(frames as f64 / first.sample_rate as f64)}Related
loudness.qwer · 1.0.0Loudness of WAV audio as broadcasters measure it (EBU R128): integrated LUFS, loudness range and true peak; silences found; leading and trailing silence trimmed; and files normalised to -16, -14 or -23 LUFS under a peak limit.7 views · 9 downloadsMIDI files midi.qwer · 1.0.0MIDI files read and written: each track's notes with their names, start and length in beats and seconds, tempo and time signature, instruments by General MIDI name; songs written from lists of notes; notes named and transposed.7 views · 7 downloadsAudio info audio_info.qwer · 1.0.0An audio file's length, bitrate, sample rate, tags and cover art, for MP3, FLAC, M4A, Ogg, WAV and AIFF.6 views · 8 downloadsAudio tags audio_tags.qwer · 1.0.0Audio tags written as well as read, for MP3, FLAC, Ogg, M4A, WAV and AIFF: title, artist, album, track, year, genre, composer, lyrics, BPM and more, cover art set, and every tag stripped; written to a copy.8 views · 7 downloadsAudio waveform audio_wave.qwer · 1.0.0A WAV file's waveform for an audio player: the loudness of each slice from 0 to 1, or drawn as an SVG of bars.6 views · 7 downloadsCUE sheets cue_sheet.qwer · 1.0.0CUE sheets read and written: REM comments, CATALOG, FILE lines, tracks with their titles, performers, ISRC, flags, PREGAP and POSTGAP, and indexes in mm:ss:ff at 75 frames a second, kept as exact frames. Each track's length given the audio's total, several files included, old Latin-1 and UTF-16 sheets read, and damage or oddities named by line.2 views · 7 downloadsThread 0 posts
On WAV: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.