Media

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

#audio#sounds#files#alerts

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 error

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.

wav_melody(path: str, notes: list) → anyor an error

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.

wav_silence(path: str, seconds: float) → anyor an error

A WAV of silence for seconds (up to 600), to pad or space out other sounds.

wav_info(path: str) → anyor an error

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.

wav_join(paths: list, to: str) → floator an error

WAV 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.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/wav.qwer$ curl -O https://quillolang.org/plugins/files/wav.qwe

Then, in your program, all of it or only what you call:

main.qwe
use wav.qweruse wav_tone, wav_melody from wav      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
hound = "3.5"

Example

wav.qwe
// 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.qwer
//! 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 &notes {        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)}
Loudness 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.audiopodcastssoundsfiles7 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.audiofilessoundsgames7 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.audiofilesuploadspodcasts6 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.audiofilespodcastsprivacy8 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.audioplayerssvg6 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.audiocdtrackschapters2 views · 7 downloads
Thread
77Useful? Like it, or add to its thread below.

Thread 0 posts

On WAV: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.