A => .gitignore +1 -0
A => Cargo.lock +410 -0
@@ 1,410 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203"
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "winapi",
+]
+
+[[package]]
+name = "git-testament"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "080c47ef3c243fb13474429c14dce386021cd64de731c353998a745c2fa2435b"
+dependencies = [
+ "git-testament-derive",
+ "no-std-compat",
+]
+
+[[package]]
+name = "git-testament-derive"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0803898541a48d6f0809fa681bc8d38603f727d191f179631d85ddc3b6a9a2c"
+dependencies = [
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "time",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
+[[package]]
+name = "itoa"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.112"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "matchers"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "no-std-compat"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "ryu"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
+
+[[package]]
+name = "sdl2"
+version = "0.35.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f035f8e87735fa3a8437292be49fe6056450f7cbb13c230b4bcd1bdd7279421f"
+dependencies = [
+ "bitflags",
+ "lazy_static",
+ "libc",
+ "sdl2-sys",
+]
+
+[[package]]
+name = "sdl2-sys"
+version = "0.35.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94cb479353c0603785c834e2307440d83d196bf255f204f7f6741358de8d6a2f"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "version-compare",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.132"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008"
+
+[[package]]
+name = "serde_json"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5"
+dependencies = [
+ "itoa 1.0.1",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
+
+[[package]]
+name = "soundview"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "git-testament",
+ "sdl2",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "time"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad"
+dependencies = [
+ "itoa 0.4.8",
+ "libc",
+ "time-macros",
+]
+
+[[package]]
+name = "time-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
+
+[[package]]
+name = "tracing"
+version = "0.1.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-serde"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
+dependencies = [
+ "serde",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
+dependencies = [
+ "ansi_term",
+ "chrono",
+ "lazy_static",
+ "matchers",
+ "regex",
+ "serde",
+ "serde_json",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+ "tracing-serde",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "version-compare"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
A => Cargo.toml +16 -0
@@ 1,16 @@
+[package]
+name = "soundview"
+version = "0.1.0"
+edition = "2021"
+authors = ["Nick Parker <nick@nickbp.com>"]
+license-file = "LICENCE.md"
+readme = "README-CRATES.md"
+description = "Voiceprint visualization"
+repository = "https://sr.ht/~nickbp/soundview/"
+
+[dependencies]
+anyhow = "1.0"
+git-testament = "0.2"
+sdl2 = "0.35"
+tracing = "0.1"
+tracing-subscriber = "0.2"<
\ No newline at end of file
A => LICENCE.md +52 -0
@@ 1,52 @@
+# The Fuck Around and Find Out License, version 0.2
+
+## Purpose
+
+This license gives everyone as much permission to work with
+this software as possible, while protecting contributors
+from liability, and ensuring this software is used
+ethically.
+
+## Acceptance
+
+In order to receive this license, you must agree to its
+rules. The rules of this license are both obligations
+under that agreement and conditions to your license.
+You must not do anything with this software that triggers
+a rule that you cannot or will not follow.
+
+## Copyright
+
+Each contributor licenses you to do everything with this
+software that would otherwise infringe that contributor's
+copyright in it.
+
+## Ethics
+
+This software must be used for Good, not Evil, as
+determined by the primary contributors to the software.
+
+## Excuse
+
+If anyone notifies you in writing that you have not
+complied with [Ethics](#ethics), you can keep your
+license by taking all practical steps to comply within 30
+days after the notice. If you do not do so, your license
+ends immediately.
+
+## Patent
+
+Each contributor licenses you to do everything with this
+software that would otherwise infringe any patent claims
+they can license or become able to license.
+
+## Reliability
+
+No contributor can revoke this license.
+
+## No Liability
+
+***As far as the law allows, this software comes as is,
+without any warranty or condition, and no contributor
+will be liable to anyone for any damages related to this
+software or this license, under any kind of legal claim.***
A => src/events.rs +56 -0
@@ 1,56 @@
+use anyhow::Result;
+use tracing::{debug, info};
+
+use sdl2::event::{Event, WindowEvent};
+use sdl2::keyboard::{Keycode, Mod};
+use sdl2::EventPump;
+
+use crate::recorder::Recorder;
+
+pub fn events_loop(mut event_pump: EventPump, recorder: Recorder) -> Result<()> {
+ loop {
+ let event = event_pump.wait_event_timeout(100);
+ if let Some(event) = event {
+ match event {
+ Event::Quit {..} |
+ // Esc, Q, Ctrl+C, Alt+F4: Quit
+ Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
+ return Ok(());
+ },
+ Event::KeyDown { keycode: Some(Keycode::Q), .. } => {
+ return Ok(());
+ },
+ Event::KeyDown { keymod: Mod::LCTRLMOD | Mod::RCTRLMOD, keycode: Some(Keycode::C), .. } => {
+ return Ok(());
+ },
+ Event::KeyDown { keymod: Mod::LALTMOD | Mod::RALTMOD, keycode: Some(Keycode::F4), .. } => {
+ return Ok(());
+ },
+
+ // R, Space: Rotate
+ Event::KeyDown { keycode: Some(Keycode::R) | Some(Keycode::Space), .. } => {
+ info!("rotate");
+ },
+
+ // A, D, I: Switch Audio Device Input
+ Event::KeyDown { keycode: Some(Keycode::A) | Some(Keycode::D) | Some(Keycode::I), .. } => {
+ info!("next device");
+ },
+
+ Event::Window {win_event: WindowEvent::SizeChanged(x, y), ..} | Event::Window {win_event: WindowEvent::Resized(x,y), ..} => {
+ info!("resize: x={}, y={}", x, y);
+ },
+
+ Event::AudioDeviceAdded {iscapture: true, which, ..} => {
+ recorder.device_added(which)?;
+ },
+ Event::AudioDeviceRemoved {iscapture: true, which: idx, ..} => {
+ info!("capture device removed: idx={}", idx);
+ },
+ _ => {
+ debug!("ignored event: {:?}", event);
+ }
+ }
+ }
+ }
+}
A => src/hsl.rs +81 -0
@@ 1,81 @@
+use sdl2::pixels::Color;
+
+fn hue_to_rgbval_with_p0(q: f64, mut t: f64) -> f64 {
+ if t < 0.0 {
+ t += 1.;
+ }
+ if t < 1./6. {
+ q * 6. * t
+ } else if t < 1./2. {
+ q
+ } else if t < 2./3. {
+ q * (2. / 3. - t) * 6.
+ } else {
+ 0.
+ }
+}
+
+fn hue_to_rgbval_with_q1(p: f64, mut t: f64) -> f64 {
+ if t < 0. {
+ t += 1.;
+ }
+ if t < 1./6. {
+ p + ((1. - p) * 6. * t)
+ } else if t < 1./2. {
+ 1.
+ } else if t < 2./3. {
+ p + ((1. - p) * (2. / 3. - t) * 6.)
+ } else {
+ p
+ }
+}
+
+fn value_to_color(max_lum: f64, lum_exponent: f64, value: f64) -> Color {
+ let lum_calc = f64::powf(value, lum_exponent);
+ let mut lum = if max_lum > lum_calc { lum_calc } else { max_lum };
+
+ let hue = 1. / 3. * (1. - value);
+ lum *= 2.;
+ if lum < 1. {
+ Color::RGB(
+ (hue_to_rgbval_with_p0(lum, hue + 1. / 3.) * 255.) as u8,
+ (hue_to_rgbval_with_p0(lum, hue) * 255.) as u8,
+ (hue_to_rgbval_with_p0(lum, hue - 1. / 3.) * 255.) as u8
+ )
+ } else {
+ lum -= 1.;
+ Color::RGB(
+ (hue_to_rgbval_with_q1(lum, hue + 1. / 3.) * 255.) as u8,
+ (hue_to_rgbval_with_q1(lum, hue) * 255.) as u8,
+ (hue_to_rgbval_with_q1(lum, hue - 1. / 3.) * 255.) as u8
+ )
+ }
+}
+
+pub struct HSL {
+ precached_vals: Vec<Color>,
+}
+
+impl HSL {
+ pub fn new(color_max_lum: usize, color_lum_exaggeration: usize) -> HSL {
+ let cache_size = 1024;
+ let max_lum = color_max_lum as f64 / 100.;
+ let lum_exponent = 1 as f64 - (color_lum_exaggeration as f64 / 100.);
+ let mut precached_vals = Vec::new();
+ for i in 0..cache_size {
+ precached_vals.push(value_to_color(max_lum, lum_exponent, i as f64 / cache_size as f64));
+ }
+ HSL {
+ precached_vals
+ }
+ }
+
+ pub fn value_to_color(self: &HSL, value: f64) -> Color {
+ let max_index: usize = self.precached_vals.len() - 1;
+ let mut index: usize = (value * self.precached_vals.len() as f64) as usize;
+ if index > max_index {
+ index = max_index;
+ }
+ return self.precached_vals[index];
+ }
+}
A => src/lib.rs +6 -0
@@ 1,6 @@
+pub mod events;
+pub mod hsl;
+/// Utilities relating to setting up log output.
+pub mod logging;
+pub mod recorder;
+pub mod render;
A => src/logging.rs +16 -0
@@ 1,16 @@
+use tracing;
+use tracing_subscriber::EnvFilter;
+
+pub fn init_logging() {
+ let filter_layer = EnvFilter::try_from_env("LOG_LEVEL")
+ .or_else(|_| EnvFilter::try_new("info"))
+ .expect("Failed to initialize filter layer");
+
+ tracing::subscriber::set_global_default(
+ tracing_subscriber::fmt()
+ .with_writer(std::io::stderr)
+ .with_env_filter(filter_layer)
+ .finish()
+ )
+ .expect("Failed to set default subscriber");
+}
A => src/main.rs +60 -0
@@ 1,60 @@
+use anyhow::{anyhow, Result};
+use git_testament::{git_testament, render_testament};
+use tracing::{debug, info, warn};
+
+use soundview::{events, logging, recorder, render};
+
+git_testament!(TESTAMENT);
+
+fn set_hint(name: &str) {
+ let before = sdl2::hint::get(name);
+ sdl2::hint::set(name, "1");
+ debug!("{}: {:?} => {:?}", name, before, sdl2::hint::get(name));
+}
+
+fn main() -> Result<()> {
+ logging::init_logging();
+ let sdl_version = sdl2::version::version();
+ info!("Soundview version {}, SDL {}.{}.{}", render_testament!(TESTAMENT), sdl_version.major, sdl_version.minor, sdl_version.patch);
+ if cfg!(unix) {
+ // 2.0.16 adds support for SDL_AUDIO_INCLUDE_MONITORS
+ if sdl_version.minor == 0 && sdl_version.patch < 16 {
+ warn!("SDL 2.0.16 or greater is required to visualize playing audio with PulseAudio");
+ }
+ }
+
+ // Enables PulseAudio "monitor" devices in the list.
+ // These are what allow visualizing audio that's playing on the system.
+ // Without this we can only visualize the mic input.
+ // This option only works on 2.0.16 or newer.
+ set_hint("SDL_AUDIO_INCLUDE_MONITORS");
+
+ let sdl_context = sdl2::init().map_err(|e| anyhow!(e))?;
+
+ let video_subsystem = sdl_context.video().map_err(|e| anyhow!(e))?;
+ if video_subsystem.is_screen_saver_enabled() {
+ debug!("disabling screensaver");
+ video_subsystem.disable_screen_saver();
+ }
+ let window = video_subsystem.window("soundview", 800, 600)
+ .resizable()
+ .build()
+ .map_err(|e| anyhow!(e))?;
+
+ // TODO run events handling and rendering in separate threads?
+ render::render_loop(
+ window.into_canvas()
+ .accelerated()
+ .present_vsync()
+ .build()
+ .map_err(|e| anyhow!(e))?
+ )?;
+
+ // TODO clean teardown after events_loop exits
+ events::events_loop(
+ sdl_context.event_pump().map_err(|e| anyhow!(e))?,
+ recorder::Recorder::new(sdl_context.audio().map_err(|e| anyhow!(e))?)
+ )?;
+
+ Ok(())
+}
A => src/recorder.rs +67 -0
@@ 1,67 @@
+use anyhow::{anyhow, Result};
+use tracing::info;
+
+use sdl2::audio::{AudioCallback, AudioSpecDesired};
+use sdl2::AudioSubsystem;
+
+struct Handler {
+}
+
+impl AudioCallback for Handler {
+ type Channel = f32;
+
+ fn callback(&mut self, out: &mut [f32]) {
+ let mut val: f32 = 0.;
+ for x in out.iter() {
+ if *x < 0. {
+ val += -x;
+ } else {
+ val += x;
+ }
+ }
+ info!("callback {} {:?}", out.len(), val);
+ }
+}
+
+pub struct Recorder {
+ audio_subsystem: AudioSubsystem,
+}
+
+impl Recorder {
+ pub fn new(audio_subsystem: AudioSubsystem) -> Recorder {
+ Recorder {
+ audio_subsystem
+ }
+ }
+
+ pub fn device_added(self: &Recorder, which: u32) -> Result<()> {
+ let name = self.audio_subsystem.audio_capture_device_name(which).map_err(|e| anyhow!(e))?;
+ info!("capture device added: idx={} name={:?}", which, name);
+ if name != "Monitor of Built-in Audio Analog Stereo" {
+ // ignore
+ return Ok(());
+ }
+ info!("setting up capture");
+ self.record(name.as_str())
+ }
+
+ fn record(self: &Recorder, device_name: &str) -> Result<()> {
+ // TODO events arent logged, this probably needs to be in different thread
+ let desired_spec = AudioSpecDesired {
+ freq: Some(44100),
+ channels: Some(1), // mono
+ samples: None // default sample size, power of 2
+ };
+
+ let rec_dev = self.audio_subsystem.open_capture(device_name, &desired_spec, |_actual_spec| {
+ // initialize the audio callback
+ Handler {
+ }
+ }).map_err(|e| anyhow!(e))?;
+
+ // Start playback
+ rec_dev.resume();
+
+ Ok(())
+ }
+}
A => src/render.rs +21 -0
@@ 1,21 @@
+use anyhow::Result;
+
+use sdl2::pixels::Color;
+use sdl2::render::Canvas;
+use sdl2::video::Window;
+use std::time::Duration;
+
+pub fn render_loop(mut canvas: Canvas<Window>) -> Result<()> {
+ let mut i = 0;
+
+ loop {
+ i = (i + 1) % 255;
+ canvas.set_draw_color(Color::RGB(i, 64, 255 - i));
+ canvas.clear();
+
+ canvas.present();
+ ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
+ }
+
+ Ok(())
+}