From cbb33ad24da48abc9dd6cfe92aef660f18d02097 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Sat, 27 Jun 2020 18:29:55 +0200 Subject: [PATCH 01/61] Add json config for websocket, update test utils --- Cargo.toml | 1 + src/socketserver.rs | 41 ++++++- testscripts/makesineraw.py | 3 +- testscripts/test_file_sine.yml | 41 +++++++ web/camilla.html | 191 +++++++++++++++++++++++++++++++++ 5 files changed, 272 insertions(+), 5 deletions(-) create mode 100644 testscripts/test_file_sine.yml create mode 100644 web/camilla.html diff --git a/Cargo.toml b/Cargo.toml index ca0cb918..1ab7439e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ path = "src/bin.rs" alsa = { version = "0.4", optional = true } serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.8" +serde_json = "1.0" serde_with = "1.4.0" realfft = "0.2.0" fftw = { version = "0.6.2", optional = true } diff --git a/src/socketserver.rs b/src/socketserver.rs index f0588b37..7e5a8631 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -8,8 +8,10 @@ use config; enum WSCommand { SetConfigName(String), SetConfig(String), + SetConfigJson(String), Reload, GetConfig, + GetConfigJson, GetConfigName, Exit, Stop, @@ -26,6 +28,7 @@ fn parse_command(cmd: &ws::Message) -> WSCommand { match cmdarg[0] { "reload" => WSCommand::Reload, "getconfig" => WSCommand::GetConfig, + "getconfigjson" => WSCommand::GetConfigJson, "getconfigname" => WSCommand::GetConfigName, "exit" => WSCommand::Exit, "stop" => WSCommand::Stop, @@ -43,6 +46,13 @@ fn parse_command(cmd: &ws::Message) -> WSCommand { WSCommand::Invalid } } + "setconfigjson" => { + if cmdarg.len() == 2 { + WSCommand::SetConfigJson(cmdarg[1].to_string()) + } else { + WSCommand::Invalid + } + } _ => WSCommand::Invalid, } } else { @@ -76,18 +86,24 @@ pub fn start_server( } WSCommand::GetConfig => { //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); - socket.send( + socket.send(format!("OK:GETCONFIG:{}", serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(), - ) + )) + } + WSCommand::GetConfigJson => { + //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); + socket.send(format!("OK:GETCONFIGJSON:{}", + serde_json::to_string(&*active_config_inst.lock().unwrap()).unwrap(), + )) } WSCommand::GetConfigName => socket.send( - active_config_path_inst + format!("OK:GETCONFIGNAME:{}", active_config_path_inst .lock() .unwrap() .as_ref() .unwrap_or(&"NONE".to_string()) .to_string(), - ), + )), WSCommand::SetConfigName(path) => match config::load_validate_config(&path) { Ok(_) => { *active_config_path_inst.lock().unwrap() = Some(path.clone()); @@ -112,6 +128,23 @@ pub fn start_server( } } } + WSCommand::SetConfigJson(config_json) => { + match serde_json::from_str::(&config_json) { + Ok(conf) => match config::validate_config(conf.clone()) { + Ok(()) => { + //*active_config_path_inst.lock().unwrap() = String::from("none"); + *new_config_inst.lock().unwrap() = Some(conf); + signal_reload_inst.store(true, Ordering::Relaxed); + socket.send("OK:SETCONFIGJSON") + } + _ => socket.send("ERROR:SETCONFIGJSON"), + }, + Err(error) => { + error!("Config error: {}", error); + socket.send("ERROR:SETCONFIGJSON") + } + } + } WSCommand::Stop => { *new_config_inst.lock().unwrap() = None; signal_exit_inst.store(2, Ordering::Relaxed); diff --git a/testscripts/makesineraw.py b/testscripts/makesineraw.py index ca271923..0d9bd932 100644 --- a/testscripts/makesineraw.py +++ b/testscripts/makesineraw.py @@ -3,6 +3,7 @@ import sys f = float(sys.argv[2]) fs = float(sys.argv[1]) +length = int(sys.argv[3]) t = np.linspace(0, 20, num=int(20*fs), endpoint=False) wave = 0.5*np.sin(f*2*np.pi*t) wave= np.reshape(wave,(-1,1)) @@ -10,7 +11,7 @@ wave64 = wave.astype('float64') -name = "sine_{:.0f}_{:.0f}_f64_2ch.raw".format(f, fs) +name = "sine_{:.0f}_{:.0f}_{}s_f64_2ch.raw".format(f, fs, length) #print(wave64) wave64.tofile(name) diff --git a/testscripts/test_file_sine.yml b/testscripts/test_file_sine.yml new file mode 100644 index 00000000..97cf0405 --- /dev/null +++ b/testscripts/test_file_sine.yml @@ -0,0 +1,41 @@ +--- +devices: + samplerate: 44100 + chunksize: 1024 + playback: + type: File + channels: 2 + filename: "result_f64.raw" + format: FLOAT64LE + capture: + type: File + channels: 2 + filename: "sine_1000_44100_20s_f64_2ch.raw" + format: FLOAT64LE + extra_samples: 0 + + + + +filters: + dummy: + type: Conv + parameters: + type: File + filename: testscripts/spike_f64_65k.raw + format: FLOAT64LE + + + +pipeline: + - type: Filter + channel: 0 + names: + - dummy + - type: Filter + channel: 1 + names: + - dummy + + + diff --git a/web/camilla.html b/web/camilla.html new file mode 100644 index 00000000..33ca5731 --- /dev/null +++ b/web/camilla.html @@ -0,0 +1,191 @@ + + + +CamillaDSP config + + + +
Configuration +
+
+ + + + + +

+ Configuration file +
+
+ + + + +

+ Command response +
+
+ + + + + + From 51caf7965ea9d28958b68e3347a20add04f97b18 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Sat, 27 Jun 2020 18:35:59 +0200 Subject: [PATCH 02/61] Update websocket readme --- websocket.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/websocket.md b/websocket.md index cd126f48..0174da14 100644 --- a/websocket.md +++ b/websocket.md @@ -6,9 +6,11 @@ If additionally the "wait" flag is given, it will wait for a config to be upload The available commands are: - `getconfig` : read the current configuration as yaml - * response is the config in yaml format. + * response is `OK:GETCONFIG:(yamldata)` where yamldata is the config in yaml format. +- `getconfigjson` : read the current configuration as json + * response is `OK:GETCONFIG:(jsondata)` where yamldata is the config in JSON format. - `getconfigname` : get name and path of current config file - * response is `OK:/path/to/current.yml` + * response is `OK:GETCONFIGNAME:/path/to/current.yml` - `reload` : reload current config file (same as SIGHUP) * response is `OK:RELOAD` or `ERROR:RELOAD` - `stop` : stop processing and wait for a new config to be uploaded with `setconfig` @@ -17,6 +19,8 @@ The available commands are: * response is `OK:/path/to/file.yml` or `ERROR:/path/to/file.yml` - `setconfig:` : provide a new config as a yaml string. Applied directly. * response is `OK:SETCONFIG` or `ERROR:SETCONFIG` +- `setconfigjson:` : provide a new config as a JSON string. Applied directly. + * response is `OK:SETCONFIGJSON` or `ERROR:SETCONFIGJSON` ## Controlling from Python @@ -39,7 +43,7 @@ In [3]: ws.send("getconfigname") Out[3]: 19 In [4]: print(ws.recv()) -/path/to/someconfig.yml +OK:GETCONFIGNAME:/path/to/someconfig.yml ``` ### Switch to a different config file @@ -49,7 +53,7 @@ In [5]: ws.send("setconfigname:/path/to/otherconfig.yml") Out[5]: 52 In [6]: print(ws.recv()) -OK:/path/to/otherconfig.yml +OK:SETCONFIGNAME:/path/to/otherconfig.yml In [7]: ws.send("reload") Out[7]: 12 @@ -65,7 +69,7 @@ In [9]: ws.send("getconfig") Out[9]: 15 In [10]: print(ws.recv()) ---- +OK:GETCONFIG:--- devices: samplerate: 44100 buffersize: 1024 From 516185da8bfc9891ec820746240e39896e753b23 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Sat, 27 Jun 2020 18:36:35 +0200 Subject: [PATCH 03/61] Format --- src/socketserver.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/socketserver.rs b/src/socketserver.rs index 7e5a8631..b7674b85 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -86,18 +86,21 @@ pub fn start_server( } WSCommand::GetConfig => { //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); - socket.send(format!("OK:GETCONFIG:{}", + socket.send(format!( + "OK:GETCONFIG:{}", serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(), )) } WSCommand::GetConfigJson => { //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); - socket.send(format!("OK:GETCONFIGJSON:{}", + socket.send(format!( + "OK:GETCONFIGJSON:{}", serde_json::to_string(&*active_config_inst.lock().unwrap()).unwrap(), )) } - WSCommand::GetConfigName => socket.send( - format!("OK:GETCONFIGNAME:{}", active_config_path_inst + WSCommand::GetConfigName => socket.send(format!( + "OK:GETCONFIGNAME:{}", + active_config_path_inst .lock() .unwrap() .as_ref() From bf5d09351539b389b7ec57ea51f6751aa65e0a04 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Sat, 27 Jun 2020 22:42:11 +0200 Subject: [PATCH 04/61] Update html --- web/camilla.html | 303 +++++++++++++++++++++++------------------------ 1 file changed, 148 insertions(+), 155 deletions(-) diff --git a/web/camilla.html b/web/camilla.html index 33ca5731..bb9fb69d 100644 --- a/web/camilla.html +++ b/web/camilla.html @@ -1,191 +1,184 @@ + + CamillaDSP config + +
Configuration -
+
- - - + + + - +

Configuration file -
+
- - - + +

Command response -
+
+ + socket.onmessage = function (event) { + console.log(event.data) + var parts = split(event.data, ":", 3); + var status = parts[0]; + var cmd = parts[1]; + var data = parts[2]; + console.log(parts) + if (status == "OK") { + if (cmd == "GETCONFIG") { + var div = document.getElementById('configeditor'); + div.innerText = data; + } + else if (cmd == "GETCONFIGNAME") { + var div = document.getElementById('configpath'); + div.innerText = data; + } + else { + var div = document.getElementById('response'); + div.innerText = event.data; + } + } + else { + var div = document.getElementById('response'); + div.innerText = event.data; + } + }; + + socket.onclose = function (event) { + if (event.wasClean) { + alert(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`); + } else { + alert('[close] Connection died'); + } + }; + + socket.onerror = function (error) { + alert(`[error] ${error.message}`); + }; + \ No newline at end of file From 7ac0d86ae8d429f8ed2e95380a71def6b06f69bc Mon Sep 17 00:00:00 2001 From: HEnquist Date: Sat, 27 Jun 2020 18:29:55 +0200 Subject: [PATCH 05/61] Add json config for websocket, update test utils --- Cargo.toml | 1 + src/socketserver.rs | 41 ++++++- testscripts/makesineraw.py | 3 +- testscripts/test_file_sine.yml | 41 +++++++ web/camilla.html | 191 +++++++++++++++++++++++++++++++++ 5 files changed, 272 insertions(+), 5 deletions(-) create mode 100644 testscripts/test_file_sine.yml create mode 100644 web/camilla.html diff --git a/Cargo.toml b/Cargo.toml index a7a73852..3aa41666 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ alsa = { version = "0.4", optional = true } [dependencies] serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.8" +serde_json = "1.0" serde_with = "1.4.0" realfft = "0.2.0" fftw = { version = "0.6.2", optional = true } diff --git a/src/socketserver.rs b/src/socketserver.rs index f0588b37..7e5a8631 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -8,8 +8,10 @@ use config; enum WSCommand { SetConfigName(String), SetConfig(String), + SetConfigJson(String), Reload, GetConfig, + GetConfigJson, GetConfigName, Exit, Stop, @@ -26,6 +28,7 @@ fn parse_command(cmd: &ws::Message) -> WSCommand { match cmdarg[0] { "reload" => WSCommand::Reload, "getconfig" => WSCommand::GetConfig, + "getconfigjson" => WSCommand::GetConfigJson, "getconfigname" => WSCommand::GetConfigName, "exit" => WSCommand::Exit, "stop" => WSCommand::Stop, @@ -43,6 +46,13 @@ fn parse_command(cmd: &ws::Message) -> WSCommand { WSCommand::Invalid } } + "setconfigjson" => { + if cmdarg.len() == 2 { + WSCommand::SetConfigJson(cmdarg[1].to_string()) + } else { + WSCommand::Invalid + } + } _ => WSCommand::Invalid, } } else { @@ -76,18 +86,24 @@ pub fn start_server( } WSCommand::GetConfig => { //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); - socket.send( + socket.send(format!("OK:GETCONFIG:{}", serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(), - ) + )) + } + WSCommand::GetConfigJson => { + //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); + socket.send(format!("OK:GETCONFIGJSON:{}", + serde_json::to_string(&*active_config_inst.lock().unwrap()).unwrap(), + )) } WSCommand::GetConfigName => socket.send( - active_config_path_inst + format!("OK:GETCONFIGNAME:{}", active_config_path_inst .lock() .unwrap() .as_ref() .unwrap_or(&"NONE".to_string()) .to_string(), - ), + )), WSCommand::SetConfigName(path) => match config::load_validate_config(&path) { Ok(_) => { *active_config_path_inst.lock().unwrap() = Some(path.clone()); @@ -112,6 +128,23 @@ pub fn start_server( } } } + WSCommand::SetConfigJson(config_json) => { + match serde_json::from_str::(&config_json) { + Ok(conf) => match config::validate_config(conf.clone()) { + Ok(()) => { + //*active_config_path_inst.lock().unwrap() = String::from("none"); + *new_config_inst.lock().unwrap() = Some(conf); + signal_reload_inst.store(true, Ordering::Relaxed); + socket.send("OK:SETCONFIGJSON") + } + _ => socket.send("ERROR:SETCONFIGJSON"), + }, + Err(error) => { + error!("Config error: {}", error); + socket.send("ERROR:SETCONFIGJSON") + } + } + } WSCommand::Stop => { *new_config_inst.lock().unwrap() = None; signal_exit_inst.store(2, Ordering::Relaxed); diff --git a/testscripts/makesineraw.py b/testscripts/makesineraw.py index ca271923..0d9bd932 100644 --- a/testscripts/makesineraw.py +++ b/testscripts/makesineraw.py @@ -3,6 +3,7 @@ import sys f = float(sys.argv[2]) fs = float(sys.argv[1]) +length = int(sys.argv[3]) t = np.linspace(0, 20, num=int(20*fs), endpoint=False) wave = 0.5*np.sin(f*2*np.pi*t) wave= np.reshape(wave,(-1,1)) @@ -10,7 +11,7 @@ wave64 = wave.astype('float64') -name = "sine_{:.0f}_{:.0f}_f64_2ch.raw".format(f, fs) +name = "sine_{:.0f}_{:.0f}_{}s_f64_2ch.raw".format(f, fs, length) #print(wave64) wave64.tofile(name) diff --git a/testscripts/test_file_sine.yml b/testscripts/test_file_sine.yml new file mode 100644 index 00000000..97cf0405 --- /dev/null +++ b/testscripts/test_file_sine.yml @@ -0,0 +1,41 @@ +--- +devices: + samplerate: 44100 + chunksize: 1024 + playback: + type: File + channels: 2 + filename: "result_f64.raw" + format: FLOAT64LE + capture: + type: File + channels: 2 + filename: "sine_1000_44100_20s_f64_2ch.raw" + format: FLOAT64LE + extra_samples: 0 + + + + +filters: + dummy: + type: Conv + parameters: + type: File + filename: testscripts/spike_f64_65k.raw + format: FLOAT64LE + + + +pipeline: + - type: Filter + channel: 0 + names: + - dummy + - type: Filter + channel: 1 + names: + - dummy + + + diff --git a/web/camilla.html b/web/camilla.html new file mode 100644 index 00000000..33ca5731 --- /dev/null +++ b/web/camilla.html @@ -0,0 +1,191 @@ + + + +CamillaDSP config + + + +
Configuration +
+
+ + + + + +

+ Configuration file +
+
+ + + + +

+ Command response +
+
+ + + + + + From a044ed1a07e6411a884d777dd0de827c230759e2 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Sat, 27 Jun 2020 18:35:59 +0200 Subject: [PATCH 06/61] Update websocket readme --- websocket.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/websocket.md b/websocket.md index cd126f48..0174da14 100644 --- a/websocket.md +++ b/websocket.md @@ -6,9 +6,11 @@ If additionally the "wait" flag is given, it will wait for a config to be upload The available commands are: - `getconfig` : read the current configuration as yaml - * response is the config in yaml format. + * response is `OK:GETCONFIG:(yamldata)` where yamldata is the config in yaml format. +- `getconfigjson` : read the current configuration as json + * response is `OK:GETCONFIG:(jsondata)` where yamldata is the config in JSON format. - `getconfigname` : get name and path of current config file - * response is `OK:/path/to/current.yml` + * response is `OK:GETCONFIGNAME:/path/to/current.yml` - `reload` : reload current config file (same as SIGHUP) * response is `OK:RELOAD` or `ERROR:RELOAD` - `stop` : stop processing and wait for a new config to be uploaded with `setconfig` @@ -17,6 +19,8 @@ The available commands are: * response is `OK:/path/to/file.yml` or `ERROR:/path/to/file.yml` - `setconfig:` : provide a new config as a yaml string. Applied directly. * response is `OK:SETCONFIG` or `ERROR:SETCONFIG` +- `setconfigjson:` : provide a new config as a JSON string. Applied directly. + * response is `OK:SETCONFIGJSON` or `ERROR:SETCONFIGJSON` ## Controlling from Python @@ -39,7 +43,7 @@ In [3]: ws.send("getconfigname") Out[3]: 19 In [4]: print(ws.recv()) -/path/to/someconfig.yml +OK:GETCONFIGNAME:/path/to/someconfig.yml ``` ### Switch to a different config file @@ -49,7 +53,7 @@ In [5]: ws.send("setconfigname:/path/to/otherconfig.yml") Out[5]: 52 In [6]: print(ws.recv()) -OK:/path/to/otherconfig.yml +OK:SETCONFIGNAME:/path/to/otherconfig.yml In [7]: ws.send("reload") Out[7]: 12 @@ -65,7 +69,7 @@ In [9]: ws.send("getconfig") Out[9]: 15 In [10]: print(ws.recv()) ---- +OK:GETCONFIG:--- devices: samplerate: 44100 buffersize: 1024 From 36f5ee2e12e9d2d39c2772bac57851837b26fd16 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Sat, 27 Jun 2020 18:36:35 +0200 Subject: [PATCH 07/61] Format --- src/socketserver.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/socketserver.rs b/src/socketserver.rs index 7e5a8631..b7674b85 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -86,18 +86,21 @@ pub fn start_server( } WSCommand::GetConfig => { //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); - socket.send(format!("OK:GETCONFIG:{}", + socket.send(format!( + "OK:GETCONFIG:{}", serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(), )) } WSCommand::GetConfigJson => { //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); - socket.send(format!("OK:GETCONFIGJSON:{}", + socket.send(format!( + "OK:GETCONFIGJSON:{}", serde_json::to_string(&*active_config_inst.lock().unwrap()).unwrap(), )) } - WSCommand::GetConfigName => socket.send( - format!("OK:GETCONFIGNAME:{}", active_config_path_inst + WSCommand::GetConfigName => socket.send(format!( + "OK:GETCONFIGNAME:{}", + active_config_path_inst .lock() .unwrap() .as_ref() From ea2fd2455939339903b71b6cca167b0e3a69fc8d Mon Sep 17 00:00:00 2001 From: HEnquist Date: Sat, 27 Jun 2020 22:42:11 +0200 Subject: [PATCH 08/61] Update html --- web/camilla.html | 303 +++++++++++++++++++++++------------------------ 1 file changed, 148 insertions(+), 155 deletions(-) diff --git a/web/camilla.html b/web/camilla.html index 33ca5731..bb9fb69d 100644 --- a/web/camilla.html +++ b/web/camilla.html @@ -1,191 +1,184 @@ + + CamillaDSP config + +
Configuration -
+
- - - + + + - +

Configuration file -
+
- - - + +

Command response -
+
+ + socket.onmessage = function (event) { + console.log(event.data) + var parts = split(event.data, ":", 3); + var status = parts[0]; + var cmd = parts[1]; + var data = parts[2]; + console.log(parts) + if (status == "OK") { + if (cmd == "GETCONFIG") { + var div = document.getElementById('configeditor'); + div.innerText = data; + } + else if (cmd == "GETCONFIGNAME") { + var div = document.getElementById('configpath'); + div.innerText = data; + } + else { + var div = document.getElementById('response'); + div.innerText = event.data; + } + } + else { + var div = document.getElementById('response'); + div.innerText = event.data; + } + }; + + socket.onclose = function (event) { + if (event.wasClean) { + alert(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`); + } else { + alert('[close] Connection died'); + } + }; + + socket.onerror = function (error) { + alert(`[error] ${error.message}`); + }; + \ No newline at end of file From 748d0ac1c5a38c059a346031fb3379444ae2850a Mon Sep 17 00:00:00 2001 From: HEnquist Date: Mon, 6 Jul 2020 22:00:28 +0200 Subject: [PATCH 09/61] Add measurement of capture rate --- src/alsadevice.rs | 22 ++++++++++++++++++++++ src/audiodevice.rs | 2 ++ src/bin.rs | 6 +++++- src/cpaldevice.rs | 21 ++++++++++++++++++++- src/filedevice.rs | 23 +++++++++++++++++++++++ src/pulsedevice.rs | 24 +++++++++++++++++++++++- src/socketserver.rs | 10 ++++++++++ websocket.md | 2 ++ 8 files changed, 107 insertions(+), 3 deletions(-) diff --git a/src/alsadevice.rs b/src/alsadevice.rs index 481d17d7..94d9a664 100644 --- a/src/alsadevice.rs +++ b/src/alsadevice.rs @@ -15,6 +15,7 @@ use rubato::Resampler; use std::ffi::CString; use std::sync::mpsc; use std::sync::{Arc, Barrier}; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; use std::time::{Duration, SystemTime}; @@ -76,6 +77,7 @@ struct CaptureParams { samplerate: usize, capture_samplerate: usize, async_src: bool, + measured_rate: Arc, } struct PlaybackParams { @@ -294,6 +296,9 @@ fn capture_loop_bytes( } } let mut capture_bytes = params.chunksize * params.channels * params.bytes_per_sample; + let mut start = SystemTime::now(); + let mut now; + let mut bytes_counter = 0; loop { match channels.command.try_recv() { Ok(CommandMessage::Exit) => { @@ -323,6 +328,21 @@ fn capture_loop_bytes( match capture_res { Ok(_) => { trace!("Captured {} bytes", capture_bytes); + now = SystemTime::now(); + bytes_counter += capture_bytes; + if now.duration_since(start).unwrap().as_millis() > 1000 + { + let meas_time = now.duration_since(start).unwrap().as_secs_f32(); + let bytes_per_sec = bytes_counter as f32 / meas_time; + let measured_rate_f = bytes_per_sec / (params.channels * params.bytes_per_sample) as f32; + trace!( + "Measured sample rate is {} Hz", + measured_rate_f + ); + params.measured_rate.store(measured_rate_f as usize, Ordering::Relaxed); + start = now; + bytes_counter = 0; + } } Err(msg) => { channels @@ -498,6 +518,7 @@ impl CaptureDevice for AlsaCaptureDevice { barrier: Arc, status_channel: mpsc::Sender, command_channel: mpsc::Receiver, + measured_rate: Arc, ) -> Res>> { let devname = self.devname.clone(); let samplerate = self.samplerate; @@ -583,6 +604,7 @@ impl CaptureDevice for AlsaCaptureDevice { samplerate, capture_samplerate, async_src, + measured_rate, }; let cap_channels = CaptureChannels { audio: channel, diff --git a/src/audiodevice.rs b/src/audiodevice.rs index 73e26141..0751ec27 100644 --- a/src/audiodevice.rs +++ b/src/audiodevice.rs @@ -14,6 +14,7 @@ use rubato::{ }; use std::sync::mpsc; use std::sync::{Arc, Barrier}; +use std::sync::atomic::AtomicUsize; use std::thread; use std::time::Instant; @@ -97,6 +98,7 @@ pub trait CaptureDevice { barrier: Arc, status_channel: mpsc::Sender, command_channel: mpsc::Receiver, + measured_rate: Arc, ) -> Res>>; } diff --git a/src/bin.rs b/src/bin.rs index 68279849..110d6830 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -104,6 +104,7 @@ fn run( active_config_shared: Arc>>, config_path: Arc>>, new_config_shared: Arc>>, + measured_rate: Arc, ) -> Res { let conf = match new_config_shared.lock().unwrap().clone() { Some(cfg) => cfg, @@ -147,7 +148,7 @@ fn run( // Capture thread let mut capture_dev = audiodevice::get_capture_device(conf_cap.devices); let cap_handle = capture_dev - .start(tx_cap, barrier_cap, tx_status_cap, rx_command_cap) + .start(tx_cap, barrier_cap, tx_status_cap, rx_command_cap, measured_rate) .unwrap(); let delay = time::Duration::from_millis(100); @@ -386,6 +387,7 @@ fn main() { let signal_reload = Arc::new(AtomicBool::new(false)); let signal_exit = Arc::new(AtomicUsize::new(0)); + let measured_rate = Arc::new(AtomicUsize::new(0)); //let active_config = Arc::new(Mutex::new(String::new())); let active_config = Arc::new(Mutex::new(None)); let new_config = Arc::new(Mutex::new(configuration)); @@ -403,6 +405,7 @@ fn main() { active_config.clone(), active_config_path.clone(), new_config.clone(), + measured_rate.clone() ); } } @@ -421,6 +424,7 @@ fn main() { active_config.clone(), active_config_path.clone(), new_config.clone(), + measured_rate.clone(), ); match exitstatus { Err(e) => { diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index c63b6ebf..ca90c06f 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -302,7 +302,7 @@ impl PlaybackDevice for CpalPlaybackDevice { match channel.recv() { Ok(AudioMessage::Audio(chunk)) => { now = SystemTime::now(); - delay += buffer_fill.load(Ordering::Relaxed) as isize; + delay += (buffer_fill.load(Ordering::Relaxed)/channels_clone) as isize; ndelays += 1; if adjust && (now.duration_since(start).unwrap().as_millis() @@ -384,6 +384,7 @@ impl CaptureDevice for CpalCaptureDevice { barrier: Arc, status_channel: mpsc::Sender, command_channel: mpsc::Receiver, + measured_rate: Arc, ) -> Res>> { let host_cfg = self.host.clone(); let devname = self.devname.clone(); @@ -484,6 +485,9 @@ impl CaptureDevice for CpalCaptureDevice { let mut capture_samples = chunksize_samples; let mut sample_queue_i: VecDeque = VecDeque::with_capacity(2*chunksize*channels); let mut sample_queue_f: VecDeque = VecDeque::with_capacity(2*chunksize*channels); + let mut start = SystemTime::now(); + let mut now; + let mut sample_counter = 0; loop { match command_channel.try_recv() { Ok(CommandMessage::Exit) => { @@ -561,6 +565,21 @@ impl CaptureDevice for CpalCaptureDevice { }, _ => panic!("Unsupported sample format"), }; + now = SystemTime::now(); + sample_counter += capture_samples; + if now.duration_since(start).unwrap().as_millis() > 1000 + { + let meas_time = now.duration_since(start).unwrap().as_secs_f32(); + let samples_per_sec = sample_counter as f32 / meas_time; + let measured_rate_f = samples_per_sec / channels as f32; + trace!( + "Measured sample rate is {} Hz", + measured_rate_f + ); + measured_rate.store(measured_rate_f as usize, Ordering::Relaxed); + start = now; + sample_counter = 0; + } if (chunk.maxval - chunk.minval) > silence { if silent_nbr > silent_limit { debug!("Resuming processing"); diff --git a/src/filedevice.rs b/src/filedevice.rs index df7e7b12..8283abf6 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -10,7 +10,9 @@ use std::io::ErrorKind; use std::io::{Read, Write}; use std::sync::mpsc; use std::sync::{Arc, Barrier}; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; +use std::time::SystemTime; use rubato::Resampler; @@ -68,6 +70,7 @@ struct CaptureParams { resampling_ratio: f32, read_bytes: usize, async_src: bool, + measured_rate: Arc, } //struct PlaybackParams { @@ -248,6 +251,9 @@ fn capture_loop( let mut capture_bytes_temp; let mut extra_bytes_left = params.extra_bytes; let mut nbr_bytes_read = 0; + let mut start = SystemTime::now(); + let mut now; + let mut bytes_counter = 0; loop { match msg_channels.command.try_recv() { Ok(CommandMessage::Exit) => { @@ -320,6 +326,21 @@ fn capture_loop( .unwrap(); break; } + now = SystemTime::now(); + bytes_counter += bytes; + if now.duration_since(start).unwrap().as_millis() > 1000 + { + let meas_time = now.duration_since(start).unwrap().as_secs_f32(); + let bytes_per_sec = bytes_counter as f32 / meas_time; + let measured_rate_f = bytes_per_sec / (params.channels * params.store_bytes) as f32; + trace!( + "Measured sample rate is {} Hz", + measured_rate_f + ); + params.measured_rate.store(measured_rate_f as usize, Ordering::Relaxed); + start = now; + bytes_counter = 0; + } } Err(err) => { debug!("Encountered a read error"); @@ -375,6 +396,7 @@ impl CaptureDevice for FileCaptureDevice { barrier: Arc, status_channel: mpsc::Sender, command_channel: mpsc::Receiver, + measured_rate: Arc, ) -> Res>> { let filename = self.filename.clone(); let samplerate = self.samplerate; @@ -451,6 +473,7 @@ impl CaptureDevice for FileCaptureDevice { resampling_ratio: samplerate as f32 / capture_samplerate as f32, read_bytes, async_src, + measured_rate, }; let msg_channels = CaptureChannels { audio: channel, diff --git a/src/pulsedevice.rs b/src/pulsedevice.rs index c3049d48..7201e38c 100644 --- a/src/pulsedevice.rs +++ b/src/pulsedevice.rs @@ -13,7 +13,9 @@ use conversions::{ use rubato::Resampler; use std::sync::mpsc; use std::sync::{Arc, Barrier}; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; +use std::time::SystemTime; use CommandMessage; use PrcFmt; @@ -229,6 +231,7 @@ impl CaptureDevice for PulseCaptureDevice { barrier: Arc, status_channel: mpsc::Sender, command_channel: mpsc::Receiver, + measured_rate: Arc, ) -> Res>> { let devname = self.devname.clone(); let samplerate = self.samplerate; @@ -300,6 +303,9 @@ impl CaptureDevice for PulseCaptureDevice { let mut buf = vec![0u8; buffer_bytes]; let chunksize_bytes = channels * chunksize * store_bytes; let mut capture_bytes = chunksize_bytes; + let mut start = SystemTime::now(); + let mut now; + let mut bytes_counter = 0; loop { match command_channel.try_recv() { Ok(CommandMessage::Exit) => { @@ -334,7 +340,23 @@ impl CaptureDevice for PulseCaptureDevice { } let read_res = pulsedevice.read(&mut buf[0..capture_bytes]); match read_res { - Ok(()) => {} + Ok(()) => { + now = SystemTime::now(); + bytes_counter += capture_bytes; + if now.duration_since(start).unwrap().as_millis() > 1000 + { + let meas_time = now.duration_since(start).unwrap().as_secs_f32(); + let bytes_per_sec = bytes_counter as f32 / meas_time; + let measured_rate_f = bytes_per_sec / (channels * store_bytes) as f32; + trace!( + "Measured sample rate is {} Hz", + measured_rate_f + ); + measured_rate.store(measured_rate_f as usize, Ordering::Relaxed); + start = now; + bytes_counter = 0; + } + } Err(msg) => { status_channel .send(StatusMessage::CaptureError { diff --git a/src/socketserver.rs b/src/socketserver.rs index b7674b85..a656d3f3 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -13,6 +13,7 @@ enum WSCommand { GetConfig, GetConfigJson, GetConfigName, + GetCaptureRate, Exit, Stop, Invalid, @@ -32,6 +33,7 @@ fn parse_command(cmd: &ws::Message) -> WSCommand { "getconfigname" => WSCommand::GetConfigName, "exit" => WSCommand::Exit, "stop" => WSCommand::Stop, + "getcapturerate" => WSCommand::GetCaptureRate, "setconfigname" => { if cmdarg.len() == 2 { WSCommand::SetConfigName(cmdarg[1].to_string()) @@ -67,6 +69,7 @@ pub fn start_server( active_config_shared: Arc>>, active_config_path: Arc>>, new_config_shared: Arc>>, + measured_rate: Arc, ) { debug!("Start websocket server on port {}", port); thread::spawn(move || { @@ -76,6 +79,7 @@ pub fn start_server( let active_config_inst = active_config_shared.clone(); let new_config_inst = new_config_shared.clone(); let active_config_path_inst = active_config_path.clone(); + let measured_rate_inst = measured_rate.clone(); move |msg: ws::Message| { let command = parse_command(&msg); debug!("parsed command: {:?}", command); @@ -84,6 +88,12 @@ pub fn start_server( signal_reload_inst.store(true, Ordering::Relaxed); socket.send("OK:RELOAD") } + WSCommand::GetCaptureRate => { + socket.send(format!( + "OK:GETCAPTURERATE:{}", + measured_rate_inst.load(Ordering::Relaxed) + )) + } WSCommand::GetConfig => { //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); socket.send(format!( diff --git a/websocket.md b/websocket.md index 0174da14..d0b178b4 100644 --- a/websocket.md +++ b/websocket.md @@ -11,6 +11,8 @@ The available commands are: * response is `OK:GETCONFIG:(jsondata)` where yamldata is the config in JSON format. - `getconfigname` : get name and path of current config file * response is `OK:GETCONFIGNAME:/path/to/current.yml` +- `getcapturerate` : get the measured sample rate of the capture device. + * response is `OK:GETCAPTURERATE:123456` - `reload` : reload current config file (same as SIGHUP) * response is `OK:RELOAD` or `ERROR:RELOAD` - `stop` : stop processing and wait for a new config to be uploaded with `setconfig` From 3f831021557320fcec97c99e41eadcf2ddab3aee Mon Sep 17 00:00:00 2001 From: HEnquist Date: Mon, 6 Jul 2020 22:11:06 +0200 Subject: [PATCH 10/61] Format --- src/alsadevice.rs | 17 ++++++++--------- src/audiodevice.rs | 2 +- src/bin.rs | 10 ++++++++-- src/cpaldevice.rs | 3 ++- src/filedevice.rs | 17 ++++++++--------- src/pulsedevice.rs | 2 +- src/socketserver.rs | 10 ++++------ 7 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/alsadevice.rs b/src/alsadevice.rs index 94d9a664..a4705115 100644 --- a/src/alsadevice.rs +++ b/src/alsadevice.rs @@ -13,9 +13,9 @@ use conversions::{ }; use rubato::Resampler; use std::ffi::CString; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc; use std::sync::{Arc, Barrier}; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; use std::time::{Duration, SystemTime}; @@ -330,16 +330,15 @@ fn capture_loop_bytes( trace!("Captured {} bytes", capture_bytes); now = SystemTime::now(); bytes_counter += capture_bytes; - if now.duration_since(start).unwrap().as_millis() > 1000 - { + if now.duration_since(start).unwrap().as_millis() > 1000 { let meas_time = now.duration_since(start).unwrap().as_secs_f32(); let bytes_per_sec = bytes_counter as f32 / meas_time; - let measured_rate_f = bytes_per_sec / (params.channels * params.bytes_per_sample) as f32; - trace!( - "Measured sample rate is {} Hz", - measured_rate_f - ); - params.measured_rate.store(measured_rate_f as usize, Ordering::Relaxed); + let measured_rate_f = + bytes_per_sec / (params.channels * params.bytes_per_sample) as f32; + trace!("Measured sample rate is {} Hz", measured_rate_f); + params + .measured_rate + .store(measured_rate_f as usize, Ordering::Relaxed); start = now; bytes_counter = 0; } diff --git a/src/audiodevice.rs b/src/audiodevice.rs index 0751ec27..2b2d829e 100644 --- a/src/audiodevice.rs +++ b/src/audiodevice.rs @@ -12,9 +12,9 @@ use rubato::{ FftFixedOut, InterpolationParameters, InterpolationType, Resampler, SincFixedOut, WindowFunction, }; +use std::sync::atomic::AtomicUsize; use std::sync::mpsc; use std::sync::{Arc, Barrier}; -use std::sync::atomic::AtomicUsize; use std::thread; use std::time::Instant; diff --git a/src/bin.rs b/src/bin.rs index 110d6830..39903698 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -148,7 +148,13 @@ fn run( // Capture thread let mut capture_dev = audiodevice::get_capture_device(conf_cap.devices); let cap_handle = capture_dev - .start(tx_cap, barrier_cap, tx_status_cap, rx_command_cap, measured_rate) + .start( + tx_cap, + barrier_cap, + tx_status_cap, + rx_command_cap, + measured_rate, + ) .unwrap(); let delay = time::Duration::from_millis(100); @@ -405,7 +411,7 @@ fn main() { active_config.clone(), active_config_path.clone(), new_config.clone(), - measured_rate.clone() + measured_rate.clone(), ); } } diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index ca90c06f..daf764db 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -302,7 +302,8 @@ impl PlaybackDevice for CpalPlaybackDevice { match channel.recv() { Ok(AudioMessage::Audio(chunk)) => { now = SystemTime::now(); - delay += (buffer_fill.load(Ordering::Relaxed)/channels_clone) as isize; + delay += (buffer_fill.load(Ordering::Relaxed) / channels_clone) + as isize; ndelays += 1; if adjust && (now.duration_since(start).unwrap().as_millis() diff --git a/src/filedevice.rs b/src/filedevice.rs index 8283abf6..4e082596 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -8,9 +8,9 @@ use conversions::{ use std::fs::File; use std::io::ErrorKind; use std::io::{Read, Write}; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc; use std::sync::{Arc, Barrier}; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; use std::time::SystemTime; @@ -328,16 +328,15 @@ fn capture_loop( } now = SystemTime::now(); bytes_counter += bytes; - if now.duration_since(start).unwrap().as_millis() > 1000 - { + if now.duration_since(start).unwrap().as_millis() > 1000 { let meas_time = now.duration_since(start).unwrap().as_secs_f32(); let bytes_per_sec = bytes_counter as f32 / meas_time; - let measured_rate_f = bytes_per_sec / (params.channels * params.store_bytes) as f32; - trace!( - "Measured sample rate is {} Hz", - measured_rate_f - ); - params.measured_rate.store(measured_rate_f as usize, Ordering::Relaxed); + let measured_rate_f = + bytes_per_sec / (params.channels * params.store_bytes) as f32; + trace!("Measured sample rate is {} Hz", measured_rate_f); + params + .measured_rate + .store(measured_rate_f as usize, Ordering::Relaxed); start = now; bytes_counter = 0; } diff --git a/src/pulsedevice.rs b/src/pulsedevice.rs index 7201e38c..968462f8 100644 --- a/src/pulsedevice.rs +++ b/src/pulsedevice.rs @@ -11,9 +11,9 @@ use conversions::{ chunk_to_buffer_float_bytes, }; use rubato::Resampler; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc; use std::sync::{Arc, Barrier}; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; use std::time::SystemTime; diff --git a/src/socketserver.rs b/src/socketserver.rs index a656d3f3..3c711783 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -88,12 +88,10 @@ pub fn start_server( signal_reload_inst.store(true, Ordering::Relaxed); socket.send("OK:RELOAD") } - WSCommand::GetCaptureRate => { - socket.send(format!( - "OK:GETCAPTURERATE:{}", - measured_rate_inst.load(Ordering::Relaxed) - )) - } + WSCommand::GetCaptureRate => socket.send(format!( + "OK:GETCAPTURERATE:{}", + measured_rate_inst.load(Ordering::Relaxed) + )), WSCommand::GetConfig => { //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); socket.send(format!( From 10d8c0a2ca509cec3b780393cbaa8aa5f51619e2 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Wed, 15 Jul 2020 22:51:56 +0200 Subject: [PATCH 11/61] More socketserver commands --- src/alsadevice.rs | 24 ++++++++++++++---------- src/audiodevice.rs | 6 +++--- src/bin.rs | 18 ++++++++++++------ src/filedevice.rs | 19 ++++++++++--------- src/lib.rs | 7 +++++++ src/pulsedevice.rs | 12 ++++++------ src/socketserver.rs | 44 +++++++++++++++++++++++++++++++++++++------- 7 files changed, 89 insertions(+), 41 deletions(-) diff --git a/src/alsadevice.rs b/src/alsadevice.rs index a4705115..aab5d276 100644 --- a/src/alsadevice.rs +++ b/src/alsadevice.rs @@ -13,12 +13,12 @@ use conversions::{ }; use rubato::Resampler; use std::ffi::CString; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc; -use std::sync::{Arc, Barrier}; +use std::sync::{Arc, Barrier, RwLock}; use std::thread; use std::time::{Duration, SystemTime}; +use crate::CaptureStatus; use CommandMessage; use PrcFmt; use Res; @@ -77,7 +77,7 @@ struct CaptureParams { samplerate: usize, capture_samplerate: usize, async_src: bool, - measured_rate: Arc, + capture_status: Arc>, } struct PlaybackParams { @@ -299,6 +299,7 @@ fn capture_loop_bytes( let mut start = SystemTime::now(); let mut now; let mut bytes_counter = 0; + let mut value_range = 0.0; loop { match channels.command.try_recv() { Ok(CommandMessage::Exit) => { @@ -330,15 +331,17 @@ fn capture_loop_bytes( trace!("Captured {} bytes", capture_bytes); now = SystemTime::now(); bytes_counter += capture_bytes; - if now.duration_since(start).unwrap().as_millis() > 1000 { + if now.duration_since(start).unwrap().as_millis() as usize + > params.capture_status.read().unwrap().update_interval + { let meas_time = now.duration_since(start).unwrap().as_secs_f32(); let bytes_per_sec = bytes_counter as f32 / meas_time; let measured_rate_f = bytes_per_sec / (params.channels * params.bytes_per_sample) as f32; trace!("Measured sample rate is {} Hz", measured_rate_f); - params - .measured_rate - .store(measured_rate_f as usize, Ordering::Relaxed); + let mut capt_stat = params.capture_status.write().unwrap(); + capt_stat.measured_samplerate = measured_rate_f as usize; + capt_stat.signal_range = value_range; start = now; bytes_counter = 0; } @@ -368,7 +371,8 @@ fn capture_loop_bytes( capture_bytes, ) }; - if (chunk.maxval - chunk.minval) > params.silence { + value_range = chunk.maxval - chunk.minval; + if value_range > params.silence { if silent_nbr > params.silent_limit { debug!("Resuming processing"); } @@ -517,7 +521,7 @@ impl CaptureDevice for AlsaCaptureDevice { barrier: Arc, status_channel: mpsc::Sender, command_channel: mpsc::Receiver, - measured_rate: Arc, + capture_status: Arc>, ) -> Res>> { let devname = self.devname.clone(); let samplerate = self.samplerate; @@ -603,7 +607,7 @@ impl CaptureDevice for AlsaCaptureDevice { samplerate, capture_samplerate, async_src, - measured_rate, + capture_status, }; let cap_channels = CaptureChannels { audio: channel, diff --git a/src/audiodevice.rs b/src/audiodevice.rs index 2b2d829e..c56a7615 100644 --- a/src/audiodevice.rs +++ b/src/audiodevice.rs @@ -12,12 +12,12 @@ use rubato::{ FftFixedOut, InterpolationParameters, InterpolationType, Resampler, SincFixedOut, WindowFunction, }; -use std::sync::atomic::AtomicUsize; use std::sync::mpsc; -use std::sync::{Arc, Barrier}; +use std::sync::{Arc, Barrier, RwLock}; use std::thread; use std::time::Instant; +use crate::CaptureStatus; use CommandMessage; use PrcFmt; use Res; @@ -98,7 +98,7 @@ pub trait CaptureDevice { barrier: Arc, status_channel: mpsc::Sender, command_channel: mpsc::Receiver, - measured_rate: Arc, + capture_status: Arc>, ) -> Res>>; } diff --git a/src/bin.rs b/src/bin.rs index 39903698..d08e9329 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -30,7 +30,7 @@ use log::LevelFilter; use std::env; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::mpsc; -use std::sync::{Arc, Barrier, Mutex}; +use std::sync::{Arc, Barrier, Mutex, RwLock}; use std::thread; use std::time; @@ -48,6 +48,8 @@ use camillalib::CommandMessage; use camillalib::ExitStatus; +use camillalib::CaptureStatus; + fn get_new_config( config_path: &Arc>>, new_config_shared: &Arc>>, @@ -104,7 +106,7 @@ fn run( active_config_shared: Arc>>, config_path: Arc>>, new_config_shared: Arc>>, - measured_rate: Arc, + capture_status: Arc>, ) -> Res { let conf = match new_config_shared.lock().unwrap().clone() { Some(cfg) => cfg, @@ -153,7 +155,7 @@ fn run( barrier_cap, tx_status_cap, rx_command_cap, - measured_rate, + capture_status, ) .unwrap(); @@ -393,7 +395,11 @@ fn main() { let signal_reload = Arc::new(AtomicBool::new(false)); let signal_exit = Arc::new(AtomicUsize::new(0)); - let measured_rate = Arc::new(AtomicUsize::new(0)); + let capture_status = Arc::new(RwLock::new(CaptureStatus { + measured_samplerate: 0, + update_interval: 1000, + signal_range: 0.0, + })); //let active_config = Arc::new(Mutex::new(String::new())); let active_config = Arc::new(Mutex::new(None)); let new_config = Arc::new(Mutex::new(configuration)); @@ -411,7 +417,7 @@ fn main() { active_config.clone(), active_config_path.clone(), new_config.clone(), - measured_rate.clone(), + capture_status.clone(), ); } } @@ -430,7 +436,7 @@ fn main() { active_config.clone(), active_config_path.clone(), new_config.clone(), - measured_rate.clone(), + capture_status.clone(), ); match exitstatus { Err(e) => { diff --git a/src/filedevice.rs b/src/filedevice.rs index 4e082596..0a1a070a 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -8,14 +8,14 @@ use conversions::{ use std::fs::File; use std::io::ErrorKind; use std::io::{Read, Write}; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc; -use std::sync::{Arc, Barrier}; +use std::sync::{Arc, Barrier, RwLock}; use std::thread; use std::time::SystemTime; use rubato::Resampler; +use crate::CaptureStatus; use CommandMessage; use PrcFmt; use Res; @@ -70,7 +70,7 @@ struct CaptureParams { resampling_ratio: f32, read_bytes: usize, async_src: bool, - measured_rate: Arc, + capture_status: Arc>, } //struct PlaybackParams { @@ -328,15 +328,16 @@ fn capture_loop( } now = SystemTime::now(); bytes_counter += bytes; - if now.duration_since(start).unwrap().as_millis() > 1000 { + if now.duration_since(start).unwrap().as_millis() as usize + > params.capture_status.read().unwrap().update_interval + { let meas_time = now.duration_since(start).unwrap().as_secs_f32(); let bytes_per_sec = bytes_counter as f32 / meas_time; let measured_rate_f = bytes_per_sec / (params.channels * params.store_bytes) as f32; trace!("Measured sample rate is {} Hz", measured_rate_f); - params - .measured_rate - .store(measured_rate_f as usize, Ordering::Relaxed); + let mut capt_stat = params.capture_status.write().unwrap(); + capt_stat.measured_samplerate = measured_rate_f as usize; start = now; bytes_counter = 0; } @@ -395,7 +396,7 @@ impl CaptureDevice for FileCaptureDevice { barrier: Arc, status_channel: mpsc::Sender, command_channel: mpsc::Receiver, - measured_rate: Arc, + capture_status: Arc>, ) -> Res>> { let filename = self.filename.clone(); let samplerate = self.samplerate; @@ -472,7 +473,7 @@ impl CaptureDevice for FileCaptureDevice { resampling_ratio: samplerate as f32 / capture_samplerate as f32, read_bytes, async_src, - measured_rate, + capture_status, }; let msg_channels = CaptureChannels { audio: channel, diff --git a/src/lib.rs b/src/lib.rs index c193edf5..ed760de0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,3 +80,10 @@ pub enum ExitStatus { Restart, Exit, } + +#[derive(Clone, Debug)] +pub struct CaptureStatus { + pub update_interval: usize, + pub measured_samplerate: usize, + pub signal_range: PrcFmt, +} diff --git a/src/pulsedevice.rs b/src/pulsedevice.rs index 968462f8..eb247d8d 100644 --- a/src/pulsedevice.rs +++ b/src/pulsedevice.rs @@ -11,12 +11,12 @@ use conversions::{ chunk_to_buffer_float_bytes, }; use rubato::Resampler; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc; -use std::sync::{Arc, Barrier}; +use std::sync::{Arc, Barrier, RwLock}; use std::thread; use std::time::SystemTime; +use crate::CaptureStatus; use CommandMessage; use PrcFmt; use Res; @@ -231,7 +231,7 @@ impl CaptureDevice for PulseCaptureDevice { barrier: Arc, status_channel: mpsc::Sender, command_channel: mpsc::Receiver, - measured_rate: Arc, + capture_status: Arc>, ) -> Res>> { let devname = self.devname.clone(); let samplerate = self.samplerate; @@ -343,8 +343,7 @@ impl CaptureDevice for PulseCaptureDevice { Ok(()) => { now = SystemTime::now(); bytes_counter += capture_bytes; - if now.duration_since(start).unwrap().as_millis() > 1000 - { + if now.duration_since(start).unwrap().as_millis() as usize > capture_status.read().unwrap().update_interval { let meas_time = now.duration_since(start).unwrap().as_secs_f32(); let bytes_per_sec = bytes_counter as f32 / meas_time; let measured_rate_f = bytes_per_sec / (channels * store_bytes) as f32; @@ -352,7 +351,8 @@ impl CaptureDevice for PulseCaptureDevice { "Measured sample rate is {} Hz", measured_rate_f ); - measured_rate.store(measured_rate_f as usize, Ordering::Relaxed); + let mut capt_stat = capture_status.write().unwrap(); + capt_stat.measured_samplerate = measured_rate_f as usize; start = now; bytes_counter = 0; } diff --git a/src/socketserver.rs b/src/socketserver.rs index 3c711783..a7f2b365 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -1,7 +1,8 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; use std::thread; +use crate::CaptureStatus; use config; #[derive(Debug, PartialEq)] @@ -13,7 +14,10 @@ enum WSCommand { GetConfig, GetConfigJson, GetConfigName, + GetSignalRange, GetCaptureRate, + GetUpdateInterval, + SetUpdateInterval(usize), Exit, Stop, Invalid, @@ -34,6 +38,19 @@ fn parse_command(cmd: &ws::Message) -> WSCommand { "exit" => WSCommand::Exit, "stop" => WSCommand::Stop, "getcapturerate" => WSCommand::GetCaptureRate, + "getsignalrange" => WSCommand::GetSignalRange, + "getupdateinterval" => WSCommand::GetUpdateInterval, + "setupdateinterval" => { + if cmdarg.len() == 2 { + let nbr_conv = cmdarg[1].to_string().parse::(); + match nbr_conv { + Ok(nbr) => WSCommand::SetUpdateInterval(nbr), + Err(_) => WSCommand::Invalid, + } + } else { + WSCommand::Invalid + } + } "setconfigname" => { if cmdarg.len() == 2 { WSCommand::SetConfigName(cmdarg[1].to_string()) @@ -69,7 +86,7 @@ pub fn start_server( active_config_shared: Arc>>, active_config_path: Arc>>, new_config_shared: Arc>>, - measured_rate: Arc, + capture_status: Arc>, ) { debug!("Start websocket server on port {}", port); thread::spawn(move || { @@ -79,7 +96,7 @@ pub fn start_server( let active_config_inst = active_config_shared.clone(); let new_config_inst = new_config_shared.clone(); let active_config_path_inst = active_config_path.clone(); - let measured_rate_inst = measured_rate.clone(); + let capture_status_inst = capture_status.clone(); move |msg: ws::Message| { let command = parse_command(&msg); debug!("parsed command: {:?}", command); @@ -88,10 +105,23 @@ pub fn start_server( signal_reload_inst.store(true, Ordering::Relaxed); socket.send("OK:RELOAD") } - WSCommand::GetCaptureRate => socket.send(format!( - "OK:GETCAPTURERATE:{}", - measured_rate_inst.load(Ordering::Relaxed) - )), + WSCommand::GetCaptureRate => { + let capstat = capture_status_inst.read().unwrap(); + socket.send(format!("OK:GETCAPTURERATE:{}", capstat.measured_samplerate)) + } + WSCommand::GetSignalRange => { + let capstat = capture_status_inst.read().unwrap(); + socket.send(format!("OK:GETSIGNALRANGE:{}", capstat.signal_range)) + } + WSCommand::GetUpdateInterval => { + let capstat = capture_status_inst.read().unwrap(); + socket.send(format!("OK:GETUPDATEINTERVAL:{}", capstat.update_interval)) + } + WSCommand::SetUpdateInterval(nbr) => { + let mut capstat = capture_status_inst.write().unwrap(); + capstat.update_interval = nbr; + socket.send("OK:SETUPDATEINTERVAL".to_string()) + } WSCommand::GetConfig => { //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); socket.send(format!( From e8006559e9bbc38b41ef52327f0b0982bffcaef2 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Wed, 15 Jul 2020 23:01:09 +0200 Subject: [PATCH 12/61] Update websocket readme with new commands --- websocket.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/websocket.md b/websocket.md index d0b178b4..6b98f669 100644 --- a/websocket.md +++ b/websocket.md @@ -13,6 +13,12 @@ The available commands are: * response is `OK:GETCONFIGNAME:/path/to/current.yml` - `getcapturerate` : get the measured sample rate of the capture device. * response is `OK:GETCAPTURERATE:123456` +- `getupdateinterval` : get the update interval in ms for capture rate and signalrange. + * response is `OK:GETUPDATEINTERVAL:123456` +- `setupdateinterval:` : set the update interval in ms for capturerate and signalrange. + * response is `OK:SETUPDATEINTERVAL` +- `getsignalrange` : get the range of values in the last chunk. A value of 2.0 means full level (signal swings from -1.0 to +1.0) + * response is `OK:GETSIGNALRANGE:1.23456` - `reload` : reload current config file (same as SIGHUP) * response is `OK:RELOAD` or `ERROR:RELOAD` - `stop` : stop processing and wait for a new config to be uploaded with `setconfig` From 52a692c2774c52b9f0f30456d8ffebaf180242a1 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Thu, 16 Jul 2020 08:32:49 +0200 Subject: [PATCH 13/61] Update cpal backend to use capture_status --- src/cpaldevice.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index daf764db..51a36fbe 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -12,10 +12,11 @@ use rubato::Resampler; use std::collections::VecDeque; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc; -use std::sync::{Arc, Barrier}; +use std::sync::{Arc, Barrier, RwLock}; use std::thread; use std::time::SystemTime; +use crate::CaptureStatus; use CommandMessage; use PrcFmt; use Res; @@ -385,7 +386,7 @@ impl CaptureDevice for CpalCaptureDevice { barrier: Arc, status_channel: mpsc::Sender, command_channel: mpsc::Receiver, - measured_rate: Arc, + capture_status: Arc>, ) -> Res>> { let host_cfg = self.host.clone(); let devname = self.devname.clone(); @@ -568,7 +569,7 @@ impl CaptureDevice for CpalCaptureDevice { }; now = SystemTime::now(); sample_counter += capture_samples; - if now.duration_since(start).unwrap().as_millis() > 1000 + if now.duration_since(start).unwrap().as_millis() as usize > capture_status.read().unwrap().update_interval { let meas_time = now.duration_since(start).unwrap().as_secs_f32(); let samples_per_sec = sample_counter as f32 / meas_time; @@ -577,7 +578,8 @@ impl CaptureDevice for CpalCaptureDevice { "Measured sample rate is {} Hz", measured_rate_f ); - measured_rate.store(measured_rate_f as usize, Ordering::Relaxed); + let mut capt_stat = capture_status.write().unwrap(); + capt_stat.measured_samplerate = measured_rate_f as usize; start = now; sample_counter = 0; } From f298d572068d09b3f2ceeb143f12370b6f3f216f Mon Sep 17 00:00:00 2001 From: HEnquist Date: Thu, 16 Jul 2020 08:37:27 +0200 Subject: [PATCH 14/61] Measure value range for all backends --- src/cpaldevice.rs | 5 ++++- src/filedevice.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index 51a36fbe..7a6fbb02 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -490,6 +490,7 @@ impl CaptureDevice for CpalCaptureDevice { let mut start = SystemTime::now(); let mut now; let mut sample_counter = 0; + let mut value_range = 0.0; loop { match command_channel.try_recv() { Ok(CommandMessage::Exit) => { @@ -580,10 +581,12 @@ impl CaptureDevice for CpalCaptureDevice { ); let mut capt_stat = capture_status.write().unwrap(); capt_stat.measured_samplerate = measured_rate_f as usize; + capt_stat.signal_range = value_range; start = now; sample_counter = 0; } - if (chunk.maxval - chunk.minval) > silence { + value_range = chunk.maxval - chunk.minval; + if (value_range) > silence { if silent_nbr > silent_limit { debug!("Resuming processing"); } diff --git a/src/filedevice.rs b/src/filedevice.rs index 0a1a070a..117824bd 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -254,6 +254,7 @@ fn capture_loop( let mut start = SystemTime::now(); let mut now; let mut bytes_counter = 0; + let mut value_range = 0.0; loop { match msg_channels.command.try_recv() { Ok(CommandMessage::Exit) => { @@ -338,6 +339,7 @@ fn capture_loop( trace!("Measured sample rate is {} Hz", measured_rate_f); let mut capt_stat = params.capture_status.write().unwrap(); capt_stat.measured_samplerate = measured_rate_f as usize; + capt_stat.signal_range = value_range; start = now; bytes_counter = 0; } @@ -362,7 +364,8 @@ fn capture_loop( bytes_read, scalefactor, ); - if (chunk.maxval - chunk.minval) > params.silence { + value_range = chunk.maxval - chunk.minval; + if (value_range) > params.silence { if silent_nbr > params.silent_limit { debug!("Resuming processing"); } From cbe4a33dc1cb89b033e7dab92e79f5f918e09efd Mon Sep 17 00:00:00 2001 From: HEnquist Date: Thu, 16 Jul 2020 09:47:16 +0200 Subject: [PATCH 15/61] Support skip and read_bytes/lines for coeff files --- src/config.rs | 8 ++++++++ src/fftconv.rs | 12 ++++++------ src/filters.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/config.rs b/src/config.rs index acf514db..73de5bf9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -250,6 +250,14 @@ pub enum ConvParameters { filename: String, #[serde(default)] format: FileFormat, + #[serde(default)] + skip_bytes: usize, + #[serde(default)] + read_bytes: usize, + #[serde(default)] + skip_lines: usize, + #[serde(default)] + read_lines: usize, }, Values { values: Vec, diff --git a/src/fftconv.rs b/src/fftconv.rs index 604159d4..de3a5c0b 100644 --- a/src/fftconv.rs +++ b/src/fftconv.rs @@ -69,8 +69,8 @@ impl FFTConv { pub fn from_config(name: String, data_length: usize, conf: config::ConvParameters) -> Self { let values = match conf { config::ConvParameters::Values { values } => values, - config::ConvParameters::File { filename, format } => { - filters::read_coeff_file(&filename, &format).unwrap() + config::ConvParameters::File { filename, format, read_bytes, read_lines, skip_bytes, skip_lines } => { + filters::read_coeff_file(&filename, &format, read_bytes, read_lines, skip_bytes, skip_lines).unwrap() } }; FFTConv::new(name, data_length, &values) @@ -134,8 +134,8 @@ impl Filter for FFTConv { if let config::Filter::Conv { parameters: conf } = conf { let coeffs = match conf { config::ConvParameters::Values { values } => values, - config::ConvParameters::File { filename, format } => { - filters::read_coeff_file(&filename, &format).unwrap() + config::ConvParameters::File { filename, format, read_bytes, read_lines, skip_bytes, skip_lines } => { + filters::read_coeff_file(&filename, &format, read_bytes, read_lines, skip_bytes, skip_lines).unwrap() } }; @@ -175,8 +175,8 @@ impl Filter for FFTConv { pub fn validate_config(conf: &config::ConvParameters) -> Res<()> { match conf { config::ConvParameters::Values { .. } => Ok(()), - config::ConvParameters::File { filename, format } => { - let coeffs = filters::read_coeff_file(&filename, &format)?; + config::ConvParameters::File { filename, format, read_bytes, read_lines, skip_bytes, skip_lines } => { + let coeffs = filters::read_coeff_file(&filename, &format, *read_bytes, *read_lines, *skip_bytes, *skip_lines)?; if coeffs.is_empty() { return Err(Box::new(config::ConfigError::new( "Conv coefficients are empty", diff --git a/src/filters.rs b/src/filters.rs index 3e102895..3a907377 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -13,7 +13,7 @@ use mixer; use std::collections::HashMap; use std::fs::File; use std::io::BufReader; -use std::io::{BufRead, Read}; +use std::io::{BufRead, Read, Seek, SeekFrom}; use PrcFmt; use Res; @@ -27,65 +27,108 @@ pub trait Filter { fn name(&self) -> String; } -pub fn read_coeff_file(filename: &str, format: &config::FileFormat) -> Res> { +pub fn read_coeff_file(filename: &str, format: &config::FileFormat, read_bytes: usize, read_lines: usize, skip_bytes: usize, skip_lines: usize) -> Res> { let mut coefficients = Vec::::new(); let f = File::open(filename)?; let mut file = BufReader::new(&f); + let read_bytes = if read_bytes > 0 { + read_bytes + } + else { + usize::MAX + }; + let read_lines = if read_lines > 0 { + read_lines + } + else { + usize::MAX + }; + match format { config::FileFormat::TEXT => { - for line in file.lines() { + for line in file.lines().skip(skip_lines).take(read_lines) { let l = line?; coefficients.push(l.trim().parse()?); } } config::FileFormat::FLOAT32LE => { let mut buffer = [0; 4]; + file.seek(SeekFrom::Start(skip_bytes as u64))?; + let nbr_coeffs = read_bytes/4; while let Ok(4) = file.read(&mut buffer) { let value = f32::from_le_bytes(buffer) as PrcFmt; coefficients.push(value); + if coefficients.len() >= nbr_coeffs { + break; + } } } config::FileFormat::FLOAT64LE => { let mut buffer = [0; 8]; + file.seek(SeekFrom::Start(skip_bytes as u64))?; + let nbr_coeffs = read_bytes/8; while let Ok(8) = file.read(&mut buffer) { let value = f64::from_le_bytes(buffer) as PrcFmt; coefficients.push(value); + if coefficients.len() >= nbr_coeffs { + break; + } } } config::FileFormat::S16LE => { let mut buffer = [0; 2]; + file.seek(SeekFrom::Start(skip_bytes as u64))?; + let nbr_coeffs = read_bytes/2; let scalefactor = (2.0 as PrcFmt).powi(15); while let Ok(2) = file.read(&mut buffer) { let mut value = i16::from_le_bytes(buffer) as PrcFmt; value /= scalefactor; coefficients.push(value); + if coefficients.len() >= nbr_coeffs { + break; + } } } config::FileFormat::S24LE => { let mut buffer = [0; 4]; + file.seek(SeekFrom::Start(skip_bytes as u64))?; + let nbr_coeffs = read_bytes/4; let scalefactor = (2.0 as PrcFmt).powi(23); while let Ok(4) = file.read(&mut buffer) { let mut value = i32::from_le_bytes(buffer) as PrcFmt; value /= scalefactor; coefficients.push(value); + if coefficients.len() >= nbr_coeffs { + break; + } } } config::FileFormat::S24LE3 => { let mut buffer = [0; 4]; + file.seek(SeekFrom::Start(skip_bytes as u64))?; + let nbr_coeffs = read_bytes/3; let scalefactor = (2.0 as PrcFmt).powi(23); while let Ok(3) = file.read(&mut buffer[0..3]) { let mut value = i32::from_le_bytes(buffer) as PrcFmt; value /= scalefactor; coefficients.push(value); + if coefficients.len() >= nbr_coeffs { + break; + } } } config::FileFormat::S32LE => { let mut buffer = [0; 4]; + file.seek(SeekFrom::Start(skip_bytes as u64))?; + let nbr_coeffs = read_bytes/4; let scalefactor = (2.0 as PrcFmt).powi(31); while let Ok(4) = file.read(&mut buffer) { let mut value = i32::from_le_bytes(buffer) as PrcFmt; value /= scalefactor; coefficients.push(value); + if coefficients.len() >= nbr_coeffs { + break; + } } } } From bf95aa253006d15a5855165fc16a3f6f799c5aaa Mon Sep 17 00:00:00 2001 From: HEnquist Date: Thu, 16 Jul 2020 09:49:50 +0200 Subject: [PATCH 16/61] Skip and read_bytes for FFTW --- src/fftconv_fftw.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fftconv_fftw.rs b/src/fftconv_fftw.rs index d74bbd86..f72f0540 100644 --- a/src/fftconv_fftw.rs +++ b/src/fftconv_fftw.rs @@ -82,8 +82,8 @@ impl FFTConv { pub fn from_config(name: String, data_length: usize, conf: config::ConvParameters) -> Self { let values = match conf { config::ConvParameters::Values { values } => values, - config::ConvParameters::File { filename, format } => { - filters::read_coeff_file(&filename, &format).unwrap() + config::ConvParameters::File { filename, format, read_bytes, read_lines, skip_bytes, skip_lines } => { + filters::read_coeff_file(&filename, &format, read_bytes, read_lines, skip_bytes, skip_lines).unwrap() } }; FFTConv::new(name, data_length, &values) @@ -139,8 +139,8 @@ impl Filter for FFTConv { if let config::Filter::Conv { parameters: conf } = conf { let coeffs = match conf { config::ConvParameters::Values { values } => values, - config::ConvParameters::File { filename, format } => { - filters::read_coeff_file(&filename, &format).unwrap() + config::ConvParameters::File { filename, format, read_bytes, read_lines, skip_bytes, skip_lines } => { + filters::read_coeff_file(&filename, &format, read_bytes, read_lines, skip_bytes, skip_lines).unwrap() } }; @@ -180,8 +180,8 @@ impl Filter for FFTConv { pub fn validate_config(conf: &config::ConvParameters) -> Res<()> { match conf { config::ConvParameters::Values { .. } => Ok(()), - config::ConvParameters::File { filename, format } => { - let coeffs = filters::read_coeff_file(&filename, &format)?; + config::ConvParameters::File { filename, format, read_bytes, read_lines, skip_bytes, skip_lines } => { + let coeffs = filters::read_coeff_file(&filename, &format, *read_bytes, *read_lines, *skip_bytes, *skip_lines)?; if coeffs.is_empty() { return Err(Box::new(config::ConfigError::new( "Conv coefficients are empty", From 86fa4584aa828da481321271b3b45c6ab78e6537 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Thu, 16 Jul 2020 11:33:29 +0200 Subject: [PATCH 17/61] Improve tests for coeff reading --- src/filters.rs | 37 ++++++++++++++++++++++++++++++++----- testdata/text.txt | 5 +++++ testdata/text_header.txt | 6 ++++++ 3 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 testdata/text.txt create mode 100644 testdata/text_header.txt diff --git a/src/filters.rs b/src/filters.rs index 3a907377..623cc0f4 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -327,6 +327,9 @@ mod tests { } fn compare_waveforms(left: Vec, right: Vec, maxdiff: PrcFmt) -> bool { + if left.len() != right.len() { + return false; + } for (val_l, val_r) in left.iter().zip(right.iter()) { if !is_close(*val_l, *val_r, maxdiff) { return false; @@ -337,35 +340,59 @@ mod tests { #[test] fn read_float32() { - let loaded = read_coeff_file("testdata/float32.raw", &FileFormat::FLOAT32LE).unwrap(); + let loaded = read_coeff_file("testdata/float32.raw", &FileFormat::FLOAT32LE, 0, 0, 0, 0).unwrap(); let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5, 1.0]; assert!(compare_waveforms(loaded, expected, 1e-15)); + let loaded = read_coeff_file("testdata/float32.raw", &FileFormat::FLOAT32LE, 12, 0, 4, 0).unwrap(); + let expected: Vec = vec![-0.5, 0.0, 0.5]; + assert!(compare_waveforms(loaded, expected, 1e-15)); } #[test] fn read_float64() { - let loaded = read_coeff_file("testdata/float64.raw", &FileFormat::FLOAT64LE).unwrap(); + let loaded = read_coeff_file("testdata/float64.raw", &FileFormat::FLOAT64LE, 0, 0, 0, 0).unwrap(); let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5, 1.0]; assert!(compare_waveforms(loaded, expected, 1e-15)); + let loaded = read_coeff_file("testdata/float64.raw", &FileFormat::FLOAT64LE, 24, 0, 8, 0).unwrap(); + let expected: Vec = vec![-0.5, 0.0, 0.5]; + assert!(compare_waveforms(loaded, expected, 1e-15)); } #[test] fn read_int16() { - let loaded = read_coeff_file("testdata/int16.raw", &FileFormat::S16LE).unwrap(); + let loaded = read_coeff_file("testdata/int16.raw", &FileFormat::S16LE, 0, 0, 0, 0).unwrap(); let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5, 1.0]; assert!(compare_waveforms(loaded, expected, 1e-4)); + let loaded = read_coeff_file("testdata/int16.raw", &FileFormat::S16LE, 6, 0, 2, 0).unwrap(); + let expected: Vec = vec![-0.5, 0.0, 0.5]; + assert!(compare_waveforms(loaded, expected, 1e-4)); } #[test] fn read_int24() { - let loaded = read_coeff_file("testdata/int24.raw", &FileFormat::S24LE).unwrap(); + let loaded = read_coeff_file("testdata/int24.raw", &FileFormat::S24LE, 0, 0, 0, 0).unwrap(); let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5, 1.0]; assert!(compare_waveforms(loaded, expected, 1e-6)); + let loaded = read_coeff_file("testdata/int24.raw", &FileFormat::S24LE, 12, 0, 4, 0).unwrap(); + let expected: Vec = vec![-0.5, 0.0, 0.5]; + assert!(compare_waveforms(loaded, expected, 1e-6)); } #[test] fn read_int32() { - let loaded = read_coeff_file("testdata/int32.raw", &FileFormat::S32LE).unwrap(); + let loaded = read_coeff_file("testdata/int32.raw", &FileFormat::S32LE, 0, 0, 0, 0).unwrap(); let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5, 1.0]; assert!(compare_waveforms(loaded, expected, 1e-9)); + let loaded = read_coeff_file("testdata/int32.raw", &FileFormat::S32LE, 12, 0, 4, 0).unwrap(); + let expected: Vec = vec![-0.5, 0.0, 0.5]; + assert!(compare_waveforms(loaded, expected, 1e-9)); + } + #[test] + fn read_text() { + let loaded = read_coeff_file("testdata/text.txt", &FileFormat::TEXT, 0, 0, 0, 0).unwrap(); + let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5, 1.0]; + assert!(compare_waveforms(loaded, expected, 1e-9)); + let loaded = read_coeff_file("testdata/text_header.txt", &FileFormat::TEXT, 0, 4, 0, 1).unwrap(); + let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5]; + assert!(compare_waveforms(loaded, expected, 1e-9)); } } diff --git a/testdata/text.txt b/testdata/text.txt new file mode 100644 index 00000000..d6acae64 --- /dev/null +++ b/testdata/text.txt @@ -0,0 +1,5 @@ +-1.0 +-0.5 +0.0 +0.5 +1.0 diff --git a/testdata/text_header.txt b/testdata/text_header.txt new file mode 100644 index 00000000..ff7fcc66 --- /dev/null +++ b/testdata/text_header.txt @@ -0,0 +1,6 @@ +some values +-1.0 +-0.5 +0.0 +0.5 +1.0 From 1431238d747bef7ef41d43472aa0990a0d5c0e65 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Thu, 16 Jul 2020 11:55:55 +0200 Subject: [PATCH 18/61] Simplify options, update readme --- README.md | 18 ++++++++++++ src/config.rs | 8 ++--- src/fftconv.rs | 30 ++++++++++++++----- src/fftconv_fftw.rs | 30 ++++++++++++++----- src/filters.rs | 72 ++++++++++++++++++++++----------------------- 5 files changed, 102 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 2f14a2cc..6ef56189 100644 --- a/README.md +++ b/README.md @@ -644,7 +644,25 @@ filters: type: File filename: path/to/filter.txt format: TEXT + skip_bytes_lines: 0 (*) + read_bytes_lines: 0 (*) ``` +The `type` can be "File" of "Values". Use "File" to load a file, and "Values" for giving the coefficients directly in the configuration file. + +Example for giving values: +``` +filters: + lowpass_fir: + type: Conv + parameters: + type: Values + values: [0.0, 0.1, 0.2, 0.3] +``` + +The File type supports two additional optional parameters, for advanced handling of raw files and text files with headers: +* `skip_bytes_lines`: Number of bytes (for raw files) or lines (for text) to skip at the beginning of the file. This can be used to skip over a header. Leaving it out or setting to zero means no bytes or lines are skipped. +* `read_bytes_lines`: Read only up until the specified number of bytes (for raw files) or lines (for text). Leave it out to read until the end of the file. + For testing purposes the entire "parameters" block can be left out (or commented out with a # at the start of each line). This then becomes a dummy filter that does not affect the signal. The "format" parameter can be omitted, in which case it's assumed that the format is TEXT. This format is a simple text file with one value per row: ``` diff --git a/src/config.rs b/src/config.rs index 73de5bf9..7114d1eb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -251,13 +251,9 @@ pub enum ConvParameters { #[serde(default)] format: FileFormat, #[serde(default)] - skip_bytes: usize, - #[serde(default)] - read_bytes: usize, - #[serde(default)] - skip_lines: usize, + skip_bytes_lines: usize, #[serde(default)] - read_lines: usize, + read_bytes_lines: usize, }, Values { values: Vec, diff --git a/src/fftconv.rs b/src/fftconv.rs index de3a5c0b..f12aca02 100644 --- a/src/fftconv.rs +++ b/src/fftconv.rs @@ -69,9 +69,13 @@ impl FFTConv { pub fn from_config(name: String, data_length: usize, conf: config::ConvParameters) -> Self { let values = match conf { config::ConvParameters::Values { values } => values, - config::ConvParameters::File { filename, format, read_bytes, read_lines, skip_bytes, skip_lines } => { - filters::read_coeff_file(&filename, &format, read_bytes, read_lines, skip_bytes, skip_lines).unwrap() - } + config::ConvParameters::File { + filename, + format, + read_bytes_lines, + skip_bytes_lines, + } => filters::read_coeff_file(&filename, &format, read_bytes_lines, skip_bytes_lines) + .unwrap(), }; FFTConv::new(name, data_length, &values) } @@ -134,8 +138,14 @@ impl Filter for FFTConv { if let config::Filter::Conv { parameters: conf } = conf { let coeffs = match conf { config::ConvParameters::Values { values } => values, - config::ConvParameters::File { filename, format, read_bytes, read_lines, skip_bytes, skip_lines } => { - filters::read_coeff_file(&filename, &format, read_bytes, read_lines, skip_bytes, skip_lines).unwrap() + config::ConvParameters::File { + filename, + format, + read_bytes_lines, + skip_bytes_lines, + } => { + filters::read_coeff_file(&filename, &format, read_bytes_lines, skip_bytes_lines) + .unwrap() } }; @@ -175,8 +185,14 @@ impl Filter for FFTConv { pub fn validate_config(conf: &config::ConvParameters) -> Res<()> { match conf { config::ConvParameters::Values { .. } => Ok(()), - config::ConvParameters::File { filename, format, read_bytes, read_lines, skip_bytes, skip_lines } => { - let coeffs = filters::read_coeff_file(&filename, &format, *read_bytes, *read_lines, *skip_bytes, *skip_lines)?; + config::ConvParameters::File { + filename, + format, + read_bytes_lines, + skip_bytes_lines, + } => { + let coeffs = + filters::read_coeff_file(&filename, &format, *read_bytes_lines, *skip_bytes_lines)?; if coeffs.is_empty() { return Err(Box::new(config::ConfigError::new( "Conv coefficients are empty", diff --git a/src/fftconv_fftw.rs b/src/fftconv_fftw.rs index f72f0540..df96aff7 100644 --- a/src/fftconv_fftw.rs +++ b/src/fftconv_fftw.rs @@ -82,9 +82,13 @@ impl FFTConv { pub fn from_config(name: String, data_length: usize, conf: config::ConvParameters) -> Self { let values = match conf { config::ConvParameters::Values { values } => values, - config::ConvParameters::File { filename, format, read_bytes, read_lines, skip_bytes, skip_lines } => { - filters::read_coeff_file(&filename, &format, read_bytes, read_lines, skip_bytes, skip_lines).unwrap() - } + config::ConvParameters::File { + filename, + format, + read_bytes_lines, + skip_bytes_lines, + } => filters::read_coeff_file(&filename, &format, read_bytes_lines, skip_bytes_lines) + .unwrap(), }; FFTConv::new(name, data_length, &values) } @@ -139,8 +143,14 @@ impl Filter for FFTConv { if let config::Filter::Conv { parameters: conf } = conf { let coeffs = match conf { config::ConvParameters::Values { values } => values, - config::ConvParameters::File { filename, format, read_bytes, read_lines, skip_bytes, skip_lines } => { - filters::read_coeff_file(&filename, &format, read_bytes, read_lines, skip_bytes, skip_lines).unwrap() + config::ConvParameters::File { + filename, + format, + read_bytes_lines, + skip_bytes_lines, + } => { + filters::read_coeff_file(&filename, &format, read_bytes_lines, skip_bytes_lines) + .unwrap() } }; @@ -180,8 +190,14 @@ impl Filter for FFTConv { pub fn validate_config(conf: &config::ConvParameters) -> Res<()> { match conf { config::ConvParameters::Values { .. } => Ok(()), - config::ConvParameters::File { filename, format, read_bytes, read_lines, skip_bytes, skip_lines } => { - let coeffs = filters::read_coeff_file(&filename, &format, *read_bytes, *read_lines, *skip_bytes, *skip_lines)?; + config::ConvParameters::File { + filename, + format, + read_bytes_lines, + skip_bytes_lines, + } => { + let coeffs = + filters::read_coeff_file(&filename, &format, *read_bytes_lines, *skip_bytes_lines)?; if coeffs.is_empty() { return Err(Box::new(config::ConfigError::new( "Conv coefficients are empty", diff --git a/src/filters.rs b/src/filters.rs index 623cc0f4..f584d88d 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -27,34 +27,32 @@ pub trait Filter { fn name(&self) -> String; } -pub fn read_coeff_file(filename: &str, format: &config::FileFormat, read_bytes: usize, read_lines: usize, skip_bytes: usize, skip_lines: usize) -> Res> { +pub fn read_coeff_file( + filename: &str, + format: &config::FileFormat, + read_bytes_lines: usize, + skip_bytes_lines: usize, +) -> Res> { let mut coefficients = Vec::::new(); let f = File::open(filename)?; let mut file = BufReader::new(&f); - let read_bytes = if read_bytes > 0 { - read_bytes - } - else { - usize::MAX - }; - let read_lines = if read_lines > 0 { - read_lines - } - else { + let read_bytes_lines = if read_bytes_lines > 0 { + read_bytes_lines + } else { usize::MAX }; match format { config::FileFormat::TEXT => { - for line in file.lines().skip(skip_lines).take(read_lines) { + for line in file.lines().skip(skip_bytes_lines).take(read_bytes_lines) { let l = line?; coefficients.push(l.trim().parse()?); } } config::FileFormat::FLOAT32LE => { let mut buffer = [0; 4]; - file.seek(SeekFrom::Start(skip_bytes as u64))?; - let nbr_coeffs = read_bytes/4; + file.seek(SeekFrom::Start(skip_bytes_lines as u64))?; + let nbr_coeffs = read_bytes_lines / 4; while let Ok(4) = file.read(&mut buffer) { let value = f32::from_le_bytes(buffer) as PrcFmt; coefficients.push(value); @@ -65,8 +63,8 @@ pub fn read_coeff_file(filename: &str, format: &config::FileFormat, read_bytes: } config::FileFormat::FLOAT64LE => { let mut buffer = [0; 8]; - file.seek(SeekFrom::Start(skip_bytes as u64))?; - let nbr_coeffs = read_bytes/8; + file.seek(SeekFrom::Start(skip_bytes_lines as u64))?; + let nbr_coeffs = read_bytes_lines / 8; while let Ok(8) = file.read(&mut buffer) { let value = f64::from_le_bytes(buffer) as PrcFmt; coefficients.push(value); @@ -77,8 +75,8 @@ pub fn read_coeff_file(filename: &str, format: &config::FileFormat, read_bytes: } config::FileFormat::S16LE => { let mut buffer = [0; 2]; - file.seek(SeekFrom::Start(skip_bytes as u64))?; - let nbr_coeffs = read_bytes/2; + file.seek(SeekFrom::Start(skip_bytes_lines as u64))?; + let nbr_coeffs = read_bytes_lines / 2; let scalefactor = (2.0 as PrcFmt).powi(15); while let Ok(2) = file.read(&mut buffer) { let mut value = i16::from_le_bytes(buffer) as PrcFmt; @@ -91,8 +89,8 @@ pub fn read_coeff_file(filename: &str, format: &config::FileFormat, read_bytes: } config::FileFormat::S24LE => { let mut buffer = [0; 4]; - file.seek(SeekFrom::Start(skip_bytes as u64))?; - let nbr_coeffs = read_bytes/4; + file.seek(SeekFrom::Start(skip_bytes_lines as u64))?; + let nbr_coeffs = read_bytes_lines / 4; let scalefactor = (2.0 as PrcFmt).powi(23); while let Ok(4) = file.read(&mut buffer) { let mut value = i32::from_le_bytes(buffer) as PrcFmt; @@ -105,8 +103,8 @@ pub fn read_coeff_file(filename: &str, format: &config::FileFormat, read_bytes: } config::FileFormat::S24LE3 => { let mut buffer = [0; 4]; - file.seek(SeekFrom::Start(skip_bytes as u64))?; - let nbr_coeffs = read_bytes/3; + file.seek(SeekFrom::Start(skip_bytes_lines as u64))?; + let nbr_coeffs = read_bytes_lines / 3; let scalefactor = (2.0 as PrcFmt).powi(23); while let Ok(3) = file.read(&mut buffer[0..3]) { let mut value = i32::from_le_bytes(buffer) as PrcFmt; @@ -119,8 +117,8 @@ pub fn read_coeff_file(filename: &str, format: &config::FileFormat, read_bytes: } config::FileFormat::S32LE => { let mut buffer = [0; 4]; - file.seek(SeekFrom::Start(skip_bytes as u64))?; - let nbr_coeffs = read_bytes/4; + file.seek(SeekFrom::Start(skip_bytes_lines as u64))?; + let nbr_coeffs = read_bytes_lines / 4; let scalefactor = (2.0 as PrcFmt).powi(31); while let Ok(4) = file.read(&mut buffer) { let mut value = i32::from_le_bytes(buffer) as PrcFmt; @@ -340,58 +338,60 @@ mod tests { #[test] fn read_float32() { - let loaded = read_coeff_file("testdata/float32.raw", &FileFormat::FLOAT32LE, 0, 0, 0, 0).unwrap(); + let loaded = read_coeff_file("testdata/float32.raw", &FileFormat::FLOAT32LE, 0, 0).unwrap(); let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5, 1.0]; assert!(compare_waveforms(loaded, expected, 1e-15)); - let loaded = read_coeff_file("testdata/float32.raw", &FileFormat::FLOAT32LE, 12, 0, 4, 0).unwrap(); + let loaded = + read_coeff_file("testdata/float32.raw", &FileFormat::FLOAT32LE, 12, 4).unwrap(); let expected: Vec = vec![-0.5, 0.0, 0.5]; assert!(compare_waveforms(loaded, expected, 1e-15)); } #[test] fn read_float64() { - let loaded = read_coeff_file("testdata/float64.raw", &FileFormat::FLOAT64LE, 0, 0, 0, 0).unwrap(); + let loaded = read_coeff_file("testdata/float64.raw", &FileFormat::FLOAT64LE, 0, 0).unwrap(); let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5, 1.0]; assert!(compare_waveforms(loaded, expected, 1e-15)); - let loaded = read_coeff_file("testdata/float64.raw", &FileFormat::FLOAT64LE, 24, 0, 8, 0).unwrap(); + let loaded = + read_coeff_file("testdata/float64.raw", &FileFormat::FLOAT64LE, 24, 8).unwrap(); let expected: Vec = vec![-0.5, 0.0, 0.5]; assert!(compare_waveforms(loaded, expected, 1e-15)); } #[test] fn read_int16() { - let loaded = read_coeff_file("testdata/int16.raw", &FileFormat::S16LE, 0, 0, 0, 0).unwrap(); + let loaded = read_coeff_file("testdata/int16.raw", &FileFormat::S16LE, 0, 0).unwrap(); let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5, 1.0]; assert!(compare_waveforms(loaded, expected, 1e-4)); - let loaded = read_coeff_file("testdata/int16.raw", &FileFormat::S16LE, 6, 0, 2, 0).unwrap(); + let loaded = read_coeff_file("testdata/int16.raw", &FileFormat::S16LE, 6, 2).unwrap(); let expected: Vec = vec![-0.5, 0.0, 0.5]; assert!(compare_waveforms(loaded, expected, 1e-4)); } #[test] fn read_int24() { - let loaded = read_coeff_file("testdata/int24.raw", &FileFormat::S24LE, 0, 0, 0, 0).unwrap(); + let loaded = read_coeff_file("testdata/int24.raw", &FileFormat::S24LE, 0, 0).unwrap(); let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5, 1.0]; assert!(compare_waveforms(loaded, expected, 1e-6)); - let loaded = read_coeff_file("testdata/int24.raw", &FileFormat::S24LE, 12, 0, 4, 0).unwrap(); + let loaded = read_coeff_file("testdata/int24.raw", &FileFormat::S24LE, 12, 4).unwrap(); let expected: Vec = vec![-0.5, 0.0, 0.5]; assert!(compare_waveforms(loaded, expected, 1e-6)); } #[test] fn read_int32() { - let loaded = read_coeff_file("testdata/int32.raw", &FileFormat::S32LE, 0, 0, 0, 0).unwrap(); + let loaded = read_coeff_file("testdata/int32.raw", &FileFormat::S32LE, 0, 0).unwrap(); let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5, 1.0]; assert!(compare_waveforms(loaded, expected, 1e-9)); - let loaded = read_coeff_file("testdata/int32.raw", &FileFormat::S32LE, 12, 0, 4, 0).unwrap(); + let loaded = read_coeff_file("testdata/int32.raw", &FileFormat::S32LE, 12, 4).unwrap(); let expected: Vec = vec![-0.5, 0.0, 0.5]; assert!(compare_waveforms(loaded, expected, 1e-9)); } #[test] fn read_text() { - let loaded = read_coeff_file("testdata/text.txt", &FileFormat::TEXT, 0, 0, 0, 0).unwrap(); + let loaded = read_coeff_file("testdata/text.txt", &FileFormat::TEXT, 0, 0).unwrap(); let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5, 1.0]; assert!(compare_waveforms(loaded, expected, 1e-9)); - let loaded = read_coeff_file("testdata/text_header.txt", &FileFormat::TEXT, 0, 4, 0, 1).unwrap(); + let loaded = read_coeff_file("testdata/text_header.txt", &FileFormat::TEXT, 4, 1).unwrap(); let expected: Vec = vec![-1.0, -0.5, 0.0, 0.5]; assert!(compare_waveforms(loaded, expected, 1e-9)); } From 2124bb5f4e07681a7e5872e3e2805240b79c6325 Mon Sep 17 00:00:00 2001 From: Henrik Enquist Date: Thu, 16 Jul 2020 21:59:11 +0200 Subject: [PATCH 19/61] Update to cpal 0.12, wip --- Cargo.toml | 2 +- src/cpaldevice.rs | 279 +++++++++++++++++++++------------------------- 2 files changed, 128 insertions(+), 153 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3aa41666..1ee1b61e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ ws = { version = "0.9.1", optional = true } libpulse-binding = { version = "2.0", optional = true } libpulse-simple-binding = { version = "2.0", optional = true } rubato = "0.4.3" -cpal = { version = "0.11.0", optional = true } +cpal = { version = "0.12.0", optional = true } [dev-dependencies] criterion = "0.3" diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index 7a6fbb02..b887e283 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -5,9 +5,9 @@ use conversions::{ chunk_to_queue_float, chunk_to_queue_int, queue_to_chunk_float, queue_to_chunk_int, }; use cpal; -use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait}; -use cpal::{ChannelCount, Format, HostId, SampleRate}; -use cpal::{Device, EventLoop, Host}; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use cpal::{ChannelCount, StreamConfig, HostId, SampleRate}; +use cpal::{Device, Host}; use rubato::Resampler; use std::collections::VecDeque; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -64,7 +64,7 @@ fn open_cpal_playback( samplerate: usize, channels: usize, format: &SampleFormat, -) -> Res<(Host, Device, EventLoop)> { +) -> Res<(Device, StreamConfig, cpal::SampleFormat)> { let host_id = match host_cfg { #[cfg(target_os = "macos")] CpalHost::CoreAudio => HostId::CoreAudio, @@ -85,21 +85,20 @@ fn open_cpal_playback( )))) } }; - let data_type = match format { + let sample_format = match format { SampleFormat::S16LE => cpal::SampleFormat::I16, SampleFormat::FLOAT32LE => cpal::SampleFormat::F32, _ => panic!("Unsupported sample format"), }; - let format = Format { + let stream_config = StreamConfig { channels: channels as ChannelCount, sample_rate: SampleRate(samplerate as u32), - data_type, }; - let event_loop = host.event_loop(); - let stream_id = event_loop.build_output_stream(&device, &format)?; - event_loop.play_stream(stream_id.clone())?; + //let event_loop = host.event_loop(); + //let stream_id = event_loop.build_output_stream(&device, &format)?; + //event_loop.play_stream(stream_id.clone())?; debug!("Opened CPAL playback device {}", devname); - Ok((host, device, event_loop)) + Ok((device, stream_config, sample_format)) } fn open_cpal_capture( @@ -108,7 +107,7 @@ fn open_cpal_capture( samplerate: usize, channels: usize, format: &SampleFormat, -) -> Res<(Host, Device, EventLoop)> { +) -> Res<(Device, StreamConfig, cpal::SampleFormat)> { let host_id = match host_cfg { #[cfg(target_os = "macos")] CpalHost::CoreAudio => HostId::CoreAudio, @@ -129,21 +128,19 @@ fn open_cpal_capture( )))) } }; - let data_type = match format { + let sample_format = match format { SampleFormat::S16LE => cpal::SampleFormat::I16, SampleFormat::FLOAT32LE => cpal::SampleFormat::F32, _ => panic!("Unsupported sample format"), }; - let format = Format { + let stream_config = StreamConfig { channels: channels as ChannelCount, sample_rate: SampleRate(samplerate as u32), - data_type, }; - let event_loop = host.event_loop(); - let stream_id = event_loop.build_input_stream(&device, &format)?; - event_loop.play_stream(stream_id.clone())?; + //let stream_id = event_loop.build_input_stream(&device, &format)?; + //event_loop.play_stream(stream_id.clone())?; debug!("Opened CPAL capture device {}", devname); - Ok((host, device, event_loop)) + Ok((device, stream_config, sample_format)) } fn write_data_to_device(output: &mut [T], queue: &mut VecDeque) @@ -192,14 +189,13 @@ impl PlaybackDevice for CpalPlaybackDevice { .name("CpalPlayback".to_string()) .spawn(move || { match open_cpal_playback(host_cfg, &devname, samplerate, channels, &format) { - Ok((_host, _device, event_loop)) => { + Ok((device, stream_config, sample_format)) => { match status_channel.send(StatusMessage::PlaybackReady) { Ok(()) => {} Err(_err) => {} } let scalefactor = (2.0 as PrcFmt).powi(bits - 1); - barrier.wait(); - debug!("Starting playback loop"); + let (tx_dev, rx_dev) = mpsc::sync_channel(1); let buffer_fill = Arc::new(AtomicUsize::new(0)); let buffer_fill_clone = buffer_fill.clone(); @@ -210,95 +206,77 @@ impl PlaybackDevice for CpalPlaybackDevice { let mut speed; let mut diff: isize; - match format { + let stream = match format { SampleFormat::S16LE => { + trace!("Build i16 output stream"); let mut sample_queue: VecDeque = VecDeque::with_capacity(4 * chunksize_clone * channels_clone); - std::thread::spawn(move || { - event_loop.run(move |id, result| { - let data = match result { - Ok(data) => data, - Err(err) => { - error!( - "an error occurred on stream {:?}: {}", - id, err - ); - return; - } - }; - match data { - cpal::StreamData::Output { - buffer: - cpal::UnknownTypeOutputBuffer::I16(mut buffer), - } => { - trace!( - "Playback device requests {} samples", - buffer.len() - ); - while sample_queue.len() < buffer.len() { - trace!("Convert chunk to device format"); - let chunk = rx_dev.recv().unwrap(); - chunk_to_queue_int( - chunk, - &mut sample_queue, - scalefactor, - ); - } - write_data_to_device( - &mut buffer, - &mut sample_queue, - ); - buffer_fill_clone - .store(sample_queue.len(), Ordering::Relaxed); - } - _ => (), - }; - }); - }); - } + let stream = device.build_output_stream( + &stream_config, + move |mut buffer: &mut [i16], _: &cpal::OutputCallbackInfo| { + trace!( + "Playback device requests {} samples", + buffer.len() + ); + while sample_queue.len() < buffer.len() { + trace!("Convert chunk to device format"); + let chunk = rx_dev.recv().unwrap(); + chunk_to_queue_int( + chunk, + &mut sample_queue, + scalefactor, + ); + } + write_data_to_device( + &mut buffer, + &mut sample_queue, + ); + buffer_fill_clone + .store(sample_queue.len(), Ordering::Relaxed); + }, + move |err| error!("an error occurred on stream: {}", err) + ).unwrap(); + //stream.play().unwrap(); + trace!("i16 output stream ready"); + stream + }, SampleFormat::FLOAT32LE => { + trace!("Build f32 output stream"); let mut sample_queue: VecDeque = VecDeque::with_capacity(4 * chunksize_clone * channels_clone); - std::thread::spawn(move || { - event_loop.run(move |id, result| { - let data = match result { - Ok(data) => data, - Err(err) => { - error!( - "an error occurred on stream {:?}: {}", - id, err - ); - return; - } - }; - match data { - cpal::StreamData::Output { - buffer: - cpal::UnknownTypeOutputBuffer::F32(mut buffer), - } => { - trace!( - "Playback device requests {} samples", - buffer.len() - ); - while sample_queue.len() < buffer.len() { - trace!("Convert chunk to device format"); - let chunk = rx_dev.recv().unwrap(); - chunk_to_queue_float(chunk, &mut sample_queue); - } - write_data_to_device( - &mut buffer, - &mut sample_queue, - ); - buffer_fill_clone - .store(sample_queue.len(), Ordering::Relaxed); - } - _ => (), - }; - }); - }); - } + let stream = device.build_output_stream( + &stream_config, + move |mut buffer: &mut [f32], _: &cpal::OutputCallbackInfo| { + trace!( + "Playback device requests {} samples", + buffer.len() + ); + while sample_queue.len() < buffer.len() { + trace!("Convert chunk to device format"); + let chunk = rx_dev.recv().unwrap(); + chunk_to_queue_float( + chunk, + &mut sample_queue, + ); + } + write_data_to_device( + &mut buffer, + &mut sample_queue, + ); + buffer_fill_clone + .store(sample_queue.len(), Ordering::Relaxed); + }, + move |err| error!("an error occurred on stream: {}", err) + ).unwrap(); + //stream.play().unwrap(); + trace!("f32 output stream ready"); + stream + }, _ => panic!("Unsupported sample format!"), - } + }; + barrier.wait(); + debug!("Starting playback loop"); + stream.play().unwrap(); loop { match channel.recv() { Ok(AudioMessage::Audio(chunk)) => { @@ -410,7 +388,7 @@ impl CaptureDevice for CpalCaptureDevice { silence = silence.powf(self.silence_threshold / 20.0); let silent_limit = (self.silence_timeout * ((samplerate / chunksize) as PrcFmt)) as usize; let handle = thread::Builder::new() - .name("PulseCapture".to_string()) + .name("CpalCapture".to_string()) .spawn(move || { let mut resampler = if enable_resampling { debug!("Creating resampler"); @@ -425,64 +403,61 @@ impl CaptureDevice for CpalCaptureDevice { None }; match open_cpal_capture(host_cfg, &devname, capture_samplerate, channels, &format) { - Ok((_host, _device, event_loop)) => { + Ok((device, stream_config, sample_format)) => { match status_channel.send(StatusMessage::CaptureReady) { Ok(()) => {} Err(_err) => {} } let scalefactor = (2.0 as PrcFmt).powi(bits - 1); let mut silent_nbr: usize = 0; - barrier.wait(); - debug!("starting captureloop"); let (tx_dev_i, rx_dev_i) = mpsc::sync_channel(1); let (tx_dev_f, rx_dev_f) = mpsc::sync_channel(1); - match format { + let stream = match format { SampleFormat::S16LE => { - std::thread::spawn(move || { - event_loop.run(move |id, result| { - let data = match result { - Ok(data) => data, - Err(err) => { - error!("an error occurred on stream {:?}: {}", id, err); - return; - } - }; - match data { - cpal::StreamData::Input { buffer: cpal::UnknownTypeInputBuffer::I16(buffer) } => { - trace!("Capture device provides {} samples", buffer.len()); - let mut buffer_copy = Vec::new(); - buffer_copy.extend_from_slice(&buffer); - tx_dev_i.send(buffer_copy).unwrap(); - }, - _ => (), - }; - }); - }); + trace!("Build i16 input stream"); + let stream = device.build_input_stream( + &stream_config, + move |buffer: &[i16], _: &cpal::InputCallbackInfo| { + trace!( + "Playback device requests {} samples", + buffer.len() + ); + trace!("Capture device provides {} samples", buffer.len()); + let mut buffer_copy = Vec::new(); + buffer_copy.extend_from_slice(&buffer); + tx_dev_i.send(buffer_copy).unwrap(); + }, + move |err| error!("an error occurred on stream: {}", err) + ).unwrap(); + //stream.play().unwrap(); + trace!("i16 input stream ready"); + stream }, SampleFormat::FLOAT32LE => { - std::thread::spawn(move || { - event_loop.run(move |id, result| { - let data = match result { - Ok(data) => data, - Err(err) => { - error!("an error occurred on stream {:?}: {}", id, err); - return; - } - }; - match data { - cpal::StreamData::Input { buffer: cpal::UnknownTypeInputBuffer::F32(buffer) } => { - trace!("Capture device provides {} samples", buffer.len()); - let mut buffer_copy = Vec::new(); - buffer_copy.extend_from_slice(&buffer); - tx_dev_f.send(buffer_copy).unwrap(); - }, - _ => (), - }; - }); - }); + trace!("Build f32 input stream"); + let stream = device.build_input_stream( + &stream_config, + move |buffer: &[f32], _: &cpal::InputCallbackInfo| { + trace!( + "Playback device requests {} samples", + buffer.len() + ); + trace!("Capture device provides {} samples", buffer.len()); + let mut buffer_copy = Vec::new(); + buffer_copy.extend_from_slice(&buffer); + tx_dev_f.send(buffer_copy).unwrap(); + }, + move |err| error!("an error occurred on stream: {}", err) + ).unwrap(); + //stream.play().unwrap(); + trace!("f32 input stream ready"); + stream }, _ => panic!("Unsupported sample format!"), - } + }; + barrier.wait(); + debug!("starting captureloop"); + stream.play().unwrap(); let chunksize_samples = channels * chunksize; let mut capture_samples = chunksize_samples; let mut sample_queue_i: VecDeque = VecDeque::with_capacity(2*chunksize*channels); @@ -523,7 +498,7 @@ impl CaptureDevice for CpalCaptureDevice { let mut chunk = match format { SampleFormat::S16LE => { while sample_queue_i.len() < capture_samples { - trace!("Read buffer message"); + trace!("Read message to fill capture buffer"); match rx_dev_i.recv() { Ok(buf) => { write_data_from_device(&buf, &mut sample_queue_i); @@ -546,7 +521,7 @@ impl CaptureDevice for CpalCaptureDevice { }, SampleFormat::FLOAT32LE => { while sample_queue_f.len() < capture_samples { - trace!("Read buffer message"); + trace!("Read message to fill capture buffer"); match rx_dev_f.recv() { Ok(buf) => { write_data_from_device(&buf, &mut sample_queue_f); From a727b6a1034d65f904344e4bef628529e4cbefe5 Mon Sep 17 00:00:00 2001 From: Henrik Enquist Date: Thu, 16 Jul 2020 22:29:02 +0200 Subject: [PATCH 20/61] Better error handling for cpal --- src/cpaldevice.rs | 97 +++++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 41 deletions(-) diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index b887e283..686d1d56 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -6,8 +6,8 @@ use conversions::{ }; use cpal; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use cpal::{ChannelCount, StreamConfig, HostId, SampleRate}; -use cpal::{Device, Host}; +use cpal::Device; +use cpal::{ChannelCount, HostId, SampleRate, StreamConfig}; use rubato::Resampler; use std::collections::VecDeque; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -60,7 +60,7 @@ pub struct CpalCaptureDevice { fn open_cpal_playback( host_cfg: CpalHost, - devname: &String, + devname: &str, samplerate: usize, channels: usize, format: &SampleFormat, @@ -74,7 +74,7 @@ fn open_cpal_playback( let host = cpal::host_from_id(host_id)?; let mut devices = host.devices()?; let device = match devices.find(|dev| match dev.name() { - Ok(n) => &n == devname, + Ok(n) => n == devname, _ => false, }) { Some(dev) => dev, @@ -103,7 +103,7 @@ fn open_cpal_playback( fn open_cpal_capture( host_cfg: CpalHost, - devname: &String, + devname: &str, samplerate: usize, channels: usize, format: &SampleFormat, @@ -117,7 +117,7 @@ fn open_cpal_capture( let host = cpal::host_from_id(host_id)?; let mut devices = host.devices()?; let device = match devices.find(|dev| match dev.name() { - Ok(n) => &n == devname, + Ok(n) => n == devname, _ => false, }) { Some(dev) => dev, @@ -189,13 +189,13 @@ impl PlaybackDevice for CpalPlaybackDevice { .name("CpalPlayback".to_string()) .spawn(move || { match open_cpal_playback(host_cfg, &devname, samplerate, channels, &format) { - Ok((device, stream_config, sample_format)) => { + Ok((device, stream_config, _sample_format)) => { match status_channel.send(StatusMessage::PlaybackReady) { Ok(()) => {} Err(_err) => {} } let scalefactor = (2.0 as PrcFmt).powi(bits - 1); - + let (tx_dev, rx_dev) = mpsc::sync_channel(1); let buffer_fill = Arc::new(AtomicUsize::new(0)); let buffer_fill_clone = buffer_fill.clone(); @@ -214,10 +214,7 @@ impl PlaybackDevice for CpalPlaybackDevice { let stream = device.build_output_stream( &stream_config, move |mut buffer: &mut [i16], _: &cpal::OutputCallbackInfo| { - trace!( - "Playback device requests {} samples", - buffer.len() - ); + trace!("Playback device requests {} samples", buffer.len()); while sample_queue.len() < buffer.len() { trace!("Convert chunk to device format"); let chunk = rx_dev.recv().unwrap(); @@ -227,19 +224,16 @@ impl PlaybackDevice for CpalPlaybackDevice { scalefactor, ); } - write_data_to_device( - &mut buffer, - &mut sample_queue, - ); + write_data_to_device(&mut buffer, &mut sample_queue); buffer_fill_clone .store(sample_queue.len(), Ordering::Relaxed); }, - move |err| error!("an error occurred on stream: {}", err) - ).unwrap(); + move |err| error!("an error occurred on stream: {}", err), + ); //stream.play().unwrap(); trace!("i16 output stream ready"); stream - }, + } SampleFormat::FLOAT32LE => { trace!("Build f32 output stream"); let mut sample_queue: VecDeque = @@ -247,36 +241,42 @@ impl PlaybackDevice for CpalPlaybackDevice { let stream = device.build_output_stream( &stream_config, move |mut buffer: &mut [f32], _: &cpal::OutputCallbackInfo| { - trace!( - "Playback device requests {} samples", - buffer.len() - ); + trace!("Playback device requests {} samples", buffer.len()); while sample_queue.len() < buffer.len() { trace!("Convert chunk to device format"); let chunk = rx_dev.recv().unwrap(); - chunk_to_queue_float( - chunk, - &mut sample_queue, - ); + chunk_to_queue_float(chunk, &mut sample_queue); } - write_data_to_device( - &mut buffer, - &mut sample_queue, - ); + write_data_to_device(&mut buffer, &mut sample_queue); buffer_fill_clone .store(sample_queue.len(), Ordering::Relaxed); }, - move |err| error!("an error occurred on stream: {}", err) - ).unwrap(); + move |err| error!("an error occurred on stream: {}", err), + ); //stream.play().unwrap(); trace!("f32 output stream ready"); stream - }, + } _ => panic!("Unsupported sample format!"), }; + if let Err(err) = &stream { + status_channel + .send(StatusMessage::PlaybackError { + message: format!("{}", err), + }) + .unwrap(); + } barrier.wait(); - debug!("Starting playback loop"); - stream.play().unwrap(); + if let Ok(strm) = &stream { + match strm.play() { + Ok(_) => debug!("Starting playback loop"), + Err(err) => status_channel + .send(StatusMessage::PlaybackError { + message: format!("{}", err), + }) + .unwrap(), + } + } loop { match channel.recv() { Ok(AudioMessage::Audio(chunk)) => { @@ -403,7 +403,7 @@ impl CaptureDevice for CpalCaptureDevice { None }; match open_cpal_capture(host_cfg, &devname, capture_samplerate, channels, &format) { - Ok((device, stream_config, sample_format)) => { + Ok((device, stream_config, _sample_format)) => { match status_channel.send(StatusMessage::CaptureReady) { Ok(()) => {} Err(_err) => {} @@ -428,7 +428,7 @@ impl CaptureDevice for CpalCaptureDevice { tx_dev_i.send(buffer_copy).unwrap(); }, move |err| error!("an error occurred on stream: {}", err) - ).unwrap(); + ); //stream.play().unwrap(); trace!("i16 input stream ready"); stream @@ -448,16 +448,31 @@ impl CaptureDevice for CpalCaptureDevice { tx_dev_f.send(buffer_copy).unwrap(); }, move |err| error!("an error occurred on stream: {}", err) - ).unwrap(); + ); //stream.play().unwrap(); trace!("f32 input stream ready"); stream }, _ => panic!("Unsupported sample format!"), }; + if let Err(err) = &stream { + status_channel + .send(StatusMessage::CaptureError { + message: format!("{}", err), + }) + .unwrap(); + } barrier.wait(); - debug!("starting captureloop"); - stream.play().unwrap(); + if let Ok(strm) = &stream { + match strm.play() { + Ok(_) => debug!("Starting capture loop"), + Err(err) => status_channel + .send(StatusMessage::CaptureError { + message: format!("{}", err), + }) + .unwrap(), + } + } let chunksize_samples = channels * chunksize; let mut capture_samples = chunksize_samples; let mut sample_queue_i: VecDeque = VecDeque::with_capacity(2*chunksize*channels); From cc48086b789fc2eeae92b2154c709e098739f94c Mon Sep 17 00:00:00 2001 From: henrik Date: Tue, 21 Jul 2020 22:23:40 +0200 Subject: [PATCH 21/61] Update readme for coreaudio --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2f14a2cc..1cff1c4d 100644 --- a/README.md +++ b/README.md @@ -306,21 +306,19 @@ Set VB-CABLE as the default playback device in the control panel, and let Camill The device name is the same as seen in the Windows volume control. For example, the VB-CABLE device name is "CABLE Output (VB-Audio Virtual Cable)". The device name is built from the inpout/output name and card name, and the format is "{input/output name} ({card name})". -The sample format appears to always be 32-bit float (FLOAT32LE). +The sample format appears to always be 32-bit float (FLOAT32LE) even if the device is configured to use aother format. The sample rate must match the default format of the device. To change this, open "Sound" in the Control panel, select the sound card, and click "Properties". Then open the "Advanced" tab and select the desired format under "Default Format". ## CoreAudio -CoreAudio is supported by the cpal library and should work, but hasn't actually been tested. - -To capture audio from applications a virtual sound card is needed. See for example [BlackHole](https://github.com/ExistentialAudio/BlackHole). +To capture audio from applications a virtual sound card is needed. This has been verified to work with [BlackHole](https://github.com/ExistentialAudio/BlackHole). Set the virtual sound card as the default playback device in the Sound preferences, and let CamillaDSP capture from the output of this card. -The device name is the same as the one shown in System Preferences / Sound. +The device name is the same as the one shown in the "Audio MIDI Setup" that can be found under "Other" in Launchpad. The name for BlackHole is "BlackHole 16ch", and the build in audio in a MacBook Pro is called "Built-in Output". -The sample format appears to always be 32-bit float (FLOAT32LE). +The sample format appears to always be 32-bit float (FLOAT32LE) even if the device is configured to use aother format. # Configuration From c9d286b282e1bee05b9124dd1159ca6a9380602e Mon Sep 17 00:00:00 2001 From: henrik Date: Tue, 21 Jul 2020 22:50:06 +0200 Subject: [PATCH 22/61] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1cff1c4d..b380c9a4 100644 --- a/README.md +++ b/README.md @@ -316,7 +316,7 @@ To capture audio from applications a virtual sound card is needed. This has been Set the virtual sound card as the default playback device in the Sound preferences, and let CamillaDSP capture from the output of this card. -The device name is the same as the one shown in the "Audio MIDI Setup" that can be found under "Other" in Launchpad. The name for BlackHole is "BlackHole 16ch", and the build in audio in a MacBook Pro is called "Built-in Output". +The device name is the same as the one shown in the "Audio MIDI Setup" that can be found under "Other" in Launchpad. The name for BlackHole is "BlackHole 16ch", and the built in audio in a MacBook Pro is called "Built-in Output". The sample format appears to always be 32-bit float (FLOAT32LE) even if the device is configured to use aother format. From 2d344001974292ddf5bd05003e5a27455fa1f6c0 Mon Sep 17 00:00:00 2001 From: Henrik Enquist Date: Thu, 23 Jul 2020 21:50:48 +0200 Subject: [PATCH 23/61] Updates for cpal 0.12.1 --- Cargo.toml | 4 ++-- src/cpaldevice.rs | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1ee1b61e..a6868795 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "camilladsp" -version = "0.3.1" +version = "0.3.2" authors = ["Henrik Enquist "] description = "A flexible tool for processing audio" @@ -43,7 +43,7 @@ ws = { version = "0.9.1", optional = true } libpulse-binding = { version = "2.0", optional = true } libpulse-simple-binding = { version = "2.0", optional = true } rubato = "0.4.3" -cpal = { version = "0.12.0", optional = true } +cpal = { version = "0.12.1", optional = true } [dev-dependencies] criterion = "0.3" diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index 686d1d56..de2eea9e 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -7,7 +7,7 @@ use conversions::{ use cpal; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::Device; -use cpal::{ChannelCount, HostId, SampleRate, StreamConfig}; +use cpal::{ChannelCount, HostId, SampleRate, StreamConfig, BufferSize}; use rubato::Resampler; use std::collections::VecDeque; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -93,10 +93,8 @@ fn open_cpal_playback( let stream_config = StreamConfig { channels: channels as ChannelCount, sample_rate: SampleRate(samplerate as u32), + buffer_size: BufferSize::Default, }; - //let event_loop = host.event_loop(); - //let stream_id = event_loop.build_output_stream(&device, &format)?; - //event_loop.play_stream(stream_id.clone())?; debug!("Opened CPAL playback device {}", devname); Ok((device, stream_config, sample_format)) } @@ -136,9 +134,8 @@ fn open_cpal_capture( let stream_config = StreamConfig { channels: channels as ChannelCount, sample_rate: SampleRate(samplerate as u32), + buffer_size: BufferSize::Default, }; - //let stream_id = event_loop.build_input_stream(&device, &format)?; - //event_loop.play_stream(stream_id.clone())?; debug!("Opened CPAL capture device {}", devname); Ok((device, stream_config, sample_format)) } From 40596de6e5d7390079ad20c36eb5ccf10fd6cf64 Mon Sep 17 00:00:00 2001 From: Henrik Enquist Date: Thu, 23 Jul 2020 21:55:23 +0200 Subject: [PATCH 24/61] Format --- src/cpaldevice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index de2eea9e..17fb8a94 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -7,7 +7,7 @@ use conversions::{ use cpal; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::Device; -use cpal::{ChannelCount, HostId, SampleRate, StreamConfig, BufferSize}; +use cpal::{BufferSize, ChannelCount, HostId, SampleRate, StreamConfig}; use rubato::Resampler; use std::collections::VecDeque; use std::sync::atomic::{AtomicUsize, Ordering}; From 73e66a6cee21ba2448ef3612e41fb2d5e3243505 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Sun, 26 Jul 2020 13:39:52 +0200 Subject: [PATCH 25/61] Add new commands for state etc --- src/alsadevice.rs | 2 +- src/bin.rs | 4 ++++ src/cpaldevice.rs | 2 +- src/filedevice.rs | 2 +- src/lib.rs | 21 ++++++++++++++++++++- src/pulsedevice.rs | 5 ++++- src/socketserver.rs | 18 ++++++++++++++++++ 7 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/alsadevice.rs b/src/alsadevice.rs index aab5d276..14d6ce46 100644 --- a/src/alsadevice.rs +++ b/src/alsadevice.rs @@ -341,7 +341,7 @@ fn capture_loop_bytes( trace!("Measured sample rate is {} Hz", measured_rate_f); let mut capt_stat = params.capture_status.write().unwrap(); capt_stat.measured_samplerate = measured_rate_f as usize; - capt_stat.signal_range = value_range; + capt_stat.signal_range = value_range as f32; start = now; bytes_counter = 0; } diff --git a/src/bin.rs b/src/bin.rs index d08e9329..d5027c79 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -49,6 +49,8 @@ use camillalib::CommandMessage; use camillalib::ExitStatus; use camillalib::CaptureStatus; +use camillalib::ProcessingState; + fn get_new_config( config_path: &Arc>>, @@ -399,6 +401,8 @@ fn main() { measured_samplerate: 0, update_interval: 1000, signal_range: 0.0, + rate_adjust: 0.0, + state: ProcessingState::Inactive, })); //let active_config = Arc::new(Mutex::new(String::new())); let active_config = Arc::new(Mutex::new(None)); diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index 17fb8a94..3b60dff2 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -568,7 +568,7 @@ impl CaptureDevice for CpalCaptureDevice { ); let mut capt_stat = capture_status.write().unwrap(); capt_stat.measured_samplerate = measured_rate_f as usize; - capt_stat.signal_range = value_range; + capt_stat.signal_range = value_range as f32; start = now; sample_counter = 0; } diff --git a/src/filedevice.rs b/src/filedevice.rs index 117824bd..87a634fc 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -339,7 +339,7 @@ fn capture_loop( trace!("Measured sample rate is {} Hz", measured_rate_f); let mut capt_stat = params.capture_status.write().unwrap(); capt_stat.measured_samplerate = measured_rate_f as usize; - capt_stat.signal_range = value_range; + capt_stat.signal_range = value_range as f32; start = now; bytes_counter = 0; } diff --git a/src/lib.rs b/src/lib.rs index ed760de0..422298b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,9 +81,28 @@ pub enum ExitStatus { Exit, } +#[derive(Clone, Debug)] +pub enum ProcessingState { + Running, + Paused, + Inactive, +} + #[derive(Clone, Debug)] pub struct CaptureStatus { pub update_interval: usize, pub measured_samplerate: usize, - pub signal_range: PrcFmt, + pub signal_range: f32, + pub state: ProcessingState, + pub rate_adjust: f32, +} + +impl ProcessingState { + pub fn to_string(&self) -> String { + match self { + ProcessingState::Running => String::from("RUNNING"), + ProcessingState::Paused => String::from("PAUSED"), + ProcessingState::Inactive => String::from("INACTIVE"), + } + } } diff --git a/src/pulsedevice.rs b/src/pulsedevice.rs index eb247d8d..5d7ebb93 100644 --- a/src/pulsedevice.rs +++ b/src/pulsedevice.rs @@ -306,6 +306,7 @@ impl CaptureDevice for PulseCaptureDevice { let mut start = SystemTime::now(); let mut now; let mut bytes_counter = 0; + let mut value_range = 0.0; loop { match command_channel.try_recv() { Ok(CommandMessage::Exit) => { @@ -353,6 +354,7 @@ impl CaptureDevice for PulseCaptureDevice { ); let mut capt_stat = capture_status.write().unwrap(); capt_stat.measured_samplerate = measured_rate_f as usize; + capt_stat.signal_range = value_range as f32; start = now; bytes_counter = 0; } @@ -384,7 +386,8 @@ impl CaptureDevice for PulseCaptureDevice { ), _ => panic!("Unsupported sample format"), }; - if (chunk.maxval - chunk.minval) > silence { + value_range = chunk.maxval - chunk.minval; + if (value_range) > silence { if silent_nbr > silent_limit { debug!("Resuming processing"); } diff --git a/src/socketserver.rs b/src/socketserver.rs index a7f2b365..be45f4ed 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -1,6 +1,7 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, RwLock}; use std::thread; +use clap::crate_version; use crate::CaptureStatus; use config; @@ -18,6 +19,9 @@ enum WSCommand { GetCaptureRate, GetUpdateInterval, SetUpdateInterval(usize), + GetVersion, + GetState, + GetRateAdjust, Exit, Stop, Invalid, @@ -39,6 +43,9 @@ fn parse_command(cmd: &ws::Message) -> WSCommand { "stop" => WSCommand::Stop, "getcapturerate" => WSCommand::GetCaptureRate, "getsignalrange" => WSCommand::GetSignalRange, + "getstate" => WSCommand::GetState, + "getversion" => WSCommand::GetVersion, + "getrateadjust" => WSCommand::GetRateAdjust, "getupdateinterval" => WSCommand::GetUpdateInterval, "setupdateinterval" => { if cmdarg.len() == 2 { @@ -113,6 +120,17 @@ pub fn start_server( let capstat = capture_status_inst.read().unwrap(); socket.send(format!("OK:GETSIGNALRANGE:{}", capstat.signal_range)) } + WSCommand::GetVersion => { + socket.send(format!("OK:GETVERSION:{}", crate_version!())) + } + WSCommand::GetState => { + let capstat = capture_status_inst.read().unwrap(); + socket.send(format!("OK:GETSTATE:{}", &capstat.state.to_string())) + } + WSCommand::GetRateAdjust => { + let capstat = capture_status_inst.read().unwrap(); + socket.send(format!("OK:GETRATEADJUST:{}", capstat.rate_adjust)) + } WSCommand::GetUpdateInterval => { let capstat = capture_status_inst.read().unwrap(); socket.send(format!("OK:GETUPDATEINTERVAL:{}", capstat.update_interval)) From f506c32cd303efbbe0edbb0f14287fa44a1427f5 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Mon, 27 Jul 2020 21:51:21 +0200 Subject: [PATCH 26/61] Update values for state, rate_adjust --- src/alsadevice.rs | 10 ++++++++++ src/bin.rs | 23 +++++++++++------------ src/cpaldevice.rs | 10 ++++++++++ src/filedevice.rs | 10 ++++++++++ src/lib.rs | 20 +++++++++++--------- src/pulsedevice.rs | 10 ++++++++++ src/socketserver.rs | 2 +- 7 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/alsadevice.rs b/src/alsadevice.rs index 14d6ce46..93050244 100644 --- a/src/alsadevice.rs +++ b/src/alsadevice.rs @@ -21,6 +21,7 @@ use std::time::{Duration, SystemTime}; use crate::CaptureStatus; use CommandMessage; use PrcFmt; +use ProcessingState; use Res; use StatusMessage; @@ -300,6 +301,8 @@ fn capture_loop_bytes( let mut now; let mut bytes_counter = 0; let mut value_range = 0.0; + let mut rate_adjust = 0.0; + let mut state = ProcessingState::Running; loop { match channels.command.try_recv() { Ok(CommandMessage::Exit) => { @@ -309,6 +312,7 @@ fn capture_loop_bytes( break; } Ok(CommandMessage::SetSpeed { speed }) => { + rate_adjust = speed; if let Some(elem) = &element { elval.set_integer(0, (100_000.0 * speed) as i32).unwrap(); elem.write(&elval).unwrap(); @@ -342,6 +346,8 @@ fn capture_loop_bytes( let mut capt_stat = params.capture_status.write().unwrap(); capt_stat.measured_samplerate = measured_rate_f as usize; capt_stat.signal_range = value_range as f32; + capt_stat.rate_adjust = rate_adjust as f32; + capt_stat.state = state; start = now; bytes_counter = 0; } @@ -374,11 +380,13 @@ fn capture_loop_bytes( value_range = chunk.maxval - chunk.minval; if value_range > params.silence { if silent_nbr > params.silent_limit { + state = ProcessingState::Running; debug!("Resuming processing"); } silent_nbr = 0; } else if params.silent_limit > 0 { if silent_nbr == params.silent_limit { + state = ProcessingState::Paused; debug!("Pausing processing"); } silent_nbr += 1; @@ -394,6 +402,8 @@ fn capture_loop_bytes( channels.audio.send(msg).unwrap(); } } + let mut capt_stat = params.capture_status.write().unwrap(); + capt_stat.state = ProcessingState::Inactive; } fn get_nbr_capture_bytes( diff --git a/src/bin.rs b/src/bin.rs index d5027c79..fb3a9ab7 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -46,12 +46,11 @@ use camillalib::StatusMessage; use camillalib::CommandMessage; -use camillalib::ExitStatus; +use camillalib::ExitState; use camillalib::CaptureStatus; use camillalib::ProcessingState; - fn get_new_config( config_path: &Arc>>, new_config_shared: &Arc>>, @@ -109,12 +108,12 @@ fn run( config_path: Arc>>, new_config_shared: Arc>>, capture_status: Arc>, -) -> Res { +) -> Res { let conf = match new_config_shared.lock().unwrap().clone() { Some(cfg) => cfg, None => { error!("Tried to start without config!"); - return Ok(ExitStatus::Exit); + return Ok(ExitState::Exit); } }; let (tx_pb, rx_pb) = mpsc::sync_channel(conf.devices.queuelimit); @@ -194,7 +193,7 @@ fn run( trace!("Wait for cap.."); cap_handle.join().unwrap(); *new_config_shared.lock().unwrap() = Some(conf); - return Ok(ExitStatus::Restart); + return Ok(ExitState::Restart); } config::ConfigChange::None => { debug!("No changes in config."); @@ -216,7 +215,7 @@ fn run( pb_handle.join().unwrap(); trace!("Wait for cap.."); cap_handle.join().unwrap(); - return Ok(ExitStatus::Exit); + return Ok(ExitState::Exit); } 2 => { debug!("Stop requested..."); @@ -227,7 +226,7 @@ fn run( trace!("Wait for cap.."); cap_handle.join().unwrap(); *new_config_shared.lock().unwrap() = None; - return Ok(ExitStatus::Restart); + return Ok(ExitState::Restart); } _ => {} }; @@ -249,15 +248,15 @@ fn run( } StatusMessage::PlaybackError { message } => { error!("Playback error: {}", message); - return Ok(ExitStatus::Exit); + return Ok(ExitState::Exit); } StatusMessage::CaptureError { message } => { error!("Capture error: {}", message); - return Ok(ExitStatus::Exit); + return Ok(ExitState::Exit); } StatusMessage::PlaybackDone => { info!("Playback finished"); - return Ok(ExitStatus::Exit); + return Ok(ExitState::Exit); } StatusMessage::CaptureDone => { info!("Capture finished"); @@ -449,13 +448,13 @@ fn main() { break; } } - Ok(ExitStatus::Exit) => { + Ok(ExitState::Exit) => { debug!("Exiting"); if !wait { break; } } - Ok(ExitStatus::Restart) => { + Ok(ExitState::Restart) => { debug!("Restarting with new config"); } }; diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index 3b60dff2..d51ee514 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -19,6 +19,7 @@ use std::time::SystemTime; use crate::CaptureStatus; use CommandMessage; use PrcFmt; +use ProcessingState; use Res; use StatusMessage; @@ -478,6 +479,8 @@ impl CaptureDevice for CpalCaptureDevice { let mut now; let mut sample_counter = 0; let mut value_range = 0.0; + let mut rate_adjust = 0.0; + let mut state = ProcessingState::Running; loop { match command_channel.try_recv() { Ok(CommandMessage::Exit) => { @@ -487,6 +490,7 @@ impl CaptureDevice for CpalCaptureDevice { break; } Ok(CommandMessage::SetSpeed { speed }) => { + rate_adjust = speed; if let Some(resampl) = &mut resampler { debug!("Adjusting resampler rate to {}", speed); if async_src { @@ -569,17 +573,21 @@ impl CaptureDevice for CpalCaptureDevice { let mut capt_stat = capture_status.write().unwrap(); capt_stat.measured_samplerate = measured_rate_f as usize; capt_stat.signal_range = value_range as f32; + capt_stat.rate_adjust = rate_adjust as f32; + capt_stat.state = state; start = now; sample_counter = 0; } value_range = chunk.maxval - chunk.minval; if (value_range) > silence { if silent_nbr > silent_limit { + state = ProcessingState::Running; debug!("Resuming processing"); } silent_nbr = 0; } else if silent_limit > 0 { if silent_nbr == silent_limit { + state = ProcessingState::Paused; debug!("Pausing processing"); } silent_nbr += 1; @@ -595,6 +603,8 @@ impl CaptureDevice for CpalCaptureDevice { channel.send(msg).unwrap(); } } + let mut capt_stat = capture_status.write().unwrap(); + capt_stat.state = ProcessingState::Inactive; } Err(err) => { status_channel diff --git a/src/filedevice.rs b/src/filedevice.rs index 87a634fc..2d7e4f8c 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -18,6 +18,7 @@ use rubato::Resampler; use crate::CaptureStatus; use CommandMessage; use PrcFmt; +use ProcessingState; use Res; use StatusMessage; @@ -255,6 +256,8 @@ fn capture_loop( let mut now; let mut bytes_counter = 0; let mut value_range = 0.0; + let mut rate_adjust = 0.0; + let mut state = ProcessingState::Running; loop { match msg_channels.command.try_recv() { Ok(CommandMessage::Exit) => { @@ -267,6 +270,7 @@ fn capture_loop( break; } Ok(CommandMessage::SetSpeed { speed }) => { + rate_adjust = speed; if let Some(resampl) = &mut resampler { if params.async_src { if resampl.set_resample_ratio_relative(speed).is_err() { @@ -340,6 +344,8 @@ fn capture_loop( let mut capt_stat = params.capture_status.write().unwrap(); capt_stat.measured_samplerate = measured_rate_f as usize; capt_stat.signal_range = value_range as f32; + capt_stat.rate_adjust = rate_adjust as f32; + capt_stat.state = state; start = now; bytes_counter = 0; } @@ -367,11 +373,13 @@ fn capture_loop( value_range = chunk.maxval - chunk.minval; if (value_range) > params.silence { if silent_nbr > params.silent_limit { + state = ProcessingState::Running; debug!("Resuming processing"); } silent_nbr = 0; } else if params.silent_limit > 0 { if silent_nbr == params.silent_limit { + state = ProcessingState::Paused; debug!("Pausing processing"); } silent_nbr += 1; @@ -389,6 +397,8 @@ fn capture_loop( msg_channels.audio.send(msg).unwrap(); } } + let mut capt_stat = params.capture_status.write().unwrap(); + capt_stat.state = ProcessingState::Inactive; } /// Start a capture thread providing AudioMessages via a channel diff --git a/src/lib.rs b/src/lib.rs index 422298b8..a757f39d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ extern crate log; extern crate env_logger; use std::error; +use std::fmt; // Sample format #[cfg(feature = "32bit")] @@ -76,12 +77,12 @@ pub enum CommandMessage { Exit, } -pub enum ExitStatus { +pub enum ExitState { Restart, Exit, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Copy)] pub enum ProcessingState { Running, Paused, @@ -97,12 +98,13 @@ pub struct CaptureStatus { pub rate_adjust: f32, } -impl ProcessingState { - pub fn to_string(&self) -> String { - match self { - ProcessingState::Running => String::from("RUNNING"), - ProcessingState::Paused => String::from("PAUSED"), - ProcessingState::Inactive => String::from("INACTIVE"), - } +impl fmt::Display for ProcessingState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let desc = match self { + ProcessingState::Running => "RUNNING", + ProcessingState::Paused => "PAUSED", + ProcessingState::Inactive => "INACTIVE", + }; + write!(f, "{}", desc) } } diff --git a/src/pulsedevice.rs b/src/pulsedevice.rs index 5d7ebb93..14bf7d63 100644 --- a/src/pulsedevice.rs +++ b/src/pulsedevice.rs @@ -19,6 +19,7 @@ use std::time::SystemTime; use crate::CaptureStatus; use CommandMessage; use PrcFmt; +use ProcessingState; use Res; use StatusMessage; @@ -307,6 +308,8 @@ impl CaptureDevice for PulseCaptureDevice { let mut now; let mut bytes_counter = 0; let mut value_range = 0.0; + let mut rate_adjust = 0.0; + let mut state = ProcessingState::Running; loop { match command_channel.try_recv() { Ok(CommandMessage::Exit) => { @@ -316,6 +319,7 @@ impl CaptureDevice for PulseCaptureDevice { break; } Ok(CommandMessage::SetSpeed { speed }) => { + rate_adjust = speed; if let Some(resampl) = &mut resampler { if async_src { if resampl.set_resample_ratio_relative(speed).is_err() { @@ -355,6 +359,8 @@ impl CaptureDevice for PulseCaptureDevice { let mut capt_stat = capture_status.write().unwrap(); capt_stat.measured_samplerate = measured_rate_f as usize; capt_stat.signal_range = value_range as f32; + capt_stat.rate_adjust = rate_adjust as f32; + capt_stat.state = state; start = now; bytes_counter = 0; } @@ -389,11 +395,13 @@ impl CaptureDevice for PulseCaptureDevice { value_range = chunk.maxval - chunk.minval; if (value_range) > silence { if silent_nbr > silent_limit { + state = ProcessingState::Running; debug!("Resuming processing"); } silent_nbr = 0; } else if silent_limit > 0 { if silent_nbr == silent_limit { + state = ProcessingState::Paused; debug!("Pausing processing"); } silent_nbr += 1; @@ -409,6 +417,8 @@ impl CaptureDevice for PulseCaptureDevice { channel.send(msg).unwrap(); } } + let mut capt_stat = capture_status.write().unwrap(); + capt_stat.state = ProcessingState::Inactive; } Err(err) => { status_channel diff --git a/src/socketserver.rs b/src/socketserver.rs index be45f4ed..3404f8df 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -1,7 +1,7 @@ +use clap::crate_version; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, RwLock}; use std::thread; -use clap::crate_version; use crate::CaptureStatus; use config; From 10350d875c0a0633cc0ced3136d9c53f35882f0a Mon Sep 17 00:00:00 2001 From: HEnquist Date: Tue, 28 Jul 2020 20:52:36 +0200 Subject: [PATCH 27/61] Update websocket readme --- websocket.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/websocket.md b/websocket.md index 6b98f669..8943c9f0 100644 --- a/websocket.md +++ b/websocket.md @@ -5,6 +5,13 @@ If the websocket server is enabled with the `-p` option, CamillaDSP will listen If additionally the "wait" flag is given, it will wait for a config to be uploaded via the websocket server before starting the processing. The available commands are: +- `getstate` : get the current state of the processing. Possible values are: + * "RUNNING": the processing is running normally. + * "PAUSED": processing is paused because the input signal is silent. + * "INACTIVE": the program is inactive and waiting for a new configuration. + * response is `OK:GETSTATE:RUNNING` +- `getversion` : read the CamillaDSP version + * response is `OK:GETVERSION:1.2.3` - `getconfig` : read the current configuration as yaml * response is `OK:GETCONFIG:(yamldata)` where yamldata is the config in yaml format. - `getconfigjson` : read the current configuration as json @@ -19,6 +26,8 @@ The available commands are: * response is `OK:SETUPDATEINTERVAL` - `getsignalrange` : get the range of values in the last chunk. A value of 2.0 means full level (signal swings from -1.0 to +1.0) * response is `OK:GETSIGNALRANGE:1.23456` +- `getrateadjust` : get the adjustment factor applied to the asynchronous resampler. + * response is `OK:GETRATEADJUST:1.0023` - `reload` : reload current config file (same as SIGHUP) * response is `OK:RELOAD` or `ERROR:RELOAD` - `stop` : stop processing and wait for a new config to be uploaded with `setconfig` @@ -30,7 +39,14 @@ The available commands are: - `setconfigjson:` : provide a new config as a JSON string. Applied directly. * response is `OK:SETCONFIGJSON` or `ERROR:SETCONFIGJSON` -## Controlling from Python +## Controlling from Python using pyCamillaDSP + +The recommended way of controlling CamillaDSP with Python is by using the [pyCamillaDSP library](https://github.com/HEnquist/pycamilladsp). + +Please see the readme in that library for instructions. + + +## Controlling directly using Python You need the websocket_client module installed for this to work. The package is called `python-websocket-client` on Fedora and `python3-websocket` on Debian/Ubuntu. From 699dc6cf26bdcbd530b4914805bb89739c5561e0 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Tue, 28 Jul 2020 20:53:10 +0200 Subject: [PATCH 28/61] Add changelog --- CHANGELOG.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..a956f17b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,51 @@ +## 0.3.2 +New features: +- New commands to get more information from the websocket server. +- Possible to skip lines or bytes in coefficient files. +Bugfixes: + +TODO: +- Improved cryptic error message for errors in text coefficient files + + +## 0.3.1 +New features: +- Rate adjust via the resampler also for Wasapi and CoreAudio. + + +## 0.3.0 +New features: +- Support for Windows (Wasapi) and macOS (CoreAudio) via the Cpal library + + +## 0.2.2 +New features: +- Fix building on Windows and macOS. +- Updated versions of several libraries. +- Improved speed from optimization of several important loops. + + +## 0.2.1 +New features: +- Convolver was optimized to be up to a factor 2 faster + +## 0.2.0 +New features: +- Synchronous resampler that replaces the previous FastSync, BalancedSync and AccurateSync types with a single one called Synchronous. This uses FFT for a major speedup. +- The Async resamplers have been optimized and are now around a factor 2 faster than before. +Bugfixes: +- Fixed error when setting Alsa buffer size in some cases. + + +## 0.1.0 +New features: +- Support for asynchronous resampling in all backends. +- Added S24LE3 format (corresponds to Alsa S24_3LE) +- File capture device can skip a number of bytes at the beginning of a file and then read a limited number of bytes +Other: +- Alsa backend rewritten to reduce code duplication +- Improved debug output + + +## 0.0.14 +Last version without resampling From b60a5bdcaf8be0797481e28f05cf074804ce448c Mon Sep 17 00:00:00 2001 From: HEnquist Date: Tue, 28 Jul 2020 21:38:15 +0200 Subject: [PATCH 29/61] Improve error message for text coeff reading --- CHANGELOG.md | 3 +-- src/filters.rs | 32 +++++++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a956f17b..07368319 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,8 @@ New features: - New commands to get more information from the websocket server. - Possible to skip lines or bytes in coefficient files. +- Updated Cpal library Bugfixes: - -TODO: - Improved cryptic error message for errors in text coefficient files diff --git a/src/filters.rs b/src/filters.rs index f584d88d..69e1527f 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -44,9 +44,35 @@ pub fn read_coeff_file( match format { config::FileFormat::TEXT => { - for line in file.lines().skip(skip_bytes_lines).take(read_bytes_lines) { - let l = line?; - coefficients.push(l.trim().parse()?); + for (nbr, line) in file + .lines() + .skip(skip_bytes_lines) + .take(read_bytes_lines) + .enumerate() + { + match line { + Err(err) => { + let msg = format!( + "Can't read line {} of file '{}', error: {}", + nbr + 1 + skip_bytes_lines, + filename, + err + ); + return Err(Box::new(config::ConfigError::new(&msg))); + } + Ok(l) => match l.trim().parse() { + Ok(val) => coefficients.push(val), + Err(err) => { + let msg = format!( + "Can't parse value on line {} of file '{}', error: {}", + nbr + 1 + skip_bytes_lines, + filename, + err + ); + return Err(Box::new(config::ConfigError::new(&msg))); + } + }, + } } } config::FileFormat::FLOAT32LE => { From 13435008f961a2c4db77b18c0a3a1fcedee7f839 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Wed, 29 Jul 2020 22:06:35 +0200 Subject: [PATCH 30/61] Change macOS virtual sound card recommendation --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a52dc245..17ed3651 100644 --- a/README.md +++ b/README.md @@ -306,19 +306,19 @@ Set VB-CABLE as the default playback device in the control panel, and let Camill The device name is the same as seen in the Windows volume control. For example, the VB-CABLE device name is "CABLE Output (VB-Audio Virtual Cable)". The device name is built from the inpout/output name and card name, and the format is "{input/output name} ({card name})". -The sample format appears to always be 32-bit float (FLOAT32LE) even if the device is configured to use aother format. +The sample format is always 32-bit float (FLOAT32LE) even if the device is configured to use aother format. The sample rate must match the default format of the device. To change this, open "Sound" in the Control panel, select the sound card, and click "Properties". Then open the "Advanced" tab and select the desired format under "Default Format". ## CoreAudio -To capture audio from applications a virtual sound card is needed. This has been verified to work with [BlackHole](https://github.com/ExistentialAudio/BlackHole). +To capture audio from applications a virtual sound card is needed. This has been verified to work well with [Soundflower](https://github.com/mattingalls/Soundflower) Set the virtual sound card as the default playback device in the Sound preferences, and let CamillaDSP capture from the output of this card. -The device name is the same as the one shown in the "Audio MIDI Setup" that can be found under "Other" in Launchpad. The name for BlackHole is "BlackHole 16ch", and the built in audio in a MacBook Pro is called "Built-in Output". +The device name is the same as the one shown in the "Audio MIDI Setup" that can be found under "Other" in Launchpad. The name for the 2-channel interface of Soundflower is "Soundflower (2ch)", and the built in audio in a MacBook Pro is called "Built-in Output". -The sample format appears to always be 32-bit float (FLOAT32LE) even if the device is configured to use aother format. +The sample format is always 32-bit float (FLOAT32LE) even if the device is configured to use another format. # Configuration From 82ed3b93c20074f3eb7649f0b6f06cd4adb42233 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Wed, 5 Aug 2020 21:55:53 +0200 Subject: [PATCH 31/61] Fix exit command --- src/bin.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/bin.rs b/src/bin.rs index fb3a9ab7..c0cb6636 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -430,6 +430,10 @@ fn main() { debug!("Wait for config"); while new_config.lock().unwrap().is_none() { trace!("waiting..."); + if signal_exit.load(Ordering::Relaxed) == 1 { + // exit requested + break; + } thread::sleep(delay); } debug!("Config ready"); @@ -450,7 +454,8 @@ fn main() { } Ok(ExitState::Exit) => { debug!("Exiting"); - if !wait { + if !wait || signal_exit.load(Ordering::Relaxed) == 1 { + // wait mode not active, or exit requested break; } } From 69d81a3eec54b9e73c7cb455a71bc8579828b7ae Mon Sep 17 00:00:00 2001 From: HEnquist Date: Wed, 5 Aug 2020 21:59:36 +0200 Subject: [PATCH 32/61] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07368319..7511a962 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,10 @@ New features: - New commands to get more information from the websocket server. - Possible to skip lines or bytes in coefficient files. - Updated Cpal library + Bugfixes: - Improved cryptic error message for errors in text coefficient files +- Fix websocket `exit` command ## 0.3.1 @@ -32,6 +34,7 @@ New features: New features: - Synchronous resampler that replaces the previous FastSync, BalancedSync and AccurateSync types with a single one called Synchronous. This uses FFT for a major speedup. - The Async resamplers have been optimized and are now around a factor 2 faster than before. + Bugfixes: - Fixed error when setting Alsa buffer size in some cases. @@ -41,6 +44,7 @@ New features: - Support for asynchronous resampling in all backends. - Added S24LE3 format (corresponds to Alsa S24_3LE) - File capture device can skip a number of bytes at the beginning of a file and then read a limited number of bytes + Other: - Alsa backend rewritten to reduce code duplication - Improved debug output From f289f8198d11cac35da37eb89f7ad58ece36d0ef Mon Sep 17 00:00:00 2001 From: HEnquist Date: Wed, 5 Aug 2020 22:04:29 +0200 Subject: [PATCH 33/61] Format --- src/bin.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index c0cb6636..abc6b608 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -431,7 +431,7 @@ fn main() { while new_config.lock().unwrap().is_none() { trace!("waiting..."); if signal_exit.load(Ordering::Relaxed) == 1 { - // exit requested + // exit requested break; } thread::sleep(delay); @@ -455,7 +455,7 @@ fn main() { Ok(ExitState::Exit) => { debug!("Exiting"); if !wait || signal_exit.load(Ordering::Relaxed) == 1 { - // wait mode not active, or exit requested + // wait mode not active, or exit requested break; } } From 132abcf2b66765a8be730da8a24f26e25e599481 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Sat, 8 Aug 2020 21:05:37 +0200 Subject: [PATCH 34/61] Fix getconfigname response --- CHANGELOG.md | 1 + src/socketserver.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7511a962..cfc1e6b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ New features: Bugfixes: - Improved cryptic error message for errors in text coefficient files - Fix websocket `exit` command +- Correct response of `setconfigname` command ## 0.3.1 diff --git a/src/socketserver.rs b/src/socketserver.rs index 3404f8df..8911d342 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -166,9 +166,9 @@ pub fn start_server( WSCommand::SetConfigName(path) => match config::load_validate_config(&path) { Ok(_) => { *active_config_path_inst.lock().unwrap() = Some(path.clone()); - socket.send(format!("OK:{}", path)) + socket.send(format!("OK:SETCONFIGNAME:{}", path)) } - _ => socket.send(format!("ERROR:{}", path)), + _ => socket.send("ERROR:SETCONFIGNAME"), }, WSCommand::SetConfig(config_yml) => { match serde_yaml::from_str::(&config_yml) { From b3b1a9395625420632739e35d9d519651eeb5fbf Mon Sep 17 00:00:00 2001 From: HEnquist Date: Tue, 11 Aug 2020 00:33:53 +0200 Subject: [PATCH 35/61] Improve file not found error --- src/filters.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/filters.rs b/src/filters.rs index 69e1527f..41080eee 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -34,7 +34,14 @@ pub fn read_coeff_file( skip_bytes_lines: usize, ) -> Res> { let mut coefficients = Vec::::new(); - let f = File::open(filename)?; + let f = match File::open(filename) { + Ok(f) => f, + Err(_) => { + return Err(Box::new(config::ConfigError::new( + format!("Could not open cofficient file {}", filename).as_str(), + ))); + } + }; let mut file = BufReader::new(&f); let read_bytes_lines = if read_bytes_lines > 0 { read_bytes_lines From 9482c053b44d07e0cbb7aa2410573c195da6d9a6 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Tue, 11 Aug 2020 08:04:06 +0200 Subject: [PATCH 36/61] Fix typo in error message --- src/filters.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/filters.rs b/src/filters.rs index 41080eee..88a5d97c 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -36,9 +36,13 @@ pub fn read_coeff_file( let mut coefficients = Vec::::new(); let f = match File::open(filename) { Ok(f) => f, - Err(_) => { + Err(err) => { return Err(Box::new(config::ConfigError::new( - format!("Could not open cofficient file {}", filename).as_str(), + format!( + "Could not open coefficient file '{}', error: {}", + filename, err + ) + .as_str(), ))); } }; From 23811c5450448ef6c65de5bca1b39f66c4c508df Mon Sep 17 00:00:00 2001 From: HEnquist Date: Tue, 11 Aug 2020 21:15:12 +0200 Subject: [PATCH 37/61] Refactor error messages --- src/bin.rs | 4 +-- src/biquadcombo.rs | 16 ++++++------ src/config.rs | 62 ++++++++++++++++++++++++--------------------- src/cpaldevice.rs | 12 +++------ src/fftconv.rs | 4 +-- src/fftconv_fftw.rs | 4 +-- src/filters.rs | 28 ++++++++------------ 7 files changed, 59 insertions(+), 71 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index abc6b608..80f8065f 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -95,9 +95,7 @@ fn get_new_config( } } else { error!("No new config supplied and no path set"); - Err(Box::new(config::ConfigError::new( - "No new config supplied and no path set", - ))) + Err(config::ConfigError::new("No new config supplied and no path set").into()) } } diff --git a/src/biquadcombo.rs b/src/biquadcombo.rs index a5ffd92e..67b876c1 100644 --- a/src/biquadcombo.rs +++ b/src/biquadcombo.rs @@ -155,24 +155,24 @@ pub fn validate_config(conf: &config::BiquadComboParameters) -> Res<()> { config::BiquadComboParameters::LinkwitzRileyHighpass { freq, order } | config::BiquadComboParameters::LinkwitzRileyLowpass { freq, order } => { if *freq <= 0.0 { - return Err(Box::new(config::ConfigError::new("Frequency must be > 0"))); + return Err(config::ConfigError::new("Frequency must be > 0").into()); } if (*order % 2 > 0) && (*order == 0) { - return Err(Box::new(config::ConfigError::new( - "LR order must be an even non-zero number", - ))); + return Err( + config::ConfigError::new("LR order must be an even non-zero number").into(), + ); } Ok(()) } config::BiquadComboParameters::ButterworthHighpass { freq, order } | config::BiquadComboParameters::ButterworthLowpass { freq, order } => { if *freq <= 0.0 { - return Err(Box::new(config::ConfigError::new("Frequency must be > 0"))); + return Err(config::ConfigError::new("Frequency must be > 0").into()); } if *order % 2 > 0 { - return Err(Box::new(config::ConfigError::new( - "Butterworth order must be larger than zero", - ))); + return Err( + config::ConfigError::new("Butterworth order must be larger than zero").into(), + ); } Ok(()) } diff --git a/src/config.rs b/src/config.rs index 7114d1eb..236d68d5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -458,25 +458,25 @@ pub struct Configuration { pub fn load_config(filename: &str) -> Res { let file = match File::open(filename) { Ok(f) => f, - Err(_) => { - return Err(Box::new(ConfigError::new("Could not open config file!"))); + Err(err) => { + let msg = format!("Could not open config file '{}'. Error: {}", filename, err); + return Err(ConfigError::new(&msg).into()); } }; let mut buffered_reader = BufReader::new(file); let mut contents = String::new(); let _number_of_bytes: usize = match buffered_reader.read_to_string(&mut contents) { Ok(number_of_bytes) => number_of_bytes, - Err(_err) => { - return Err(Box::new(ConfigError::new("Could not read config file!"))); + Err(err) => { + let msg = format!("Could not read config file '{}'. Error: {}", filename, err); + return Err(ConfigError::new(&msg).into()); } }; let configuration: Configuration = match serde_yaml::from_str(&contents) { Ok(config) => config, Err(err) => { - return Err(Box::new(ConfigError::new(&format!( - "Invalid config file!\n{}", - err - )))); + let msg = format!("Invalid config file!\n{}", err); + return Err(ConfigError::new(&msg).into()); } }; Ok(configuration) @@ -539,12 +539,14 @@ pub fn config_diff(currentconf: &Configuration, newconf: &Configuration) -> Conf /// Validate the loaded configuration, stop on errors and print a helpful message. pub fn validate_config(conf: Configuration) -> Res<()> { if conf.devices.target_level >= 2 * conf.devices.chunksize { - return Err(Box::new(ConfigError::new("target_level is too large."))); + let msg = format!( + "target_level can't be larger than {}", + 2 * conf.devices.chunksize + ); + return Err(ConfigError::new(&msg).into()); } if conf.devices.adjust_period <= 0.0 { - return Err(Box::new(ConfigError::new( - "adjust_period must be positive and > 0", - ))); + return Err(ConfigError::new("adjust_period must be positive and > 0").into()); } let mut num_channels = match conf.devices.capture { #[cfg(all(feature = "alsa-backend", target_os = "linux"))] @@ -562,36 +564,37 @@ pub fn validate_config(conf: Configuration) -> Res<()> { match step { PipelineStep::Mixer { name } => { if !conf.mixers.contains_key(&name) { - return Err(Box::new(ConfigError::new(&format!( - "Use of missing mixer '{}'", - name - )))); + let msg = format!("Use of missing mixer '{}'", name); + return Err(ConfigError::new(&msg).into()); } else { let chan_in = conf.mixers.get(&name).unwrap().channels.r#in; if chan_in != num_channels { - return Err(Box::new(ConfigError::new(&format!( + let msg = format!( "Mixer '{}' has wrong number of input channels. Expected {}, found {}.", name, num_channels, chan_in - )))); + ); + return Err(ConfigError::new(&msg).into()); } num_channels = conf.mixers.get(&name).unwrap().channels.out; } } PipelineStep::Filter { channel, names } => { if channel > num_channels { - return Err(Box::new(ConfigError::new(&format!( - "Use of non existing channel {}", - channel - )))); + let msg = format!("Use of non existing channel {}", channel); + return Err(ConfigError::new(&msg).into()); } for name in names { if !conf.filters.contains_key(&name) { - return Err(Box::new(ConfigError::new(&format!( - "Use of missing filter '{}'", - name - )))); + let msg = format!("Use of missing filter '{}'", name); + return Err(ConfigError::new(&msg).into()); + } + match filters::validate_filter(fs, &conf.filters.get(&name).unwrap()) { + Ok(_) => {} + Err(err) => { + let msg = format!("Invalid filter '{}'. Reason: {}", name, err); + return Err(ConfigError::new(&msg).into()); + } } - filters::validate_filter(fs, &conf.filters.get(&name).unwrap())?; } } } @@ -608,10 +611,11 @@ pub fn validate_config(conf: Configuration) -> Res<()> { PlaybackDevice::Wasapi { channels, .. } => channels, }; if num_channels != num_channels_out { - return Err(Box::new(ConfigError::new(&format!( + let msg = format!( "Pipeline outputs {} channels, playback device has {}.", num_channels, num_channels_out - )))); + ); + return Err(ConfigError::new(&msg).into()); } Ok(()) } diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index d51ee514..5edb5fa8 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -80,10 +80,8 @@ fn open_cpal_playback( }) { Some(dev) => dev, None => { - return Err(Box::new(ConfigError::new(&format!( - "Could not find device: {}", - devname - )))) + let msg = format!("Could not find device '{}'", devname); + return Err(ConfigError::new(&msg).into()); } }; let sample_format = match format { @@ -121,10 +119,8 @@ fn open_cpal_capture( }) { Some(dev) => dev, None => { - return Err(Box::new(ConfigError::new(&format!( - "Could not find device: {}", - devname - )))) + let msg = format!("Could not find device '{}'", devname); + return Err(ConfigError::new(&msg).into()); } }; let sample_format = match format { diff --git a/src/fftconv.rs b/src/fftconv.rs index f12aca02..f8497604 100644 --- a/src/fftconv.rs +++ b/src/fftconv.rs @@ -194,9 +194,7 @@ pub fn validate_config(conf: &config::ConvParameters) -> Res<()> { let coeffs = filters::read_coeff_file(&filename, &format, *read_bytes_lines, *skip_bytes_lines)?; if coeffs.is_empty() { - return Err(Box::new(config::ConfigError::new( - "Conv coefficients are empty", - ))); + return Err(config::ConfigError::new("Conv coefficients are empty").into()); } Ok(()) } diff --git a/src/fftconv_fftw.rs b/src/fftconv_fftw.rs index df96aff7..4fa9ff91 100644 --- a/src/fftconv_fftw.rs +++ b/src/fftconv_fftw.rs @@ -199,9 +199,7 @@ pub fn validate_config(conf: &config::ConvParameters) -> Res<()> { let coeffs = filters::read_coeff_file(&filename, &format, *read_bytes_lines, *skip_bytes_lines)?; if coeffs.is_empty() { - return Err(Box::new(config::ConfigError::new( - "Conv coefficients are empty", - ))); + return Err(config::ConfigError::new("Conv coefficients are empty").into()); } Ok(()) } diff --git a/src/filters.rs b/src/filters.rs index 88a5d97c..bb006edc 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -37,13 +37,11 @@ pub fn read_coeff_file( let f = match File::open(filename) { Ok(f) => f, Err(err) => { - return Err(Box::new(config::ConfigError::new( - format!( - "Could not open coefficient file '{}', error: {}", - filename, err - ) - .as_str(), - ))); + let msg = format!( + "Could not open coefficient file '{}'. Error: {}", + filename, err + ); + return Err(config::ConfigError::new(&msg).into()); } }; let mut file = BufReader::new(&f); @@ -64,23 +62,23 @@ pub fn read_coeff_file( match line { Err(err) => { let msg = format!( - "Can't read line {} of file '{}', error: {}", + "Can't read line {} of file '{}'. Error: {}", nbr + 1 + skip_bytes_lines, filename, err ); - return Err(Box::new(config::ConfigError::new(&msg))); + return Err(config::ConfigError::new(&msg).into()); } Ok(l) => match l.trim().parse() { Ok(val) => coefficients.push(val), Err(err) => { let msg = format!( - "Can't parse value on line {} of file '{}', error: {}", + "Can't parse value on line {} of file '{}'. Error: {}", nbr + 1 + skip_bytes_lines, filename, err ); - return Err(Box::new(config::ConfigError::new(&msg))); + return Err(config::ConfigError::new(&msg).into()); } }, } @@ -329,17 +327,13 @@ pub fn validate_filter(fs: usize, filter_config: &config::Filter) -> Res<()> { config::Filter::Biquad { parameters } => { let coeffs = biquad::BiquadCoefficients::from_config(fs, parameters.clone()); if !coeffs.is_stable() { - return Err(Box::new(config::ConfigError::new( - "Unstable filter specified", - ))); + return Err(config::ConfigError::new("Unstable filter specified").into()); } Ok(()) } config::Filter::Delay { parameters } => { if parameters.delay < 0.0 { - return Err(Box::new(config::ConfigError::new( - "Negative delay specified", - ))); + return Err(config::ConfigError::new("Negative delay specified").into()); } Ok(()) } From 6453f2c12b31e3436b4dae57e32e76090bc56bfa Mon Sep 17 00:00:00 2001 From: HEnquist Date: Tue, 11 Aug 2020 21:17:34 +0200 Subject: [PATCH 38/61] Update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfc1e6b6..1161938b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,11 @@ New features: - New commands to get more information from the websocket server. - Possible to skip lines or bytes in coefficient files. - Updated Cpal library +- Improved error messages Bugfixes: -- Improved cryptic error message for errors in text coefficient files - Fix websocket `exit` command -- Correct response of `setconfigname` command +- Correct response of `setconfigname` websocket command ## 0.3.1 From 0365aa73247461604c47ea25d416097ca94ec88d Mon Sep 17 00:00:00 2001 From: HEnquist Date: Sun, 16 Aug 2020 22:46:09 +0200 Subject: [PATCH 39/61] Add new websocket commands --- src/socketserver.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/socketserver.rs b/src/socketserver.rs index 8911d342..6fb7cdf3 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -13,6 +13,8 @@ enum WSCommand { SetConfigJson(String), Reload, GetConfig, + ReadConfigFile(String), + ValidateConfig(String), GetConfigJson, GetConfigName, GetSignalRange, @@ -37,6 +39,20 @@ fn parse_command(cmd: &ws::Message) -> WSCommand { match cmdarg[0] { "reload" => WSCommand::Reload, "getconfig" => WSCommand::GetConfig, + "validateconfig" => { + if cmdarg.len() == 2 { + WSCommand::ValidateConfig(cmdarg[1].to_string()) + } else { + WSCommand::Invalid + } + } + "readconfigfile" => { + if cmdarg.len() == 2 { + WSCommand::ReadConfigFile(cmdarg[1].to_string()) + } else { + WSCommand::Invalid + } + } "getconfigjson" => WSCommand::GetConfigJson, "getconfigname" => WSCommand::GetConfigName, "exit" => WSCommand::Exit, @@ -204,6 +220,30 @@ pub fn start_server( } } } + WSCommand::ReadConfigFile(path) => match config::load_config(&path) { + Ok(config) => socket.send(format!( + "OK:READCONFIGFILE:{}", + serde_yaml::to_string(&config).unwrap() + )), + Err(error) => socket.send(format!("ERROR:READCONFIGFILE:{}", error)), + }, + WSCommand::ValidateConfig(config_yml) => { + match serde_yaml::from_str::(&config_yml) { + Ok(conf) => match config::validate_config(conf.clone()) { + Ok(()) => socket.send(format!( + "OK:VALIDATECONFIG:{}", + serde_yaml::to_string(&conf).unwrap() + )), + Err(error) => { + socket.send(format!("ERROR:VALIDATECONFIG:{}", error)) + } + }, + Err(error) => { + error!("Config error: {}", error); + socket.send(format!("ERROR:VALIDATECONFIG:{}", error)) + } + } + } WSCommand::Stop => { *new_config_inst.lock().unwrap() = None; signal_exit_inst.store(2, Ordering::Relaxed); From b569b624733fba5a8bfb88c36ebc5fdae7798845 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Mon, 17 Aug 2020 21:40:05 +0200 Subject: [PATCH 40/61] Add readconfig ws command --- src/socketserver.rs | 15 ++++++++++++ websocket.md | 59 +++++++++++++++++++++++++++++++++------------ 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/socketserver.rs b/src/socketserver.rs index 6fb7cdf3..55b03fe8 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -13,6 +13,7 @@ enum WSCommand { SetConfigJson(String), Reload, GetConfig, + ReadConfig(String), ReadConfigFile(String), ValidateConfig(String), GetConfigJson, @@ -53,6 +54,13 @@ fn parse_command(cmd: &ws::Message) -> WSCommand { WSCommand::Invalid } } + "readconfig" => { + if cmdarg.len() == 2 { + WSCommand::ReadConfig(cmdarg[1].to_string()) + } else { + WSCommand::Invalid + } + } "getconfigjson" => WSCommand::GetConfigJson, "getconfigname" => WSCommand::GetConfigName, "exit" => WSCommand::Exit, @@ -220,6 +228,13 @@ pub fn start_server( } } } + WSCommand::ReadConfig(config_yml) => match serde_yaml::from_str::(&config_yml) { + Ok(config) => socket.send(format!( + "OK:READCONFIG:{}", + serde_yaml::to_string(&config).unwrap() + )), + Err(error) => socket.send(format!("ERROR:READCONFIG:{}", error)), + }, WSCommand::ReadConfigFile(path) => match config::load_config(&path) { Ok(config) => socket.send(format!( "OK:READCONFIGFILE:{}", diff --git a/websocket.md b/websocket.md index 8943c9f0..dfa87f48 100644 --- a/websocket.md +++ b/websocket.md @@ -5,39 +5,66 @@ If the websocket server is enabled with the `-p` option, CamillaDSP will listen If additionally the "wait" flag is given, it will wait for a config to be uploaded via the websocket server before starting the processing. The available commands are: + +### General +- `getversion` : read the CamillaDSP version + * response is `OK:GETVERSION:1.2.3` +- `stop` : stop processing and wait for a new config to be uploaded with `setconfig` + * response is `OK:STOP` +- `exit` : stop processing and exit + * response is `OK:EXIT` + +### Websocket server settings + +Commands for reading and changing settings for the websocket server. +- `getupdateinterval` : get the update interval in ms for capture rate and signalrange. + * response is `OK:GETUPDATEINTERVAL:123456` +- `setupdateinterval:` : set the update interval in ms for capturerate and signalrange. + * response is `OK:SETUPDATEINTERVAL` + +### Read processing status + +Commands for reading status parameters. - `getstate` : get the current state of the processing. Possible values are: * "RUNNING": the processing is running normally. * "PAUSED": processing is paused because the input signal is silent. * "INACTIVE": the program is inactive and waiting for a new configuration. * response is `OK:GETSTATE:RUNNING` -- `getversion` : read the CamillaDSP version - * response is `OK:GETVERSION:1.2.3` -- `getconfig` : read the current configuration as yaml - * response is `OK:GETCONFIG:(yamldata)` where yamldata is the config in yaml format. -- `getconfigjson` : read the current configuration as json - * response is `OK:GETCONFIG:(jsondata)` where yamldata is the config in JSON format. -- `getconfigname` : get name and path of current config file - * response is `OK:GETCONFIGNAME:/path/to/current.yml` - `getcapturerate` : get the measured sample rate of the capture device. * response is `OK:GETCAPTURERATE:123456` -- `getupdateinterval` : get the update interval in ms for capture rate and signalrange. - * response is `OK:GETUPDATEINTERVAL:123456` -- `setupdateinterval:` : set the update interval in ms for capturerate and signalrange. - * response is `OK:SETUPDATEINTERVAL` - `getsignalrange` : get the range of values in the last chunk. A value of 2.0 means full level (signal swings from -1.0 to +1.0) * response is `OK:GETSIGNALRANGE:1.23456` - `getrateadjust` : get the adjustment factor applied to the asynchronous resampler. * response is `OK:GETRATEADJUST:1.0023` -- `reload` : reload current config file (same as SIGHUP) - * response is `OK:RELOAD` or `ERROR:RELOAD` -- `stop` : stop processing and wait for a new config to be uploaded with `setconfig` -- `exit` : stop processing and exit + +### Config management + +Commands for reading and changing the active configuration +- `getconfig` : read the current configuration as yaml + * response is `OK:GETCONFIG:(yamldata)` where yamldata is the config in yaml format. +- `getconfigjson` : read the current configuration as json + * response is `OK:GETCONFIG:(jsondata)` where yamldata is the config in JSON format. +- `getconfigname` : get name and path of current config file + * response is `OK:GETCONFIGNAME:/path/to/current.yml` - `setconfigname:/path/to/file.yml` : change config file name, not applied until `reload` is called * response is `OK:/path/to/file.yml` or `ERROR:/path/to/file.yml` - `setconfig:` : provide a new config as a yaml string. Applied directly. * response is `OK:SETCONFIG` or `ERROR:SETCONFIG` - `setconfigjson:` : provide a new config as a JSON string. Applied directly. * response is `OK:SETCONFIGJSON` or `ERROR:SETCONFIGJSON` +- `reload` : reload current config file (same as SIGHUP) + * response is `OK:RELOAD` or `ERROR:RELOAD` + +### Config reading and checking + +These commands are used to check the syntax and contents of configurations. They do not affect the active configuration. +- `readconfig:` : read the provided config, check for syntax errors, and return the config with all optional fields filled with their default values. + * response is `OK:READCONFIG:` or `ERROR:READCONFIG:` +- `readconfigfile:/path/to/file.yml` : read a config from a file, check for syntax errors, and return the config with all optional fields filled with their default values. + * response is `OK:READCONFIGFILE:` or `ERROR:READCONFIGFILE:` +- `validateconfig:` : read the provided config, check for syntax errors and validate the contents. Returns the config with all optional fields filled with their default values. + * response is `OK:VALIDATECONFIG:` or `ERROR:VALIDATECONFIG:` + ## Controlling from Python using pyCamillaDSP From ffe527df2b7e0d97a33c0bc7efd932193b7e5fa2 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Tue, 18 Aug 2020 23:25:38 +0200 Subject: [PATCH 41/61] Format --- src/socketserver.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/socketserver.rs b/src/socketserver.rs index 55b03fe8..f4be335f 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -228,13 +228,15 @@ pub fn start_server( } } } - WSCommand::ReadConfig(config_yml) => match serde_yaml::from_str::(&config_yml) { - Ok(config) => socket.send(format!( - "OK:READCONFIG:{}", - serde_yaml::to_string(&config).unwrap() - )), - Err(error) => socket.send(format!("ERROR:READCONFIG:{}", error)), - }, + WSCommand::ReadConfig(config_yml) => { + match serde_yaml::from_str::(&config_yml) { + Ok(config) => socket.send(format!( + "OK:READCONFIG:{}", + serde_yaml::to_string(&config).unwrap() + )), + Err(error) => socket.send(format!("ERROR:READCONFIG:{}", error)), + } + } WSCommand::ReadConfigFile(path) => match config::load_config(&path) { Ok(config) => socket.send(format!( "OK:READCONFIGFILE:{}", From 231d2c3f33ae81a957f002567352ba1cb79d9bdd Mon Sep 17 00:00:00 2001 From: HEnquist Date: Wed, 19 Aug 2020 08:10:22 +0200 Subject: [PATCH 42/61] Correct sign of Alsa rate adjust speed --- src/alsadevice.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/alsadevice.rs b/src/alsadevice.rs index 93050244..4d0267b1 100644 --- a/src/alsadevice.rs +++ b/src/alsadevice.rs @@ -230,7 +230,7 @@ fn playback_loop_bytes( let av_delay = delay / ndelays; diff = av_delay - params.target_level as isize; let rel_diff = (diff as f64) / (srate as f64); - speed = 1.0 + 0.5 * rel_diff / params.adjust_period as f64; + speed = 1.0 - 0.5 * rel_diff / params.adjust_period as f64; debug!( "Current buffer level {}, set capture rate to {}%", av_delay, @@ -314,7 +314,7 @@ fn capture_loop_bytes( Ok(CommandMessage::SetSpeed { speed }) => { rate_adjust = speed; if let Some(elem) = &element { - elval.set_integer(0, (100_000.0 * speed) as i32).unwrap(); + elval.set_integer(0, (100_000.0 / speed) as i32).unwrap(); elem.write(&elval).unwrap(); } else if let Some(resampl) = &mut resampler { if params.async_src { From e966c0b6e07f1de5c61d43dd76ea8ebcfb95bbc8 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Thu, 20 Aug 2020 21:07:51 +0200 Subject: [PATCH 43/61] Add support for stdin --- src/audiodevice.rs | 47 ++++++++++++++++++++++- src/config.rs | 11 ++++++ src/filedevice.rs | 96 +++++++++++++++++++--------------------------- 3 files changed, 96 insertions(+), 58 deletions(-) diff --git a/src/audiodevice.rs b/src/audiodevice.rs index c56a7615..6c8af572 100644 --- a/src/audiodevice.rs +++ b/src/audiodevice.rs @@ -2,6 +2,7 @@ #[cfg(all(feature = "alsa-backend", target_os = "linux"))] use alsadevice; use config; +use config::SampleFormat; #[cfg(feature = "cpal-backend")] use cpaldevice; use filedevice; @@ -29,6 +30,29 @@ pub enum AudioMessage { EndOfStream, } +pub fn get_bits_per_sample(format: &SampleFormat) -> usize { + match format { + SampleFormat::S16LE => 16, + SampleFormat::S24LE => 24, + SampleFormat::S24LE3 => 24, + SampleFormat::S32LE => 32, + SampleFormat::FLOAT32LE => 32, + SampleFormat::FLOAT64LE => 64, + } +} + +pub fn get_bytes_per_sample(format: &SampleFormat) -> usize { + match format { + SampleFormat::S16LE => 2, + SampleFormat::S24LE => 4, + SampleFormat::S24LE3 => 3, + SampleFormat::S32LE => 4, + SampleFormat::FLOAT32LE => 4, + SampleFormat::FLOAT64LE => 8, + } +} + + /// Main container of audio data pub struct AudioChunk { pub frames: usize, @@ -377,7 +401,28 @@ pub fn get_capture_device(conf: config::Devices) -> Box { skip_bytes, read_bytes, } => Box::new(filedevice::FileCaptureDevice { - filename, + source: filedevice::CaptureSource::Filename(filename), + samplerate: conf.samplerate, + enable_resampling: conf.enable_resampling, + capture_samplerate, + resampler_conf: conf.resampler_type, + chunksize: conf.chunksize, + channels, + format, + extra_samples, + silence_threshold: conf.silence_threshold, + silence_timeout: conf.silence_timeout, + skip_bytes, + read_bytes, + }), + config::CaptureDevice::Stdin { + channels, + format, + extra_samples, + skip_bytes, + read_bytes, + } => Box::new(filedevice::FileCaptureDevice { + source: filedevice::CaptureSource::Stdin, samplerate: conf.samplerate, enable_resampling: conf.enable_resampling, capture_samplerate, diff --git a/src/config.rs b/src/config.rs index 236d68d5..5678eb5b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -75,6 +75,16 @@ pub enum CaptureDevice { #[serde(default)] read_bytes: usize, }, + Stdin { + channels: usize, + format: SampleFormat, + #[serde(default)] + extra_samples: usize, + #[serde(default)] + skip_bytes: usize, + #[serde(default)] + read_bytes: usize, + }, #[cfg(all(feature = "cpal-backend", target_os = "macos"))] CoreAudio { channels: usize, @@ -554,6 +564,7 @@ pub fn validate_config(conf: Configuration) -> Res<()> { #[cfg(feature = "pulse-backend")] CaptureDevice::Pulse { channels, .. } => channels, CaptureDevice::File { channels, .. } => channels, + CaptureDevice::Stdin { channels, .. } => channels, #[cfg(all(feature = "cpal-backend", target_os = "macos"))] CaptureDevice::CoreAudio { channels, .. } => channels, #[cfg(all(feature = "cpal-backend", target_os = "windows"))] diff --git a/src/filedevice.rs b/src/filedevice.rs index 2d7e4f8c..978d7280 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -7,7 +7,7 @@ use conversions::{ }; use std::fs::File; use std::io::ErrorKind; -use std::io::{Read, Write}; +use std::io::{Read, Write, stdin}; use std::sync::mpsc; use std::sync::{Arc, Barrier, RwLock}; use std::thread; @@ -30,8 +30,14 @@ pub struct FilePlaybackDevice { pub format: SampleFormat, } +#[derive(Clone)] +pub enum CaptureSource { + Filename(String), + Stdin, +} + pub struct FileCaptureDevice { - pub filename: String, + pub source: CaptureSource, pub chunksize: usize, pub samplerate: usize, pub enable_resampling: bool, @@ -92,22 +98,8 @@ impl PlaybackDevice for FilePlaybackDevice { let filename = self.filename.clone(); let chunksize = self.chunksize; let channels = self.channels; - let bits = match self.format { - SampleFormat::S16LE => 16, - SampleFormat::S24LE => 24, - SampleFormat::S24LE3 => 24, - SampleFormat::S32LE => 32, - SampleFormat::FLOAT32LE => 32, - SampleFormat::FLOAT64LE => 64, - }; - let store_bytes = match self.format { - SampleFormat::S16LE => 2, - SampleFormat::S24LE => 4, - SampleFormat::S24LE3 => 3, - SampleFormat::S32LE => 4, - SampleFormat::FLOAT32LE => 4, - SampleFormat::FLOAT64LE => 8, - }; + let bits = get_bits_per_sample(&self.format); + let store_bytes = get_bytes_per_sample(&self.format); let format = self.format.clone(); let handle = thread::Builder::new() .name("FilePlayback".to_string()) @@ -120,7 +112,7 @@ impl PlaybackDevice for FilePlaybackDevice { Err(_err) => {} } //let scalefactor = (1< { - chunk_to_buffer_float_bytes(chunk, &mut buffer, bits) + chunk_to_buffer_float_bytes(chunk, &mut buffer, bits as i32) } }; let write_res = file.write(&buffer[0..bytes]); @@ -237,7 +229,7 @@ fn get_capture_bytes( } fn capture_loop( - mut file: File, + mut file: Box, params: CaptureParams, msg_channels: CaptureChannels, mut resampler: Option>>, @@ -411,27 +403,13 @@ impl CaptureDevice for FileCaptureDevice { command_channel: mpsc::Receiver, capture_status: Arc>, ) -> Res>> { - let filename = self.filename.clone(); + let source = self.source.clone(); let samplerate = self.samplerate; let chunksize = self.chunksize; let capture_samplerate = self.capture_samplerate; let channels = self.channels; - let bits = match self.format { - SampleFormat::S16LE => 16, - SampleFormat::S24LE => 24, - SampleFormat::S24LE3 => 24, - SampleFormat::S32LE => 32, - SampleFormat::FLOAT32LE => 32, - SampleFormat::FLOAT64LE => 64, - }; - let store_bytes = match self.format { - SampleFormat::S16LE => 2, - SampleFormat::S24LE => 4, - SampleFormat::S24LE3 => 3, - SampleFormat::S32LE => 4, - SampleFormat::FLOAT32LE => 4, - SampleFormat::FLOAT64LE => 8, - }; + let bits = get_bits_per_sample(&self.format); + let store_bytes = get_bytes_per_sample(&self.format); let buffer_bytes = 2.0f32.powf( (capture_samplerate as f32 / samplerate as f32 * chunksize as f32) .log2() @@ -465,29 +443,33 @@ impl CaptureDevice for FileCaptureDevice { } else { None }; - match File::open(filename) { + let params = CaptureParams { + channels, + bits: bits as i32, + bytes_per_sample: store_bytes, + format, + store_bytes, + extra_bytes, + buffer_bytes, + silent_limit, + silence, + chunksize, + resampling_ratio: samplerate as f32 / capture_samplerate as f32, + read_bytes, + async_src, + capture_status, + }; + let file_res: Result, std::io::Error> = match source { + CaptureSource::Filename(filename) => File::open(filename).map(|f| Box::new(f) as Box), + CaptureSource::Stdin => Ok(Box::new(stdin())), + }; + match file_res { Ok(mut file) => { match status_channel.send(StatusMessage::CaptureReady) { Ok(()) => {} Err(_err) => {} } barrier.wait(); - let params = CaptureParams { - channels, - bits, - bytes_per_sample: store_bytes, - format, - store_bytes, - extra_bytes, - buffer_bytes, - silent_limit, - silence, - chunksize, - resampling_ratio: samplerate as f32 / capture_samplerate as f32, - read_bytes, - async_src, - capture_status, - }; let msg_channels = CaptureChannels { audio: channel, status: status_channel, @@ -537,7 +519,7 @@ fn send_silence( } } -fn read_retry(file: &mut File, mut buf: &mut [u8]) -> Res { +fn read_retry(file: &mut dyn Read, mut buf: &mut [u8]) -> Res { let requested = buf.len(); while !buf.is_empty() { match file.read(buf) { From cf1d0db3fc3092abe18ea9466a619a578291b9d7 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Thu, 20 Aug 2020 21:20:12 +0200 Subject: [PATCH 44/61] Support stdout --- src/audiodevice.rs | 12 ++++++++++-- src/config.rs | 5 +++++ src/filedevice.rs | 30 ++++++++++++++++++++++++------ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/audiodevice.rs b/src/audiodevice.rs index 6c8af572..af757f59 100644 --- a/src/audiodevice.rs +++ b/src/audiodevice.rs @@ -52,7 +52,6 @@ pub fn get_bytes_per_sample(format: &SampleFormat) -> usize { } } - /// Main container of audio data pub struct AudioChunk { pub frames: usize, @@ -162,7 +161,16 @@ pub fn get_playback_device(conf: config::Devices) -> Box { format, .. } => Box::new(filedevice::FilePlaybackDevice { - filename, + destination: filedevice::PlaybackDest::Filename(filename), + samplerate: conf.samplerate, + chunksize: conf.chunksize, + channels, + format, + }), + config::PlaybackDevice::Stdout { + channels, format, .. + } => Box::new(filedevice::FilePlaybackDevice { + destination: filedevice::PlaybackDest::Stdout, samplerate: conf.samplerate, chunksize: conf.chunksize, channels, diff --git a/src/config.rs b/src/config.rs index 5678eb5b..beb3c3f3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -120,6 +120,10 @@ pub enum PlaybackDevice { filename: String, format: SampleFormat, }, + Stdout { + channels: usize, + format: SampleFormat, + }, #[cfg(all(feature = "cpal-backend", target_os = "macos"))] CoreAudio { channels: usize, @@ -616,6 +620,7 @@ pub fn validate_config(conf: Configuration) -> Res<()> { #[cfg(feature = "pulse-backend")] PlaybackDevice::Pulse { channels, .. } => channels, PlaybackDevice::File { channels, .. } => channels, + PlaybackDevice::Stdout { channels, .. } => channels, #[cfg(all(feature = "cpal-backend", target_os = "macos"))] PlaybackDevice::CoreAudio { channels, .. } => channels, #[cfg(all(feature = "cpal-backend", target_os = "windows"))] diff --git a/src/filedevice.rs b/src/filedevice.rs index 978d7280..f128e844 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -7,7 +7,7 @@ use conversions::{ }; use std::fs::File; use std::io::ErrorKind; -use std::io::{Read, Write, stdin}; +use std::io::{stdin, stdout, Read, Write}; use std::sync::mpsc; use std::sync::{Arc, Barrier, RwLock}; use std::thread; @@ -23,7 +23,7 @@ use Res; use StatusMessage; pub struct FilePlaybackDevice { - pub filename: String, + pub destination: PlaybackDest, pub chunksize: usize, pub samplerate: usize, pub channels: usize, @@ -36,6 +36,12 @@ pub enum CaptureSource { Stdin, } +#[derive(Clone)] +pub enum PlaybackDest { + Filename(String), + Stdout, +} + pub struct FileCaptureDevice { pub source: CaptureSource, pub chunksize: usize, @@ -95,7 +101,7 @@ impl PlaybackDevice for FilePlaybackDevice { barrier: Arc, status_channel: mpsc::Sender, ) -> Res>> { - let filename = self.filename.clone(); + let destination = self.destination.clone(); let chunksize = self.chunksize; let channels = self.channels; let bits = get_bits_per_sample(&self.format); @@ -105,7 +111,13 @@ impl PlaybackDevice for FilePlaybackDevice { .name("FilePlayback".to_string()) .spawn(move || { //let delay = time::Duration::from_millis((4*1000*chunksize/samplerate) as u64); - match File::create(filename) { + let file_res: Result, std::io::Error> = match destination { + PlaybackDest::Filename(filename) => { + File::create(filename).map(|f| Box::new(f) as Box) + } + PlaybackDest::Stdout => Ok(Box::new(stdout())), + }; + match file_res { Ok(mut file) => { match status_channel.send(StatusMessage::PlaybackReady) { Ok(()) => {} @@ -132,7 +144,11 @@ impl PlaybackDevice for FilePlaybackDevice { store_bytes, ), SampleFormat::FLOAT32LE | SampleFormat::FLOAT64LE => { - chunk_to_buffer_float_bytes(chunk, &mut buffer, bits as i32) + chunk_to_buffer_float_bytes( + chunk, + &mut buffer, + bits as i32, + ) } }; let write_res = file.write(&buffer[0..bytes]); @@ -460,7 +476,9 @@ impl CaptureDevice for FileCaptureDevice { capture_status, }; let file_res: Result, std::io::Error> = match source { - CaptureSource::Filename(filename) => File::open(filename).map(|f| Box::new(f) as Box), + CaptureSource::Filename(filename) => { + File::open(filename).map(|f| Box::new(f) as Box) + } CaptureSource::Stdin => Ok(Box::new(stdin())), }; match file_res { From 9495004f8008592cebbfec6cd5db51bf10fae004 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Thu, 20 Aug 2020 21:48:22 +0200 Subject: [PATCH 45/61] Update readme and changelog --- CHANGELOG.md | 13 +++--- README.md | 83 +++++++++++++++++++--------------- exampleconfigs/stdio_capt.yml | 3 +- exampleconfigs/stdio_inout.yml | 6 +-- exampleconfigs/stdio_pb.yml | 3 +- 5 files changed, 57 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1161938b..76621561 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,13 @@ New features: - New commands to get more information from the websocket server. - Possible to skip lines or bytes in coefficient files. -- Updated Cpal library -- Improved error messages +- Updated Cpal library. +- Added capture and playback devices Stdin & Stdout. +- Improved error messages. Bugfixes: -- Fix websocket `exit` command -- Correct response of `setconfigname` websocket command +- Fix websocket `exit` command. +- Correct response of `setconfigname` websocket command. ## 0.3.1 @@ -17,7 +18,7 @@ New features: ## 0.3.0 New features: -- Support for Windows (Wasapi) and macOS (CoreAudio) via the Cpal library +- Support for Windows (Wasapi) and macOS (CoreAudio) via the Cpal library. ## 0.2.2 @@ -29,7 +30,7 @@ New features: ## 0.2.1 New features: -- Convolver was optimized to be up to a factor 2 faster +- Convolver was optimized to be up to a factor 2 faster. ## 0.2.0 New features: diff --git a/README.md b/README.md index 17ed3651..f5215982 100644 --- a/README.md +++ b/README.md @@ -438,9 +438,18 @@ devices: * `capture` and `playback` + Input and output devices are defined in the same way. A device needs: - * `type`: Alsa, Pulse, Wasapi, CoreAudio or File + * `type`: + The available types depend on which features that were included when compiling. All possible types are: + * `Alsa` + * `Pulse` + * `Wasapi` + * `CoreAudio` + * `File` + * `Stdin` (capture only) + * `Stdout` (playback only) * `channels`: number of channels * `device`: device name (for Alsa, Pulse, Wasapi, CoreAudio) * `filename` path the the file (for File) @@ -454,42 +463,42 @@ devices: * FLOAT32LE - 32 bit float, stored as four bytes * FLOAT64LE - 64 bit float, stored as eight bytes - Supported formats: - | | Alsa | Pulse | Wasapi | CoreAudio | - |------------|--------------------|--------------------|--------------------|--------------------| - | S16LE | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | S24LE | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | - | S24LE3 | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | - | S32LE | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | - | FLOAT32LE | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | FLOAT64LE | :heavy_check_mark: | :x: | :x: | :x: | - - - Equivalent formats (for reference): - | CamillaDSP | Alsa | Pulse | - |------------|------------|-----------| - | S16LE | S16_LE | S16LE | - | S24LE | S24_LE | S24_32LE | - | S24LE3 | S24_3LE | S24LE | - | S32LE | S32_LE | S32LE | - | FLOAT32LE | FLOAT_LE | FLOAT32LE | - | FLOAT64LE | FLOAT64_LE | - | - - The File capture device supports two additional optional parameters, for advanced handling of raw files and testing: - * `skip_bytes`: Number of bytes to skip at the beginning of the file. This can be used to skip over the header of some formats like .wav (which typocally has a fixed size 44-byte header). Leaving it out or setting to zero means no bytes are skipped. - * `read_bytes`: Read only up until the specified number of bytes. Leave it out to read until the end of the file. - - The File device type reads or writes to a file. - The format is raw interleaved samples, 2 bytes per sample for 16-bit, - and 4 bytes per sample for 24 and 32 bits. - If the capture device reaches the end of a file, the program will exit once all chunks have been played. - That delayed sound that would end up in a later chunk will be cut off. To avoid this, set the optional parameter `extra_samples` for the File capture device. - This causes the capture device to yield the given number of samples (per channel) after reaching end of file, allowing any delayed sound to be played back. - By setting the filename to `/dev/stdin` for capture, or `/dev/stdout` for playback, the sound will be written to or read from stdio, so one can play with pipes: - ``` - > camilladsp stdio_capt.yml > rawfile.dat - > cat rawfile.dat | camilladsp stdio_pb.yml - ``` + Supported formats: + | | Alsa | Pulse | Wasapi | CoreAudio | File/Stdin/Stdout | + |------------|--------------------|--------------------|--------------------|--------------------|--------------------| + | S16LE | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | + | S24LE | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | + | S24LE3 | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | + | S32LE | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | + | FLOAT32LE | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | + | FLOAT64LE | :heavy_check_mark: | :x: | :x: | :x: | :heavy_check_mark: | + + + Equivalent formats (for reference): + | CamillaDSP | Alsa | Pulse | + |------------|------------|-----------| + | S16LE | S16_LE | S16LE | + | S24LE | S24_LE | S24_32LE | + | S24LE3 | S24_3LE | S24LE | + | S32LE | S32_LE | S32LE | + | FLOAT32LE | FLOAT_LE | FLOAT32LE | + | FLOAT64LE | FLOAT64_LE | - | + + The __File__ device type reads or writes to a file, while __Stdin__ reads from stdin and __Stdout__ writes to stdout. + The format is raw interleaved samples, in the selected sample format. + If the capture device reaches the end of a file, the program will exit once all chunks have been played. + That delayed sound that would end up in a later chunk will be cut off. To avoid this, set the optional parameter `extra_samples` for the File capture device. + This causes the capture device to yield the given number of samples (per channel) after reaching end of file, allowing any delayed sound to be played back. + The __Stdin__ capture device and __Stdout__ playback device use stdin and stdout, so it's possible to easily pipe audio between applications: + ``` + > camilladsp stdio_capt.yml > rawfile.dat + > cat rawfile.dat | camilladsp stdio_pb.yml + ``` + Note: On Unix-like systems it's also possible to use the File device and set the filename to `/dev/stdin` for capture, or `/dev/stdout` for playback. + + The __File__ and __Stdin__ capture devices support two additional optional parameters, for advanced handling of raw files and testing: + * `skip_bytes`: Number of bytes to skip at the beginning of the file or stream. This can be used to skip over the header of some formats like .wav (which typically has a fixed size 44-byte header). Leaving it out or setting to zero means no bytes are skipped. + * `read_bytes`: Read only up until the specified number of bytes. Leave it out to read until the end of the file or stream. ## Resampling diff --git a/exampleconfigs/stdio_capt.yml b/exampleconfigs/stdio_capt.yml index 6e6ae36c..984477f0 100644 --- a/exampleconfigs/stdio_capt.yml +++ b/exampleconfigs/stdio_capt.yml @@ -10,7 +10,6 @@ devices: device: "MySink.monitor" format: S16LE playback: - type: File + type: Stdout channels: 2 - filename: "/dev/stdout" format: S16LE diff --git a/exampleconfigs/stdio_inout.yml b/exampleconfigs/stdio_inout.yml index 3e88bea9..2292d7ff 100644 --- a/exampleconfigs/stdio_inout.yml +++ b/exampleconfigs/stdio_inout.yml @@ -5,12 +5,10 @@ devices: silence_threshold: -60 silence_timeout: 3.0 capture: - type: File + type: Stdin channels: 2 - filename: "/dev/stdin" format: S16LE playback: - type: File + type: Stdout channels: 2 - filename: "/dev/stdout" format: S16LE diff --git a/exampleconfigs/stdio_pb.yml b/exampleconfigs/stdio_pb.yml index 5f4156cb..032fa15c 100644 --- a/exampleconfigs/stdio_pb.yml +++ b/exampleconfigs/stdio_pb.yml @@ -5,9 +5,8 @@ devices: silence_threshold: -60 silence_timeout: 3.0 capture: - type: File + type: Stdin channels: 2 - filename: "/dev/stdin" format: S16LE playback: type: Pulse From 4a8fbb9c26a8de73b331c189139390e1dda3a36f Mon Sep 17 00:00:00 2001 From: HEnquist Date: Fri, 21 Aug 2020 20:28:02 +0200 Subject: [PATCH 46/61] Refactor SampleFormat bits and bytes --- src/alsadevice.rs | 56 ++++++-------------------------------- src/audiodevice.rs | 23 ---------------- src/config.rs | 47 ++++++++++++++++++++++++++++++++ src/cpaldevice.rs | 18 ++----------- src/filedevice.rs | 40 ++++++++++++--------------- src/pulsedevice.rs | 67 +++++++++++++--------------------------------- 6 files changed, 93 insertions(+), 158 deletions(-) diff --git a/src/alsadevice.rs b/src/alsadevice.rs index 4d0267b1..34f04e16 100644 --- a/src/alsadevice.rs +++ b/src/alsadevice.rs @@ -444,30 +444,10 @@ impl PlaybackDevice for AlsaPlaybackDevice { let samplerate = self.samplerate; let chunksize = self.chunksize; let channels = self.channels; - let bits: i32 = match self.format { - SampleFormat::S16LE => 16, - SampleFormat::S24LE => 24, - SampleFormat::S24LE3 => 24, - SampleFormat::S32LE => 32, - SampleFormat::FLOAT32LE => 32, - SampleFormat::FLOAT64LE => 64, - }; - let bytes_per_sample = match self.format { - SampleFormat::S16LE => 2, - SampleFormat::S24LE => 4, - SampleFormat::S24LE3 => 3, - SampleFormat::S32LE => 4, - SampleFormat::FLOAT32LE => 4, - SampleFormat::FLOAT64LE => 8, - }; - let floats = match self.format { - SampleFormat::S16LE - | SampleFormat::S24LE - | SampleFormat::S24LE3 - | SampleFormat::S32LE => false, - SampleFormat::FLOAT32LE | SampleFormat::FLOAT64LE => true, - }; - let format = self.format.clone(); + let bits = self.format.bits_per_sample() as i32; + let bytes_per_sample = self.format.bytes_per_sample(); + let floats = self.format.is_float(); + let sample_format = self.format.clone(); let handle = thread::Builder::new() .name("AlsaPlayback".to_string()) .spawn(move || { @@ -477,7 +457,7 @@ impl PlaybackDevice for AlsaPlaybackDevice { samplerate as u32, chunksize as MachInt, channels as u32, - &format, + &sample_format, false, ) { Ok(pcmdevice) => { @@ -544,29 +524,9 @@ impl CaptureDevice for AlsaCaptureDevice { ) as usize; println!("Buffer frames {}", buffer_frames); let channels = self.channels; - let bits: i32 = match self.format { - SampleFormat::S16LE => 16, - SampleFormat::S24LE => 24, - SampleFormat::S24LE3 => 24, - SampleFormat::S32LE => 32, - SampleFormat::FLOAT32LE => 32, - SampleFormat::FLOAT64LE => 64, - }; - let bytes_per_sample = match self.format { - SampleFormat::S16LE => 2, - SampleFormat::S24LE => 4, - SampleFormat::S24LE3 => 3, - SampleFormat::S32LE => 4, - SampleFormat::FLOAT32LE => 4, - SampleFormat::FLOAT64LE => 8, - }; - let floats = match self.format { - SampleFormat::S16LE - | SampleFormat::S24LE - | SampleFormat::S24LE3 - | SampleFormat::S32LE => false, - SampleFormat::FLOAT32LE | SampleFormat::FLOAT64LE => true, - }; + let bits = self.format.bits_per_sample() as i32; + let bytes_per_sample = self.format.bytes_per_sample(); + let floats = self.format.is_float(); let mut silence: PrcFmt = 10.0; silence = silence.powf(self.silence_threshold / 20.0); let silent_limit = (self.silence_timeout * ((samplerate / chunksize) as PrcFmt)) as usize; diff --git a/src/audiodevice.rs b/src/audiodevice.rs index af757f59..9895e92c 100644 --- a/src/audiodevice.rs +++ b/src/audiodevice.rs @@ -2,7 +2,6 @@ #[cfg(all(feature = "alsa-backend", target_os = "linux"))] use alsadevice; use config; -use config::SampleFormat; #[cfg(feature = "cpal-backend")] use cpaldevice; use filedevice; @@ -30,28 +29,6 @@ pub enum AudioMessage { EndOfStream, } -pub fn get_bits_per_sample(format: &SampleFormat) -> usize { - match format { - SampleFormat::S16LE => 16, - SampleFormat::S24LE => 24, - SampleFormat::S24LE3 => 24, - SampleFormat::S32LE => 32, - SampleFormat::FLOAT32LE => 32, - SampleFormat::FLOAT64LE => 64, - } -} - -pub fn get_bytes_per_sample(format: &SampleFormat) -> usize { - match format { - SampleFormat::S16LE => 2, - SampleFormat::S24LE => 4, - SampleFormat::S24LE3 => 3, - SampleFormat::S32LE => 4, - SampleFormat::FLOAT32LE => 4, - SampleFormat::FLOAT64LE => 8, - } -} - /// Main container of audio data pub struct AudioChunk { pub frames: usize, diff --git a/src/config.rs b/src/config.rs index beb3c3f3..34fd8347 100644 --- a/src/config.rs +++ b/src/config.rs @@ -48,6 +48,53 @@ pub enum SampleFormat { FLOAT64LE, } +#[derive(Clone, Debug)] +pub enum NumberFamily { + Integer, + Float, +} + +impl SampleFormat { + pub fn bits_per_sample(&self) -> usize { + match self { + SampleFormat::S16LE => 16, + SampleFormat::S24LE => 24, + SampleFormat::S24LE3 => 24, + SampleFormat::S32LE => 32, + SampleFormat::FLOAT32LE => 32, + SampleFormat::FLOAT64LE => 64, + } + } + + pub fn bytes_per_sample(&self) -> usize { + match self { + SampleFormat::S16LE => 2, + SampleFormat::S24LE => 4, + SampleFormat::S24LE3 => 3, + SampleFormat::S32LE => 4, + SampleFormat::FLOAT32LE => 4, + SampleFormat::FLOAT64LE => 8, + } + } + + pub fn number_family(&self) -> NumberFamily { + match self { + SampleFormat::S16LE + | SampleFormat::S24LE + | SampleFormat::S24LE3 + | SampleFormat::S32LE => NumberFamily::Integer, + SampleFormat::FLOAT32LE | SampleFormat::FLOAT64LE => NumberFamily::Float, + } + } + + pub fn is_float(&self) -> bool { + match self { + SampleFormat::FLOAT32LE | SampleFormat::FLOAT64LE => true, + _ => false, + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(deny_unknown_fields)] #[serde(tag = "type")] diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index 5edb5fa8..b8e6662c 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -170,14 +170,7 @@ impl PlaybackDevice for CpalPlaybackDevice { let chunksize_clone = chunksize; let channels_clone = channels; - let bits = match self.format { - SampleFormat::S16LE => 16, - SampleFormat::S24LE => 24, - SampleFormat::S24LE3 => 24, - SampleFormat::S32LE => 32, - SampleFormat::FLOAT32LE => 32, - SampleFormat::FLOAT64LE => 64, - }; + let bits = self.format.bits_per_sample() as i32; let format = self.format.clone(); let handle = thread::Builder::new() .name("CpalPlayback".to_string()) @@ -366,14 +359,7 @@ impl CaptureDevice for CpalCaptureDevice { let capture_samplerate = self.capture_samplerate; let chunksize = self.chunksize; let channels = self.channels; - let bits = match self.format { - SampleFormat::S16LE => 16, - SampleFormat::S24LE => 24, - SampleFormat::S24LE3 => 24, - SampleFormat::S32LE => 32, - SampleFormat::FLOAT32LE => 32, - SampleFormat::FLOAT64LE => 64, - }; + let bits = self.format.bits_per_sample() as i32; let format = self.format.clone(); let enable_resampling = self.enable_resampling; let resampler_conf = self.resampler_conf.clone(); diff --git a/src/filedevice.rs b/src/filedevice.rs index f128e844..5df484ee 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -1,5 +1,6 @@ use audiodevice::*; use config; +use config::NumberFamily; use config::SampleFormat; use conversions::{ buffer_to_chunk_bytes, buffer_to_chunk_float_bytes, chunk_to_buffer_bytes, @@ -104,8 +105,8 @@ impl PlaybackDevice for FilePlaybackDevice { let destination = self.destination.clone(); let chunksize = self.chunksize; let channels = self.channels; - let bits = get_bits_per_sample(&self.format); - let store_bytes = get_bytes_per_sample(&self.format); + let bits = self.format.bits_per_sample(); + let store_bytes = self.format.bytes_per_sample(); let format = self.format.clone(); let handle = thread::Builder::new() .name("FilePlayback".to_string()) @@ -132,24 +133,19 @@ impl PlaybackDevice for FilePlaybackDevice { loop { match channel.recv() { Ok(AudioMessage::Audio(chunk)) => { - let bytes = match format { - SampleFormat::S16LE - | SampleFormat::S24LE - | SampleFormat::S24LE3 - | SampleFormat::S32LE => chunk_to_buffer_bytes( + let bytes = match format.number_family() { + NumberFamily::Integer => chunk_to_buffer_bytes( chunk, &mut buffer, scalefactor, bits as i32, store_bytes, ), - SampleFormat::FLOAT32LE | SampleFormat::FLOAT64LE => { - chunk_to_buffer_float_bytes( - chunk, - &mut buffer, - bits as i32, - ) - } + NumberFamily::Float => chunk_to_buffer_float_bytes( + chunk, + &mut buffer, + bits as i32, + ), }; let write_res = file.write(&buffer[0..bytes]); match write_res { @@ -213,13 +209,11 @@ fn build_chunk( bytes_read: usize, scalefactor: PrcFmt, ) -> AudioChunk { - match format { - SampleFormat::S16LE | SampleFormat::S24LE | SampleFormat::S24LE3 | SampleFormat::S32LE => { + match format.number_family() { + NumberFamily::Integer => { buffer_to_chunk_bytes(&buf, channels, scalefactor, bytes_per_sample, bytes_read) } - SampleFormat::FLOAT32LE | SampleFormat::FLOAT64LE => { - buffer_to_chunk_float_bytes(&buf, channels, bits, bytes_read) - } + NumberFamily::Float => buffer_to_chunk_float_bytes(&buf, channels, bits, bytes_read), } } @@ -424,8 +418,8 @@ impl CaptureDevice for FileCaptureDevice { let chunksize = self.chunksize; let capture_samplerate = self.capture_samplerate; let channels = self.channels; - let bits = get_bits_per_sample(&self.format); - let store_bytes = get_bytes_per_sample(&self.format); + let bits = self.format.bits_per_sample(); + let store_bytes = self.format.bytes_per_sample(); let buffer_bytes = 2.0f32.powf( (capture_samplerate as f32 / samplerate as f32 * chunksize as f32) .log2() @@ -434,7 +428,7 @@ impl CaptureDevice for FileCaptureDevice { * 2 * channels * store_bytes; - let format = self.format.clone(); + let sample_format = self.format.clone(); let enable_resampling = self.enable_resampling; let resampler_conf = self.resampler_conf.clone(); let async_src = resampler_is_async(&resampler_conf); @@ -463,7 +457,7 @@ impl CaptureDevice for FileCaptureDevice { channels, bits: bits as i32, bytes_per_sample: store_bytes, - format, + format: sample_format, store_bytes, extra_bytes, buffer_bytes, diff --git a/src/pulsedevice.rs b/src/pulsedevice.rs index 14bf7d63..61b68b5e 100644 --- a/src/pulsedevice.rs +++ b/src/pulsedevice.rs @@ -49,7 +49,7 @@ fn open_pulse( devname: String, samplerate: u32, channels: u8, - format: &SampleFormat, + sample_format: &SampleFormat, capture: bool, ) -> Res { // Open the device @@ -59,7 +59,7 @@ fn open_pulse( Direction::Playback }; - let pulse_format = match format { + let pulse_format = match sample_format { SampleFormat::S16LE => sample::SAMPLE_S16NE, SampleFormat::S24LE => sample::SAMPLE_S24_32NE, SampleFormat::S24LE3 => sample::SAMPLE_S24NE, @@ -68,14 +68,7 @@ fn open_pulse( _ => panic!("invalid format"), }; - let bytes = match format { - SampleFormat::S16LE => 2, - SampleFormat::S24LE => 4, - SampleFormat::S24LE3 => 3, - SampleFormat::S32LE => 4, - SampleFormat::FLOAT32LE => 4, - SampleFormat::FLOAT64LE => 8, - }; + let bytes = sample_format.bytes_per_sample(); let spec = sample::Spec { format: pulse_format, @@ -117,28 +110,20 @@ impl PlaybackDevice for PulsePlaybackDevice { let samplerate = self.samplerate; let chunksize = self.chunksize; let channels = self.channels; - let bits = match self.format { - SampleFormat::S16LE => 16, - SampleFormat::S24LE => 24, - SampleFormat::S24LE3 => 24, - SampleFormat::S32LE => 32, - SampleFormat::FLOAT32LE => 32, - SampleFormat::FLOAT64LE => 64, - }; - let store_bytes = match self.format { - SampleFormat::S16LE => 2, - SampleFormat::S24LE => 4, - SampleFormat::S24LE3 => 3, - SampleFormat::S32LE => 4, - SampleFormat::FLOAT32LE => 4, - SampleFormat::FLOAT64LE => 8, - }; - let format = self.format.clone(); + let bits = self.format.bits_per_sample() as i32; + let store_bytes = self.format.bytes_per_sample(); + let sample_format = self.format.clone(); let handle = thread::Builder::new() .name("PulsePlayback".to_string()) .spawn(move || { //let delay = time::Duration::from_millis((4*1000*chunksize/samplerate) as u64); - match open_pulse(devname, samplerate as u32, channels as u8, &format, false) { + match open_pulse( + devname, + samplerate as u32, + channels as u8, + &sample_format, + false, + ) { Ok(pulsedevice) => { match status_channel.send(StatusMessage::PlaybackReady) { Ok(()) => {} @@ -153,7 +138,7 @@ impl PlaybackDevice for PulsePlaybackDevice { loop { match channel.recv() { Ok(AudioMessage::Audio(chunk)) => { - match format { + match sample_format { SampleFormat::S16LE | SampleFormat::S24LE | SampleFormat::S32LE => { @@ -239,22 +224,8 @@ impl CaptureDevice for PulseCaptureDevice { let capture_samplerate = self.capture_samplerate; let chunksize = self.chunksize; let channels = self.channels; - let bits = match self.format { - SampleFormat::S16LE => 16, - SampleFormat::S24LE => 24, - SampleFormat::S24LE3 => 24, - SampleFormat::S32LE => 32, - SampleFormat::FLOAT32LE => 32, - SampleFormat::FLOAT64LE => 64, - }; - let store_bytes = match self.format { - SampleFormat::S16LE => 2, - SampleFormat::S24LE3 => 3, - SampleFormat::S24LE => 4, - SampleFormat::S32LE => 4, - SampleFormat::FLOAT32LE => 4, - SampleFormat::FLOAT64LE => 8, - }; + let bits = self.format.bits_per_sample() as i32; + let store_bytes = self.format.bytes_per_sample(); let buffer_bytes = 2.0f32.powf( (capture_samplerate as f32 / samplerate as f32 * chunksize as f32) .log2() @@ -263,7 +234,7 @@ impl CaptureDevice for PulseCaptureDevice { * 2 * channels * store_bytes; - let format = self.format.clone(); + let sample_format = self.format.clone(); let enable_resampling = self.enable_resampling; let resampler_conf = self.resampler_conf.clone(); let async_src = resampler_is_async(&resampler_conf); @@ -289,7 +260,7 @@ impl CaptureDevice for PulseCaptureDevice { devname, capture_samplerate as u32, channels as u8, - &format, + &sample_format, true, ) { Ok(pulsedevice) => { @@ -374,7 +345,7 @@ impl CaptureDevice for PulseCaptureDevice { } }; //let before = Instant::now(); - let mut chunk = match format { + let mut chunk = match sample_format { SampleFormat::S16LE | SampleFormat::S24LE | SampleFormat::S32LE => { buffer_to_chunk_bytes( &buf[0..capture_bytes], From 9c22a304849ccbef892f70664e6381e269532c2a Mon Sep 17 00:00:00 2001 From: HEnquist Date: Fri, 21 Aug 2020 20:39:11 +0200 Subject: [PATCH 47/61] Improve naming in filedevice --- src/filedevice.rs | 63 ++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/src/filedevice.rs b/src/filedevice.rs index 5df484ee..54069764 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -72,10 +72,9 @@ struct CaptureChannels { struct CaptureParams { channels: usize, - bits: i32, - bytes_per_sample: usize, - format: SampleFormat, - store_bytes: usize, + bits_per_sample: i32, + sample_format: SampleFormat, + store_bytes_per_sample: usize, extra_bytes: usize, buffer_bytes: usize, silent_limit: usize, @@ -105,8 +104,8 @@ impl PlaybackDevice for FilePlaybackDevice { let destination = self.destination.clone(); let chunksize = self.chunksize; let channels = self.channels; - let bits = self.format.bits_per_sample(); - let store_bytes = self.format.bytes_per_sample(); + let bits_per_sample = self.format.bits_per_sample(); + let store_bytes_per_sample = self.format.bytes_per_sample(); let format = self.format.clone(); let handle = thread::Builder::new() .name("FilePlayback".to_string()) @@ -125,11 +124,11 @@ impl PlaybackDevice for FilePlaybackDevice { Err(_err) => {} } //let scalefactor = (1< { @@ -138,13 +137,13 @@ impl PlaybackDevice for FilePlaybackDevice { chunk, &mut buffer, scalefactor, - bits as i32, - store_bytes, + bits_per_sample as i32, + store_bytes_per_sample, ), NumberFamily::Float => chunk_to_buffer_float_bytes( chunk, &mut buffer, - bits as i32, + bits_per_sample as i32, ), }; let write_res = file.write(&buffer[0..bytes]); @@ -185,10 +184,10 @@ fn get_nbr_capture_bytes( resampler: &Option>>, capture_bytes: usize, channels: usize, - store_bytes: usize, + store_bytes_per_sample: usize, ) -> usize { if let Some(resampl) = &resampler { - let new_capture_bytes = resampl.nbr_frames_needed() * channels * store_bytes; + let new_capture_bytes = resampl.nbr_frames_needed() * channels * store_bytes_per_sample; trace!( "Resampler needs {} frames, will read {} bytes", resampl.nbr_frames_needed(), @@ -204,7 +203,7 @@ fn build_chunk( buf: &[u8], format: &SampleFormat, channels: usize, - bits: i32, + bits_per_sample: i32, bytes_per_sample: usize, bytes_read: usize, scalefactor: PrcFmt, @@ -213,7 +212,9 @@ fn build_chunk( NumberFamily::Integer => { buffer_to_chunk_bytes(&buf, channels, scalefactor, bytes_per_sample, bytes_read) } - NumberFamily::Float => buffer_to_chunk_float_bytes(&buf, channels, bits, bytes_read), + NumberFamily::Float => { + buffer_to_chunk_float_bytes(&buf, channels, bits_per_sample, bytes_read) + } } } @@ -245,9 +246,9 @@ fn capture_loop( mut resampler: Option>>, ) { debug!("starting captureloop"); - let scalefactor = (2.0 as PrcFmt).powi(params.bits - 1); + let scalefactor = (2.0 as PrcFmt).powi(params.bits_per_sample - 1); let mut silent_nbr: usize = 0; - let chunksize_bytes = params.channels * params.chunksize * params.store_bytes; + let chunksize_bytes = params.channels * params.chunksize * params.store_bytes_per_sample; let mut buf = vec![0u8; params.buffer_bytes]; let mut bytes_read = 0; let mut capture_bytes = chunksize_bytes; @@ -289,7 +290,7 @@ fn capture_loop( &resampler, capture_bytes, params.channels, - params.store_bytes, + params.store_bytes_per_sample, ); capture_bytes_temp = get_capture_bytes(params.read_bytes, nbr_bytes_read, capture_bytes, &mut buf); @@ -318,7 +319,8 @@ fn capture_loop( } } else if bytes == 0 && capture_bytes > 0 { debug!("Reached end of file"); - let extra_samples = extra_bytes_left / params.store_bytes / params.channels; + let extra_samples = + extra_bytes_left / params.store_bytes_per_sample / params.channels; send_silence( extra_samples, params.channels, @@ -341,7 +343,7 @@ fn capture_loop( let meas_time = now.duration_since(start).unwrap().as_secs_f32(); let bytes_per_sec = bytes_counter as f32 / meas_time; let measured_rate_f = - bytes_per_sec / (params.channels * params.store_bytes) as f32; + bytes_per_sec / (params.channels * params.store_bytes_per_sample) as f32; trace!("Measured sample rate is {} Hz", measured_rate_f); let mut capt_stat = params.capture_status.write().unwrap(); capt_stat.measured_samplerate = measured_rate_f as usize; @@ -365,10 +367,10 @@ fn capture_loop( //let before = Instant::now(); let mut chunk = build_chunk( &buf[0..capture_bytes], - ¶ms.format, + ¶ms.sample_format, params.channels, - params.bits, - params.bytes_per_sample, + params.bits_per_sample, + params.store_bytes_per_sample, bytes_read, scalefactor, ); @@ -418,8 +420,8 @@ impl CaptureDevice for FileCaptureDevice { let chunksize = self.chunksize; let capture_samplerate = self.capture_samplerate; let channels = self.channels; - let bits = self.format.bits_per_sample(); - let store_bytes = self.format.bytes_per_sample(); + let bits_per_sample = self.format.bits_per_sample(); + let store_bytes_per_sample = self.format.bytes_per_sample(); let buffer_bytes = 2.0f32.powf( (capture_samplerate as f32 / samplerate as f32 * chunksize as f32) .log2() @@ -427,12 +429,12 @@ impl CaptureDevice for FileCaptureDevice { ) as usize * 2 * channels - * store_bytes; + * store_bytes_per_sample; let sample_format = self.format.clone(); let enable_resampling = self.enable_resampling; let resampler_conf = self.resampler_conf.clone(); let async_src = resampler_is_async(&resampler_conf); - let extra_bytes = self.extra_samples * store_bytes * channels; + let extra_bytes = self.extra_samples * store_bytes_per_sample * channels; let skip_bytes = self.skip_bytes; let read_bytes = self.read_bytes; let mut silence: PrcFmt = 10.0; @@ -455,10 +457,9 @@ impl CaptureDevice for FileCaptureDevice { }; let params = CaptureParams { channels, - bits: bits as i32, - bytes_per_sample: store_bytes, - format: sample_format, - store_bytes, + bits_per_sample: bits_per_sample as i32, + sample_format, + store_bytes_per_sample, extra_bytes, buffer_bytes, silent_limit, From db31e8b4efc99eaac482d6e11e4a6959e209f54a Mon Sep 17 00:00:00 2001 From: HEnquist Date: Fri, 21 Aug 2020 20:56:22 +0200 Subject: [PATCH 48/61] Refactor variable names --- src/alsadevice.rs | 48 +++++++++++++++++++++++----------------------- src/audiodevice.rs | 12 ++++++------ src/filedevice.rs | 24 +++++++++++------------ 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/alsadevice.rs b/src/alsadevice.rs index 34f04e16..b67b804d 100644 --- a/src/alsadevice.rs +++ b/src/alsadevice.rs @@ -35,7 +35,7 @@ pub struct AlsaPlaybackDevice { pub samplerate: usize, pub chunksize: usize, pub channels: usize, - pub format: SampleFormat, + pub sample_format: SampleFormat, pub target_level: usize, pub adjust_period: f32, pub enable_rate_adjust: bool, @@ -50,7 +50,7 @@ pub struct AlsaCaptureDevice { pub resampler_conf: config::Resampler, pub chunksize: usize, pub channels: usize, - pub format: SampleFormat, + pub sample_format: SampleFormat, pub silence_threshold: PrcFmt, pub silence_timeout: PrcFmt, } @@ -72,8 +72,8 @@ struct CaptureParams { silent_limit: usize, silence: PrcFmt, chunksize: usize, - bits: i32, - bytes_per_sample: usize, + bits_per_sample: i32, + store_bytes_per_sample: usize, floats: bool, samplerate: usize, capture_samplerate: usize, @@ -138,7 +138,7 @@ fn open_pcm( samplerate: u32, bufsize: MachInt, channels: u32, - format: &SampleFormat, + sample_format: &SampleFormat, capture: bool, ) -> Res { // Open the device @@ -153,7 +153,7 @@ fn open_pcm( let hwp = HwParams::any(&pcmdev)?; hwp.set_channels(channels)?; hwp.set_rate(samplerate, ValueOr::Nearest)?; - match format { + match sample_format { SampleFormat::S16LE => hwp.set_format(Format::s16())?, SampleFormat::S24LE => hwp.set_format(Format::s24())?, SampleFormat::S24LE3 => hwp.set_format(Format::S243LE)?, @@ -296,7 +296,7 @@ fn capture_loop_bytes( warn!("Async resampler not needed since capture device supports rate adjust. Switch to Sync type to save CPU time."); } } - let mut capture_bytes = params.chunksize * params.channels * params.bytes_per_sample; + let mut capture_bytes = params.chunksize * params.channels * params.store_bytes_per_sample; let mut start = SystemTime::now(); let mut now; let mut bytes_counter = 0; @@ -341,7 +341,7 @@ fn capture_loop_bytes( let meas_time = now.duration_since(start).unwrap().as_secs_f32(); let bytes_per_sec = bytes_counter as f32 / meas_time; let measured_rate_f = - bytes_per_sec / (params.channels * params.bytes_per_sample) as f32; + bytes_per_sec / (params.channels * params.store_bytes_per_sample) as f32; trace!("Measured sample rate is {} Hz", measured_rate_f); let mut capt_stat = params.capture_status.write().unwrap(); capt_stat.measured_samplerate = measured_rate_f as usize; @@ -365,7 +365,7 @@ fn capture_loop_bytes( buffer_to_chunk_float_bytes( &buffer[0..capture_bytes], params.channels, - params.bits, + params.bits_per_sample, capture_bytes, ) } else { @@ -373,7 +373,7 @@ fn capture_loop_bytes( &buffer[0..capture_bytes], params.channels, params.scalefactor, - params.bytes_per_sample, + params.store_bytes_per_sample, capture_bytes, ) }; @@ -414,7 +414,7 @@ fn get_nbr_capture_bytes( ) -> usize { let capture_bytes_new = if let Some(resampl) = &resampler { trace!("Resamper needs {} frames", resampl.nbr_frames_needed()); - resampl.nbr_frames_needed() * params.channels * params.bytes_per_sample + resampl.nbr_frames_needed() * params.channels * params.store_bytes_per_sample } else { capture_bytes }; @@ -444,10 +444,10 @@ impl PlaybackDevice for AlsaPlaybackDevice { let samplerate = self.samplerate; let chunksize = self.chunksize; let channels = self.channels; - let bits = self.format.bits_per_sample() as i32; - let bytes_per_sample = self.format.bytes_per_sample(); - let floats = self.format.is_float(); - let sample_format = self.format.clone(); + let bits = self.sample_format.bits_per_sample() as i32; + let bytes_per_sample = self.sample_format.bytes_per_sample(); + let floats = self.sample_format.is_float(); + let sample_format = self.sample_format.clone(); let handle = thread::Builder::new() .name("AlsaPlayback".to_string()) .spawn(move || { @@ -524,13 +524,13 @@ impl CaptureDevice for AlsaCaptureDevice { ) as usize; println!("Buffer frames {}", buffer_frames); let channels = self.channels; - let bits = self.format.bits_per_sample() as i32; - let bytes_per_sample = self.format.bytes_per_sample(); - let floats = self.format.is_float(); + let bits_per_sample = self.sample_format.bits_per_sample() as i32; + let store_bytes_per_sample = self.sample_format.bytes_per_sample(); + let floats = self.sample_format.is_float(); let mut silence: PrcFmt = 10.0; silence = silence.powf(self.silence_threshold / 20.0); let silent_limit = (self.silence_timeout * ((samplerate / chunksize) as PrcFmt)) as usize; - let format = self.format.clone(); + let sample_format = self.sample_format.clone(); let enable_resampling = self.enable_resampling; let resampler_conf = self.resampler_conf.clone(); let async_src = resampler_is_async(&resampler_conf); @@ -554,7 +554,7 @@ impl CaptureDevice for AlsaCaptureDevice { capture_samplerate as u32, buffer_frames as MachInt, channels as u32, - &format, + &sample_format, true, ) { Ok(pcmdevice) => { @@ -562,7 +562,7 @@ impl CaptureDevice for AlsaCaptureDevice { Ok(()) => {} Err(_err) => {} } - let scalefactor = (2.0 as PrcFmt).powi(bits - 1); + let scalefactor = (2.0 as PrcFmt).powi(bits_per_sample - 1); barrier.wait(); debug!("Starting captureloop"); let cap_params = CaptureParams { @@ -571,8 +571,8 @@ impl CaptureDevice for AlsaCaptureDevice { silent_limit, silence, chunksize, - bits, - bytes_per_sample, + bits_per_sample, + store_bytes_per_sample, floats, samplerate, capture_samplerate, @@ -585,7 +585,7 @@ impl CaptureDevice for AlsaCaptureDevice { command: command_channel, }; let io = pcmdevice.io(); - let buffer = vec![0u8; channels * buffer_frames * bytes_per_sample]; + let buffer = vec![0u8; channels * buffer_frames * store_bytes_per_sample]; capture_loop_bytes( cap_channels, buffer, diff --git a/src/audiodevice.rs b/src/audiodevice.rs index 9895e92c..11312498 100644 --- a/src/audiodevice.rs +++ b/src/audiodevice.rs @@ -115,7 +115,7 @@ pub fn get_playback_device(conf: config::Devices) -> Box { samplerate: conf.samplerate, chunksize: conf.chunksize, channels, - format, + sample_format: format, target_level: conf.target_level, adjust_period: conf.adjust_period, enable_rate_adjust: conf.enable_rate_adjust, @@ -142,7 +142,7 @@ pub fn get_playback_device(conf: config::Devices) -> Box { samplerate: conf.samplerate, chunksize: conf.chunksize, channels, - format, + sample_format: format, }), config::PlaybackDevice::Stdout { channels, format, .. @@ -151,7 +151,7 @@ pub fn get_playback_device(conf: config::Devices) -> Box { samplerate: conf.samplerate, chunksize: conf.chunksize, channels, - format, + sample_format: format, }), #[cfg(all(feature = "cpal-backend", target_os = "macos"))] config::PlaybackDevice::CoreAudio { @@ -357,7 +357,7 @@ pub fn get_capture_device(conf: config::Devices) -> Box { resampler_conf: conf.resampler_type, chunksize: conf.chunksize, channels, - format, + sample_format: format, silence_threshold: conf.silence_threshold, silence_timeout: conf.silence_timeout, }), @@ -393,7 +393,7 @@ pub fn get_capture_device(conf: config::Devices) -> Box { resampler_conf: conf.resampler_type, chunksize: conf.chunksize, channels, - format, + sample_format: format, extra_samples, silence_threshold: conf.silence_threshold, silence_timeout: conf.silence_timeout, @@ -414,7 +414,7 @@ pub fn get_capture_device(conf: config::Devices) -> Box { resampler_conf: conf.resampler_type, chunksize: conf.chunksize, channels, - format, + sample_format: format, extra_samples, silence_threshold: conf.silence_threshold, silence_timeout: conf.silence_timeout, diff --git a/src/filedevice.rs b/src/filedevice.rs index 54069764..9b0368a9 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -28,7 +28,7 @@ pub struct FilePlaybackDevice { pub chunksize: usize, pub samplerate: usize, pub channels: usize, - pub format: SampleFormat, + pub sample_format: SampleFormat, } #[derive(Clone)] @@ -51,7 +51,7 @@ pub struct FileCaptureDevice { pub capture_samplerate: usize, pub resampler_conf: config::Resampler, pub channels: usize, - pub format: SampleFormat, + pub sample_format: SampleFormat, pub silence_threshold: PrcFmt, pub silence_timeout: PrcFmt, pub extra_samples: usize, @@ -104,9 +104,9 @@ impl PlaybackDevice for FilePlaybackDevice { let destination = self.destination.clone(); let chunksize = self.chunksize; let channels = self.channels; - let bits_per_sample = self.format.bits_per_sample(); - let store_bytes_per_sample = self.format.bytes_per_sample(); - let format = self.format.clone(); + let bits_per_sample = self.sample_format.bits_per_sample(); + let store_bytes_per_sample = self.sample_format.bytes_per_sample(); + let sample_format = self.sample_format.clone(); let handle = thread::Builder::new() .name("FilePlayback".to_string()) .spawn(move || { @@ -132,7 +132,7 @@ impl PlaybackDevice for FilePlaybackDevice { loop { match channel.recv() { Ok(AudioMessage::Audio(chunk)) => { - let bytes = match format.number_family() { + let valid_bytes = match sample_format.number_family() { NumberFamily::Integer => chunk_to_buffer_bytes( chunk, &mut buffer, @@ -146,7 +146,7 @@ impl PlaybackDevice for FilePlaybackDevice { bits_per_sample as i32, ), }; - let write_res = file.write(&buffer[0..bytes]); + let write_res = file.write(&buffer[0..valid_bytes]); match write_res { Ok(_) => {} Err(msg) => { @@ -201,14 +201,14 @@ fn get_nbr_capture_bytes( fn build_chunk( buf: &[u8], - format: &SampleFormat, + sample_format: &SampleFormat, channels: usize, bits_per_sample: i32, bytes_per_sample: usize, bytes_read: usize, scalefactor: PrcFmt, ) -> AudioChunk { - match format.number_family() { + match sample_format.number_family() { NumberFamily::Integer => { buffer_to_chunk_bytes(&buf, channels, scalefactor, bytes_per_sample, bytes_read) } @@ -420,8 +420,8 @@ impl CaptureDevice for FileCaptureDevice { let chunksize = self.chunksize; let capture_samplerate = self.capture_samplerate; let channels = self.channels; - let bits_per_sample = self.format.bits_per_sample(); - let store_bytes_per_sample = self.format.bytes_per_sample(); + let bits_per_sample = self.sample_format.bits_per_sample(); + let store_bytes_per_sample = self.sample_format.bytes_per_sample(); let buffer_bytes = 2.0f32.powf( (capture_samplerate as f32 / samplerate as f32 * chunksize as f32) .log2() @@ -430,7 +430,7 @@ impl CaptureDevice for FileCaptureDevice { * 2 * channels * store_bytes_per_sample; - let sample_format = self.format.clone(); + let sample_format = self.sample_format.clone(); let enable_resampling = self.enable_resampling; let resampler_conf = self.resampler_conf.clone(); let async_src = resampler_is_async(&resampler_conf); From 03395c42539b2c48fd35bab4678f1dcd6a47bade Mon Sep 17 00:00:00 2001 From: HEnquist Date: Fri, 21 Aug 2020 20:56:53 +0200 Subject: [PATCH 49/61] Refactor variable names --- src/filedevice.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/filedevice.rs b/src/filedevice.rs index 9b0368a9..4441a04a 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -204,13 +204,13 @@ fn build_chunk( sample_format: &SampleFormat, channels: usize, bits_per_sample: i32, - bytes_per_sample: usize, + store_bytes_per_sample: usize, bytes_read: usize, scalefactor: PrcFmt, ) -> AudioChunk { match sample_format.number_family() { NumberFamily::Integer => { - buffer_to_chunk_bytes(&buf, channels, scalefactor, bytes_per_sample, bytes_read) + buffer_to_chunk_bytes(&buf, channels, scalefactor, store_bytes_per_sample, bytes_read) } NumberFamily::Float => { buffer_to_chunk_float_bytes(&buf, channels, bits_per_sample, bytes_read) From 70766ec522c8391d3f3458d978497fa342bb2b56 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Fri, 21 Aug 2020 21:03:58 +0200 Subject: [PATCH 50/61] Refactor variable names --- src/audiodevice.rs | 4 ++-- src/filedevice.rs | 10 ++++++--- src/pulsedevice.rs | 54 +++++++++++++++++++++++++--------------------- 3 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/audiodevice.rs b/src/audiodevice.rs index 11312498..eb46902e 100644 --- a/src/audiodevice.rs +++ b/src/audiodevice.rs @@ -130,7 +130,7 @@ pub fn get_playback_device(conf: config::Devices) -> Box { samplerate: conf.samplerate, chunksize: conf.chunksize, channels, - format, + sample_format: format, }), config::PlaybackDevice::File { channels, @@ -374,7 +374,7 @@ pub fn get_capture_device(conf: config::Devices) -> Box { capture_samplerate, chunksize: conf.chunksize, channels, - format, + sample_format: format, silence_threshold: conf.silence_threshold, silence_timeout: conf.silence_timeout, }), diff --git a/src/filedevice.rs b/src/filedevice.rs index 4441a04a..34610714 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -209,9 +209,13 @@ fn build_chunk( scalefactor: PrcFmt, ) -> AudioChunk { match sample_format.number_family() { - NumberFamily::Integer => { - buffer_to_chunk_bytes(&buf, channels, scalefactor, store_bytes_per_sample, bytes_read) - } + NumberFamily::Integer => buffer_to_chunk_bytes( + &buf, + channels, + scalefactor, + store_bytes_per_sample, + bytes_read, + ), NumberFamily::Float => { buffer_to_chunk_float_bytes(&buf, channels, bits_per_sample, bytes_read) } diff --git a/src/pulsedevice.rs b/src/pulsedevice.rs index 61b68b5e..7e495827 100644 --- a/src/pulsedevice.rs +++ b/src/pulsedevice.rs @@ -28,7 +28,7 @@ pub struct PulsePlaybackDevice { pub samplerate: usize, pub chunksize: usize, pub channels: usize, - pub format: SampleFormat, + pub sample_format: SampleFormat, } pub struct PulseCaptureDevice { @@ -39,7 +39,7 @@ pub struct PulseCaptureDevice { pub capture_samplerate: usize, pub chunksize: usize, pub channels: usize, - pub format: SampleFormat, + pub sample_format: SampleFormat, pub silence_threshold: PrcFmt, pub silence_timeout: PrcFmt, } @@ -68,7 +68,7 @@ fn open_pulse( _ => panic!("invalid format"), }; - let bytes = sample_format.bytes_per_sample(); + let bytes_per_sample = sample_format.bytes_per_sample(); let spec = sample::Spec { format: pulse_format, @@ -79,9 +79,9 @@ fn open_pulse( let attr = pulse::def::BufferAttr { maxlength: std::u32::MAX, tlength: std::u32::MAX, - prebuf: bytes as u32, + prebuf: bytes_per_sample as u32, minreq: std::u32::MAX, - fragsize: bytes as u32, + fragsize: bytes_per_sample as u32, }; let pulsedev = Simple::new( @@ -110,9 +110,9 @@ impl PlaybackDevice for PulsePlaybackDevice { let samplerate = self.samplerate; let chunksize = self.chunksize; let channels = self.channels; - let bits = self.format.bits_per_sample() as i32; - let store_bytes = self.format.bytes_per_sample(); - let sample_format = self.format.clone(); + let bits_per_sample = self.sample_format.bits_per_sample() as i32; + let store_bytes_per_sample = self.sample_format.bytes_per_sample(); + let sample_format = self.sample_format.clone(); let handle = thread::Builder::new() .name("PulsePlayback".to_string()) .spawn(move || { @@ -130,11 +130,11 @@ impl PlaybackDevice for PulsePlaybackDevice { Err(_err) => {} } //let scalefactor = (1< { @@ -146,12 +146,16 @@ impl PlaybackDevice for PulsePlaybackDevice { chunk, &mut buffer, scalefactor, - bits, - store_bytes, + bits_per_sample, + store_bytes_per_sample, ); } SampleFormat::FLOAT32LE => { - chunk_to_buffer_float_bytes(chunk, &mut buffer, bits); + chunk_to_buffer_float_bytes( + chunk, + &mut buffer, + bits_per_sample, + ); } _ => panic!("Unsupported sample format!"), }; @@ -194,10 +198,10 @@ fn get_nbr_capture_bytes( resampler: &Option>>, capture_bytes: usize, channels: usize, - store_bytes: usize, + store_bytes_per_sample: usize, ) -> usize { if let Some(resampl) = &resampler { - let new_capture_bytes = resampl.nbr_frames_needed() * channels * store_bytes; + let new_capture_bytes = resampl.nbr_frames_needed() * channels * store_bytes_per_sample; trace!( "Resampler needs {} frames, will read {} bytes", resampl.nbr_frames_needed(), @@ -224,8 +228,8 @@ impl CaptureDevice for PulseCaptureDevice { let capture_samplerate = self.capture_samplerate; let chunksize = self.chunksize; let channels = self.channels; - let bits = self.format.bits_per_sample() as i32; - let store_bytes = self.format.bytes_per_sample(); + let bits_per_sample = self.sample_format.bits_per_sample() as i32; + let store_bytes_per_sample = self.sample_format.bytes_per_sample(); let buffer_bytes = 2.0f32.powf( (capture_samplerate as f32 / samplerate as f32 * chunksize as f32) .log2() @@ -233,8 +237,8 @@ impl CaptureDevice for PulseCaptureDevice { ) as usize * 2 * channels - * store_bytes; - let sample_format = self.format.clone(); + * store_bytes_per_sample; + let sample_format = self.sample_format.clone(); let enable_resampling = self.enable_resampling; let resampler_conf = self.resampler_conf.clone(); let async_src = resampler_is_async(&resampler_conf); @@ -268,12 +272,12 @@ impl CaptureDevice for PulseCaptureDevice { Ok(()) => {} Err(_err) => {} } - let scalefactor = (2.0 as PrcFmt).powi(bits - 1); + let scalefactor = (2.0 as PrcFmt).powi(bits_per_sample - 1); let mut silent_nbr: usize = 0; barrier.wait(); debug!("starting captureloop"); let mut buf = vec![0u8; buffer_bytes]; - let chunksize_bytes = channels * chunksize * store_bytes; + let chunksize_bytes = channels * chunksize * store_bytes_per_sample; let mut capture_bytes = chunksize_bytes; let mut start = SystemTime::now(); let mut now; @@ -308,7 +312,7 @@ impl CaptureDevice for PulseCaptureDevice { &resampler, capture_bytes, channels, - store_bytes, + store_bytes_per_sample, ); if capture_bytes > buf.len() { debug!("Capture buffer too small, extending"); @@ -322,7 +326,7 @@ impl CaptureDevice for PulseCaptureDevice { if now.duration_since(start).unwrap().as_millis() as usize > capture_status.read().unwrap().update_interval { let meas_time = now.duration_since(start).unwrap().as_secs_f32(); let bytes_per_sec = bytes_counter as f32 / meas_time; - let measured_rate_f = bytes_per_sec / (channels * store_bytes) as f32; + let measured_rate_f = bytes_per_sec / (channels * store_bytes_per_sample) as f32; trace!( "Measured sample rate is {} Hz", measured_rate_f @@ -351,14 +355,14 @@ impl CaptureDevice for PulseCaptureDevice { &buf[0..capture_bytes], channels, scalefactor, - store_bytes, + store_bytes_per_sample, capture_bytes, ) } SampleFormat::FLOAT32LE => buffer_to_chunk_float_bytes( &buf[0..capture_bytes], channels, - bits, + bits_per_sample, capture_bytes, ), _ => panic!("Unsupported sample format"), From 913906ccbc15fe7f8b206d6675d44a5a5d1dd286 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Fri, 21 Aug 2020 21:24:45 +0200 Subject: [PATCH 51/61] Refactor variable names --- src/audiodevice.rs | 8 ++++---- src/cpaldevice.rs | 38 +++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/audiodevice.rs b/src/audiodevice.rs index eb46902e..e7970887 100644 --- a/src/audiodevice.rs +++ b/src/audiodevice.rs @@ -164,7 +164,7 @@ pub fn get_playback_device(conf: config::Devices) -> Box { samplerate: conf.samplerate, chunksize: conf.chunksize, channels, - format, + sample_format: format, target_level: conf.target_level, adjust_period: conf.adjust_period, enable_rate_adjust: conf.enable_rate_adjust, @@ -180,7 +180,7 @@ pub fn get_playback_device(conf: config::Devices) -> Box { samplerate: conf.samplerate, chunksize: conf.chunksize, channels, - format, + sample_format: format, target_level: conf.target_level, adjust_period: conf.adjust_period, enable_rate_adjust: conf.enable_rate_adjust, @@ -435,7 +435,7 @@ pub fn get_capture_device(conf: config::Devices) -> Box { capture_samplerate, chunksize: conf.chunksize, channels, - format, + sample_format: format, silence_threshold: conf.silence_threshold, silence_timeout: conf.silence_timeout, }), @@ -453,7 +453,7 @@ pub fn get_capture_device(conf: config::Devices) -> Box { capture_samplerate, chunksize: conf.chunksize, channels, - format, + sample_format: format, silence_threshold: conf.silence_threshold, silence_timeout: conf.silence_timeout, }), diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index b8e6662c..ebee27b8 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -38,7 +38,7 @@ pub struct CpalPlaybackDevice { pub samplerate: usize, pub chunksize: usize, pub channels: usize, - pub format: SampleFormat, + pub sample_format: SampleFormat, pub target_level: usize, pub adjust_period: f32, pub enable_rate_adjust: bool, @@ -54,7 +54,7 @@ pub struct CpalCaptureDevice { pub capture_samplerate: usize, pub chunksize: usize, pub channels: usize, - pub format: SampleFormat, + pub sample_format: SampleFormat, pub silence_threshold: PrcFmt, pub silence_timeout: PrcFmt, } @@ -64,7 +64,7 @@ fn open_cpal_playback( devname: &str, samplerate: usize, channels: usize, - format: &SampleFormat, + sample_format: &SampleFormat, ) -> Res<(Device, StreamConfig, cpal::SampleFormat)> { let host_id = match host_cfg { #[cfg(target_os = "macos")] @@ -84,7 +84,7 @@ fn open_cpal_playback( return Err(ConfigError::new(&msg).into()); } }; - let sample_format = match format { + let cpal_format = match sample_format { SampleFormat::S16LE => cpal::SampleFormat::I16, SampleFormat::FLOAT32LE => cpal::SampleFormat::F32, _ => panic!("Unsupported sample format"), @@ -95,7 +95,7 @@ fn open_cpal_playback( buffer_size: BufferSize::Default, }; debug!("Opened CPAL playback device {}", devname); - Ok((device, stream_config, sample_format)) + Ok((device, stream_config, cpal_format)) } fn open_cpal_capture( @@ -103,7 +103,7 @@ fn open_cpal_capture( devname: &str, samplerate: usize, channels: usize, - format: &SampleFormat, + sample_format: &SampleFormat, ) -> Res<(Device, StreamConfig, cpal::SampleFormat)> { let host_id = match host_cfg { #[cfg(target_os = "macos")] @@ -123,7 +123,7 @@ fn open_cpal_capture( return Err(ConfigError::new(&msg).into()); } }; - let sample_format = match format { + let cpal_format = match sample_format { SampleFormat::S16LE => cpal::SampleFormat::I16, SampleFormat::FLOAT32LE => cpal::SampleFormat::F32, _ => panic!("Unsupported sample format"), @@ -134,7 +134,7 @@ fn open_cpal_capture( buffer_size: BufferSize::Default, }; debug!("Opened CPAL capture device {}", devname); - Ok((device, stream_config, sample_format)) + Ok((device, stream_config, cpal_format)) } fn write_data_to_device(output: &mut [T], queue: &mut VecDeque) @@ -170,18 +170,18 @@ impl PlaybackDevice for CpalPlaybackDevice { let chunksize_clone = chunksize; let channels_clone = channels; - let bits = self.format.bits_per_sample() as i32; - let format = self.format.clone(); + let bits_per_sample = self.sample_format.bits_per_sample() as i32; + let sample_format = self.sample_format.clone(); let handle = thread::Builder::new() .name("CpalPlayback".to_string()) .spawn(move || { - match open_cpal_playback(host_cfg, &devname, samplerate, channels, &format) { + match open_cpal_playback(host_cfg, &devname, samplerate, channels, &sample_format) { Ok((device, stream_config, _sample_format)) => { match status_channel.send(StatusMessage::PlaybackReady) { Ok(()) => {} Err(_err) => {} } - let scalefactor = (2.0 as PrcFmt).powi(bits - 1); + let scalefactor = (2.0 as PrcFmt).powi(bits_per_sample - 1); let (tx_dev, rx_dev) = mpsc::sync_channel(1); let buffer_fill = Arc::new(AtomicUsize::new(0)); @@ -193,7 +193,7 @@ impl PlaybackDevice for CpalPlaybackDevice { let mut speed; let mut diff: isize; - let stream = match format { + let stream = match sample_format { SampleFormat::S16LE => { trace!("Build i16 output stream"); let mut sample_queue: VecDeque = @@ -359,8 +359,8 @@ impl CaptureDevice for CpalCaptureDevice { let capture_samplerate = self.capture_samplerate; let chunksize = self.chunksize; let channels = self.channels; - let bits = self.format.bits_per_sample() as i32; - let format = self.format.clone(); + let bits_per_sample = self.sample_format.bits_per_sample() as i32; + let sample_format = self.sample_format.clone(); let enable_resampling = self.enable_resampling; let resampler_conf = self.resampler_conf.clone(); let async_src = resampler_is_async(&resampler_conf); @@ -382,17 +382,17 @@ impl CaptureDevice for CpalCaptureDevice { } else { None }; - match open_cpal_capture(host_cfg, &devname, capture_samplerate, channels, &format) { + match open_cpal_capture(host_cfg, &devname, capture_samplerate, channels, &sample_format) { Ok((device, stream_config, _sample_format)) => { match status_channel.send(StatusMessage::CaptureReady) { Ok(()) => {} Err(_err) => {} } - let scalefactor = (2.0 as PrcFmt).powi(bits - 1); + let scalefactor = (2.0 as PrcFmt).powi(bits_per_sample - 1); let mut silent_nbr: usize = 0; let (tx_dev_i, rx_dev_i) = mpsc::sync_channel(1); let (tx_dev_f, rx_dev_f) = mpsc::sync_channel(1); - let stream = match format { + let stream = match sample_format { SampleFormat::S16LE => { trace!("Build i16 input stream"); let stream = device.build_input_stream( @@ -493,7 +493,7 @@ impl CaptureDevice for CpalCaptureDevice { channels, ); - let mut chunk = match format { + let mut chunk = match sample_format { SampleFormat::S16LE => { while sample_queue_i.len() < capture_samples { trace!("Read message to fill capture buffer"); From 79ea3a05f3b23d5b51a5ed7571759f4469490477 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Fri, 28 Aug 2020 19:49:42 +0200 Subject: [PATCH 52/61] Fix mixer validation --- src/config.rs | 10 +++++++++- src/mixer.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 34fd8347..f2049494 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,5 @@ use filters; +use mixer; use serde::{Deserialize, Serialize}; use serde_with; use std::collections::HashMap; @@ -638,10 +639,17 @@ pub fn validate_config(conf: Configuration) -> Res<()> { return Err(ConfigError::new(&msg).into()); } num_channels = conf.mixers.get(&name).unwrap().channels.out; + match mixer::validate_mixer(&conf.mixers.get(&name).unwrap()) { + Ok(_) => {} + Err(err) => { + let msg = format!("Invalid mixer '{}'. Reason: {}", name, err); + return Err(ConfigError::new(&msg).into()); + } + } } } PipelineStep::Filter { channel, names } => { - if channel > num_channels { + if channel >= num_channels { let msg = format!("Use of non existing channel {}", channel); return Err(ConfigError::new(&msg).into()); } diff --git a/src/mixer.rs b/src/mixer.rs index 2812ae85..b062385f 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -1,6 +1,7 @@ use audiodevice::AudioChunk; use config; use PrcFmt; +use Res; #[derive(Clone)] pub struct Mixer { @@ -86,3 +87,28 @@ impl Mixer { AudioChunk::from(input, waveforms) } } + +/// Validate the mixer config, to give a helpful message intead of a panic. +pub fn validate_mixer(mixer_config: &config::Mixer) -> Res<()> { + let chan_in = mixer_config.channels.r#in; + let chan_out = mixer_config.channels.out; + for mapping in mixer_config.mapping.iter() { + if mapping.dest >= chan_out { + let msg = format!( + "Invalid destination channel {}, max is {}.", + mapping.dest, chan_out-1 + ); + return Err(config::ConfigError::new(&msg).into()); + } + for source in mapping.sources.iter() { + if source.channel >= chan_in { + let msg = format!( + "Invalid source channel {}, max is {}.", + source.channel, chan_in-1 + ); + return Err(config::ConfigError::new(&msg).into()); + } + } + } + Ok(()) +} From bc247f10f3e1a75c36c0ea2567750d2054bfa0cd Mon Sep 17 00:00:00 2001 From: HEnquist Date: Fri, 28 Aug 2020 19:50:28 +0200 Subject: [PATCH 53/61] Format --- src/mixer.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/mixer.rs b/src/mixer.rs index b062385f..053feec2 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -93,18 +93,20 @@ pub fn validate_mixer(mixer_config: &config::Mixer) -> Res<()> { let chan_in = mixer_config.channels.r#in; let chan_out = mixer_config.channels.out; for mapping in mixer_config.mapping.iter() { - if mapping.dest >= chan_out { + if mapping.dest >= chan_out { let msg = format!( "Invalid destination channel {}, max is {}.", - mapping.dest, chan_out-1 + mapping.dest, + chan_out - 1 ); return Err(config::ConfigError::new(&msg).into()); } for source in mapping.sources.iter() { - if source.channel >= chan_in { + if source.channel >= chan_in { let msg = format!( "Invalid source channel {}, max is {}.", - source.channel, chan_in-1 + source.channel, + chan_in - 1 ); return Err(config::ConfigError::new(&msg).into()); } From 3cd656129239bb97a326b52b3d6894b18d71db1e Mon Sep 17 00:00:00 2001 From: HEnquist Date: Sat, 29 Aug 2020 10:29:16 +0200 Subject: [PATCH 54/61] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76621561..b8dcc324 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ New features: - Updated Cpal library. - Added capture and playback devices Stdin & Stdout. - Improved error messages. +- Improved validation of mixer config Bugfixes: - Fix websocket `exit` command. From 7af0bb8723d1dcd2bd19444c4489b171686216fe Mon Sep 17 00:00:00 2001 From: HEnquist Date: Fri, 4 Sep 2020 20:13:02 +0200 Subject: [PATCH 55/61] Handle pipeline shutdown better --- exampleconfigs/simpleconfig_plot.yml | 8 ++++---- src/alsadevice.rs | 22 ++++++++++++---------- src/bin.rs | 3 +++ src/cpaldevice.rs | 19 +++++++++++-------- src/filedevice.rs | 22 ++++++++++++---------- src/pulsedevice.rs | 19 +++++++++++-------- 6 files changed, 53 insertions(+), 40 deletions(-) diff --git a/exampleconfigs/simpleconfig_plot.yml b/exampleconfigs/simpleconfig_plot.yml index 79dbdaa6..563322f0 100644 --- a/exampleconfigs/simpleconfig_plot.yml +++ b/exampleconfigs/simpleconfig_plot.yml @@ -27,10 +27,10 @@ filters: peak1: type: Biquad parameters: - type: Highshelf - freq: 1000 - slope: 15 - gain: -6 + type: Peaking + freq: 100 + q: 2.0 + gain: -20 mixers: mono: diff --git a/src/alsadevice.rs b/src/alsadevice.rs index b67b804d..3fc8db89 100644 --- a/src/alsadevice.rs +++ b/src/alsadevice.rs @@ -490,11 +490,12 @@ impl PlaybackDevice for AlsaPlaybackDevice { playback_loop_bytes(pb_channels, buffer, &pcmdevice, io, pb_params); } Err(err) => { - status_channel - .send(StatusMessage::PlaybackError { - message: format!("{}", err), - }) - .unwrap(); + let send_result = status_channel.send(StatusMessage::PlaybackError { + message: format!("{}", err), + }); + if send_result.is_err() { + error!("Playback error: {}", err); + } } } }) @@ -596,11 +597,12 @@ impl CaptureDevice for AlsaCaptureDevice { ); } Err(err) => { - status_channel - .send(StatusMessage::CaptureError { - message: format!("{}", err), - }) - .unwrap(); + let send_result = status_channel.send(StatusMessage::CaptureError { + message: format!("{}", err), + }); + if send_result.is_err() { + error!("Capture error: {}", err); + } } } }) diff --git a/src/bin.rs b/src/bin.rs index 80f8065f..beceb5ea 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -445,6 +445,7 @@ fn main() { ); match exitstatus { Err(e) => { + *active_config.lock().unwrap() = None; error!("({}) {}", e.to_string(), e); if !wait { break; @@ -452,12 +453,14 @@ fn main() { } Ok(ExitState::Exit) => { debug!("Exiting"); + *active_config.lock().unwrap() = None; if !wait || signal_exit.load(Ordering::Relaxed) == 1 { // wait mode not active, or exit requested break; } } Ok(ExitState::Restart) => { + *active_config.lock().unwrap() = None; debug!("Restarting with new config"); } }; diff --git a/src/cpaldevice.rs b/src/cpaldevice.rs index ebee27b8..1448898e 100644 --- a/src/cpaldevice.rs +++ b/src/cpaldevice.rs @@ -302,11 +302,12 @@ impl PlaybackDevice for CpalPlaybackDevice { } } Err(err) => { - status_channel - .send(StatusMessage::PlaybackError { - message: format!("{}", err), - }) - .unwrap(); + let send_result = status_channel.send(StatusMessage::PlaybackError { + message: format!("{}", err), + }); + if send_result.is_err() { + error!("Playback error: {}", err); + } } } }) @@ -589,11 +590,13 @@ impl CaptureDevice for CpalCaptureDevice { capt_stat.state = ProcessingState::Inactive; } Err(err) => { - status_channel + let send_result = status_channel .send(StatusMessage::CaptureError { message: format!("{}", err), - }) - .unwrap(); + }); + if send_result.is_err() { + error!("Capture error: {}", err); + } } } }) diff --git a/src/filedevice.rs b/src/filedevice.rs index 34610714..05ca0d8b 100644 --- a/src/filedevice.rs +++ b/src/filedevice.rs @@ -167,11 +167,12 @@ impl PlaybackDevice for FilePlaybackDevice { } } Err(err) => { - status_channel - .send(StatusMessage::PlaybackError { - message: format!("{}", err), - }) - .unwrap(); + let send_result = status_channel.send(StatusMessage::PlaybackError { + message: format!("{}", err), + }); + if send_result.is_err() { + error!("Playback error: {}", err); + } } } }) @@ -501,11 +502,12 @@ impl CaptureDevice for FileCaptureDevice { capture_loop(file, params, msg_channels, resampler); } Err(err) => { - status_channel - .send(StatusMessage::CaptureError { - message: format!("{}", err), - }) - .unwrap(); + let send_result = status_channel.send(StatusMessage::CaptureError { + message: format!("{}", err), + }); + if send_result.is_err() { + error!("Capture error: {}", err); + } } } }) diff --git a/src/pulsedevice.rs b/src/pulsedevice.rs index 7e495827..d3521e82 100644 --- a/src/pulsedevice.rs +++ b/src/pulsedevice.rs @@ -181,11 +181,12 @@ impl PlaybackDevice for PulsePlaybackDevice { } } Err(err) => { - status_channel - .send(StatusMessage::PlaybackError { - message: format!("{}", err), - }) - .unwrap(); + let send_result = status_channel.send(StatusMessage::PlaybackError { + message: format!("{}", err), + }); + if send_result.is_err() { + error!("Playback error: {}", err); + } } } }) @@ -396,11 +397,13 @@ impl CaptureDevice for PulseCaptureDevice { capt_stat.state = ProcessingState::Inactive; } Err(err) => { - status_channel + let send_result = status_channel .send(StatusMessage::CaptureError { message: format!("{}", err), - }) - .unwrap(); + }); + if send_result.is_err() { + error!("Capture error: {}", err); + } } } }) From 50718c327827b1e4f515a809fe9bf11c4d4afc90 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Tue, 8 Sep 2020 21:50:49 +0200 Subject: [PATCH 56/61] Add option to set bind address for websocket --- CHANGELOG.md | 1 + README.md | 9 ++--- src/bin.rs | 38 ++++++++++++++++----- src/socketserver.rs | 83 +++++++++++++++++++++++++-------------------- websocket.md | 2 ++ 5 files changed, 84 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8dcc324..f2ef1382 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ New features: - Added capture and playback devices Stdin & Stdout. - Improved error messages. - Improved validation of mixer config +- Added option to set which IP address to bind websocket server to Bugfixes: - Fix websocket `exit` command. diff --git a/README.md b/README.md index f5215982..f7e6afd1 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,7 @@ This starts the processing defined in the specified config file. The config is f Starting with the --help flag prints a short help message: ``` > camilladsp --help -CamillaDSP 0.1.0 +CamillaDSP 0.3.2 Henrik Enquist A flexible tool for processing audio @@ -247,7 +247,8 @@ FLAGS: -w, --wait Wait for config from websocket OPTIONS: - -p, --port Port for websocket server + -a, --address
IP address to bind websocket server to + -p, --port Port for websocket server ARGS: The configuration file to use @@ -256,6 +257,8 @@ If the "check" flag is given, the program will exit after checking the configura To enable the websocket server, provide a port number with the `-p` option. Leave it out, or give 0 to disable. +By default the websocket server binds to the address 127.0.0.1 which means it's only accessible locally. If it should be also available to remote machines, give the IP address of the interface where it should be available with the `-a` option. Giving 0.0.0.0 will bind to all interfaces. + If the "wait" flag, `-w` is given, CamillaDSP will start the websocket server and wait for a configuration to be uploaded. Then the config file argument must be left out. The default logging setting prints messages of levels "error", "warn" and "info". By passing the verbosity flag once, `-v` it also prints "debug". If and if's given twice, `-vv`, it also prints "trace" messages. @@ -267,8 +270,6 @@ The configuration can be reloaded without restarting by sending a SIGHUP to the ## Controlling via websocket See the [separate readme for the websocket server](./websocket.md) -If the websocket server is enabled with the -p option, CamillaDSP will listen to incoming websocket connections on the specified port. - # Capturing audio diff --git a/src/bin.rs b/src/bin.rs index beceb5ea..5cbbb318 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -41,6 +41,8 @@ use camillalib::config; use camillalib::processing; #[cfg(feature = "socketserver")] use camillalib::socketserver; +#[cfg(feature = "socketserver")] +use std::net::IpAddr; use camillalib::StatusMessage; @@ -336,6 +338,20 @@ fn main() { Err(String::from("Must be an integer between 0 and 65535")) }), ) + .arg( + Arg::with_name("address") + .help("IP address to bind websocket server to") + .short("a") + .long("address") + .takes_value(true) + .requires("port") + .validator(|val: String| -> Result<(), String> { + if val.parse::().is_ok() { + return Ok(()); + } + Err(String::from("Must be a valid IP address")) + }), + ) .arg( Arg::with_name("wait") .short("w") @@ -410,16 +426,20 @@ fn main() { #[cfg(feature = "socketserver")] { if let Some(port_str) = matches.value_of("port") { + let serveraddress = match matches.value_of("address") { + Some(addr) => addr, + None => "127.0.0.1", + }; let serverport = port_str.parse::().unwrap(); - socketserver::start_server( - serverport, - signal_reload.clone(), - signal_exit.clone(), - active_config.clone(), - active_config_path.clone(), - new_config.clone(), - capture_status.clone(), - ); + let shared_data = socketserver::SharedData { + signal_reload: signal_reload.clone(), + signal_exit: signal_exit.clone(), + active_config: active_config.clone(), + active_config_path: active_config_path.clone(), + new_config: new_config.clone(), + capture_status: capture_status.clone(), + }; + socketserver::start_server(serveraddress, serverport, shared_data); } } diff --git a/src/socketserver.rs b/src/socketserver.rs index f4be335f..dffd4cbc 100644 --- a/src/socketserver.rs +++ b/src/socketserver.rs @@ -6,6 +6,16 @@ use std::thread; use crate::CaptureStatus; use config; +#[derive(Debug, Clone)] +pub struct SharedData { + pub signal_reload: Arc, + pub signal_exit: Arc, + pub active_config: Arc>>, + pub active_config_path: Arc>>, + pub new_config: Arc>>, + pub capture_status: Arc>, +} + #[derive(Debug, PartialEq)] enum WSCommand { SetConfigName(String), @@ -110,57 +120,47 @@ fn parse_command(cmd: &ws::Message) -> WSCommand { } } -pub fn start_server( - port: usize, - signal_reload: Arc, - signal_exit: Arc, - active_config_shared: Arc>>, - active_config_path: Arc>>, - new_config_shared: Arc>>, - capture_status: Arc>, -) { +pub fn start_server(bind_address: &str, port: usize, shared_data: SharedData) { + let address = bind_address.to_owned(); debug!("Start websocket server on port {}", port); thread::spawn(move || { - ws::listen(format!("127.0.0.1:{}", port), |socket| { - let signal_reload_inst = signal_reload.clone(); - let signal_exit_inst = signal_exit.clone(); - let active_config_inst = active_config_shared.clone(); - let new_config_inst = new_config_shared.clone(); - let active_config_path_inst = active_config_path.clone(); - let capture_status_inst = capture_status.clone(); + let ws_result = ws::listen(format!("{}:{}", address, port), |socket| { + let shared_data_inst = shared_data.clone(); move |msg: ws::Message| { let command = parse_command(&msg); debug!("parsed command: {:?}", command); match command { WSCommand::Reload => { - signal_reload_inst.store(true, Ordering::Relaxed); + shared_data_inst + .signal_reload + .store(true, Ordering::Relaxed); socket.send("OK:RELOAD") } WSCommand::GetCaptureRate => { - let capstat = capture_status_inst.read().unwrap(); + let capstat = shared_data_inst.capture_status.read().unwrap(); socket.send(format!("OK:GETCAPTURERATE:{}", capstat.measured_samplerate)) } WSCommand::GetSignalRange => { - let capstat = capture_status_inst.read().unwrap(); + let capstat = shared_data_inst.capture_status.read().unwrap(); socket.send(format!("OK:GETSIGNALRANGE:{}", capstat.signal_range)) } WSCommand::GetVersion => { socket.send(format!("OK:GETVERSION:{}", crate_version!())) } WSCommand::GetState => { - let capstat = capture_status_inst.read().unwrap(); + let capstat = shared_data_inst.capture_status.read().unwrap(); socket.send(format!("OK:GETSTATE:{}", &capstat.state.to_string())) } WSCommand::GetRateAdjust => { - let capstat = capture_status_inst.read().unwrap(); + let capstat = shared_data_inst.capture_status.read().unwrap(); socket.send(format!("OK:GETRATEADJUST:{}", capstat.rate_adjust)) } WSCommand::GetUpdateInterval => { - let capstat = capture_status_inst.read().unwrap(); + let capstat = shared_data_inst.capture_status.read().unwrap(); socket.send(format!("OK:GETUPDATEINTERVAL:{}", capstat.update_interval)) } WSCommand::SetUpdateInterval(nbr) => { - let mut capstat = capture_status_inst.write().unwrap(); + let mut capstat = shared_data_inst.capture_status.write().unwrap(); capstat.update_interval = nbr; socket.send("OK:SETUPDATEINTERVAL".to_string()) } @@ -168,19 +168,22 @@ pub fn start_server( //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); socket.send(format!( "OK:GETCONFIG:{}", - serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(), + serde_yaml::to_string(&*shared_data_inst.active_config.lock().unwrap()) + .unwrap(), )) } WSCommand::GetConfigJson => { //let conf_yaml = serde_yaml::to_string(&*active_config_inst.lock().unwrap()).unwrap(); socket.send(format!( "OK:GETCONFIGJSON:{}", - serde_json::to_string(&*active_config_inst.lock().unwrap()).unwrap(), + serde_json::to_string(&*shared_data_inst.active_config.lock().unwrap()) + .unwrap(), )) } WSCommand::GetConfigName => socket.send(format!( "OK:GETCONFIGNAME:{}", - active_config_path_inst + shared_data_inst + .active_config_path .lock() .unwrap() .as_ref() @@ -189,7 +192,8 @@ pub fn start_server( )), WSCommand::SetConfigName(path) => match config::load_validate_config(&path) { Ok(_) => { - *active_config_path_inst.lock().unwrap() = Some(path.clone()); + *shared_data_inst.active_config_path.lock().unwrap() = + Some(path.clone()); socket.send(format!("OK:SETCONFIGNAME:{}", path)) } _ => socket.send("ERROR:SETCONFIGNAME"), @@ -199,8 +203,10 @@ pub fn start_server( Ok(conf) => match config::validate_config(conf.clone()) { Ok(()) => { //*active_config_path_inst.lock().unwrap() = String::from("none"); - *new_config_inst.lock().unwrap() = Some(conf); - signal_reload_inst.store(true, Ordering::Relaxed); + *shared_data_inst.new_config.lock().unwrap() = Some(conf); + shared_data_inst + .signal_reload + .store(true, Ordering::Relaxed); socket.send("OK:SETCONFIG") } _ => socket.send("ERROR:SETCONFIG"), @@ -216,8 +222,10 @@ pub fn start_server( Ok(conf) => match config::validate_config(conf.clone()) { Ok(()) => { //*active_config_path_inst.lock().unwrap() = String::from("none"); - *new_config_inst.lock().unwrap() = Some(conf); - signal_reload_inst.store(true, Ordering::Relaxed); + *shared_data_inst.new_config.lock().unwrap() = Some(conf); + shared_data_inst + .signal_reload + .store(true, Ordering::Relaxed); socket.send("OK:SETCONFIGJSON") } _ => socket.send("ERROR:SETCONFIGJSON"), @@ -262,12 +270,12 @@ pub fn start_server( } } WSCommand::Stop => { - *new_config_inst.lock().unwrap() = None; - signal_exit_inst.store(2, Ordering::Relaxed); + *shared_data_inst.new_config.lock().unwrap() = None; + shared_data_inst.signal_exit.store(2, Ordering::Relaxed); socket.send("OK:STOP") } WSCommand::Exit => { - signal_exit_inst.store(1, Ordering::Relaxed); + shared_data_inst.signal_exit.store(1, Ordering::Relaxed); socket.send("OK:EXIT") } WSCommand::Invalid => { @@ -276,8 +284,11 @@ pub fn start_server( } } } - }) - .unwrap(); + }); + match ws_result { + Ok(_) => {} + Err(err) => error!("Failed to start websocket server: {}", err), + } }); } diff --git a/websocket.md b/websocket.md index dfa87f48..c1785f3a 100644 --- a/websocket.md +++ b/websocket.md @@ -4,6 +4,8 @@ If the websocket server is enabled with the `-p` option, CamillaDSP will listen If additionally the "wait" flag is given, it will wait for a config to be uploaded via the websocket server before starting the processing. +By default the websocket server binds to the address 127.0.0.1, which means it's only accessible locally (on the same machine). If it should be also available to remote machines, give the IP address of the interface where it should be available with the `-a` option. Giving 0.0.0.0 will bind to all interfaces. + The available commands are: ### General From cf17ed5a7009138bc0313b794b997d8bfb70fb9f Mon Sep 17 00:00:00 2001 From: HEnquist Date: Wed, 9 Sep 2020 08:45:41 +0200 Subject: [PATCH 57/61] Show reason for retrying playback and capture --- src/alsadevice.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/alsadevice.rs b/src/alsadevice.rs index 3fc8db89..6b6a01e3 100644 --- a/src/alsadevice.rs +++ b/src/alsadevice.rs @@ -103,8 +103,8 @@ fn play_buffer(buffer: &[u8], pcmdevice: &alsa::PCM, io: &alsa::pcm::IO) -> } let _frames = match io.writei(&buffer[..]) { Ok(frames) => frames, - Err(_err) => { - warn!("Retrying playback"); + Err(err) => { + warn!("Retrying playback, error: {}", err); pcmdevice.prepare()?; let delay = Duration::from_millis(5); thread::sleep(delay); @@ -123,8 +123,8 @@ fn capture_buffer(buffer: &mut [u8], pcmdevice: &alsa::PCM, io: &alsa::pcm::IO frames, - Err(_err) => { - warn!("retrying capture"); + Err(err) => { + warn!("Retrying capture, error: {}", err); pcmdevice.prepare()?; io.readi(buffer)? } @@ -413,7 +413,7 @@ fn get_nbr_capture_bytes( buf: &mut Vec, ) -> usize { let capture_bytes_new = if let Some(resampl) = &resampler { - trace!("Resamper needs {} frames", resampl.nbr_frames_needed()); + trace!("Resampler needs {} frames", resampl.nbr_frames_needed()); resampl.nbr_frames_needed() * params.channels * params.store_bytes_per_sample } else { capture_bytes From c9fb5c88b62521e212a6e76fd2df4bfba5f4fc94 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Wed, 16 Sep 2020 13:57:27 +0200 Subject: [PATCH 58/61] Fix buffer underrun soon after starting Alsa playback --- CHANGELOG.md | 1 + src/alsadevice.rs | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2ef1382..6f76dcad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ New features: Bugfixes: - Fix websocket `exit` command. - Correct response of `setconfigname` websocket command. +- Fix buffer underrun soon after starting Alsa playback. ## 0.3.1 diff --git a/src/alsadevice.rs b/src/alsadevice.rs index 6b6a01e3..12c98fbc 100644 --- a/src/alsadevice.rs +++ b/src/alsadevice.rs @@ -92,22 +92,28 @@ struct PlaybackParams { } /// Play a buffer. -fn play_buffer(buffer: &[u8], pcmdevice: &alsa::PCM, io: &alsa::pcm::IO) -> Res<()> { +fn play_buffer( + buffer: &[u8], + pcmdevice: &alsa::PCM, + io: &alsa::pcm::IO, + target_delay: u64, +) -> Res<()> { let playback_state = pcmdevice.state(); trace!("playback state {:?}", playback_state); if playback_state == State::XRun { - warn!("Prepare playback"); + warn!("Prepare playback after buffer underrun"); pcmdevice.prepare()?; - let delay = Duration::from_millis(5); - thread::sleep(delay); + thread::sleep(Duration::from_millis(target_delay)); + } else if playback_state == State::Prepared { + warn!("Starting playback from Prepared state"); + thread::sleep(Duration::from_millis(target_delay)); } let _frames = match io.writei(&buffer[..]) { Ok(frames) => frames, Err(err) => { warn!("Retrying playback, error: {}", err); pcmdevice.prepare()?; - let delay = Duration::from_millis(5); - thread::sleep(delay); + thread::sleep(Duration::from_millis(target_delay)); io.writei(&buffer[..])? } }; @@ -204,6 +210,7 @@ fn playback_loop_bytes( let mut speed; let mut diff: isize; let adjust = params.adjust_period > 0.0 && params.adjust_enabled; + let target_delay = 1000 * (params.target_level as u64) / srate as u64; loop { match channels.audio.recv() { Ok(AudioMessage::Audio(chunk)) => { @@ -245,7 +252,7 @@ fn playback_loop_bytes( .unwrap(); } - let playback_res = play_buffer(&buffer, pcmdevice, &io); + let playback_res = play_buffer(&buffer, pcmdevice, &io, target_delay); match playback_res { Ok(_) => {} Err(msg) => { From d681b24b4cd87bb653b4c97b2ee23f0ec8b92b25 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Wed, 16 Sep 2020 21:18:11 +0200 Subject: [PATCH 59/61] Change alsa playback start warning to info --- src/alsadevice.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/alsadevice.rs b/src/alsadevice.rs index 12c98fbc..7b9358ec 100644 --- a/src/alsadevice.rs +++ b/src/alsadevice.rs @@ -99,13 +99,13 @@ fn play_buffer( target_delay: u64, ) -> Res<()> { let playback_state = pcmdevice.state(); - trace!("playback state {:?}", playback_state); + trace!("Playback state {:?}", playback_state); if playback_state == State::XRun { warn!("Prepare playback after buffer underrun"); pcmdevice.prepare()?; thread::sleep(Duration::from_millis(target_delay)); } else if playback_state == State::Prepared { - warn!("Starting playback from Prepared state"); + info!("Starting playback from Prepared state"); thread::sleep(Duration::from_millis(target_delay)); } let _frames = match io.writei(&buffer[..]) { From 1f19837960c0cb31a9b185490d5c61a424d5817f Mon Sep 17 00:00:00 2001 From: HEnquist Date: Fri, 18 Sep 2020 00:36:02 +0200 Subject: [PATCH 60/61] Fix FIR scaling on reload --- CHANGELOG.md | 1 + src/fftconv.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f76dcad..18dce7f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Bugfixes: - Fix websocket `exit` command. - Correct response of `setconfigname` websocket command. - Fix buffer underrun soon after starting Alsa playback. +- Correct scaling of FIR coefficients when reloading config. ## 0.3.1 diff --git a/src/fftconv.rs b/src/fftconv.rs index f8497604..5343a3cd 100644 --- a/src/fftconv.rs +++ b/src/fftconv.rs @@ -167,7 +167,7 @@ impl Filter for FFTConv { for (n, coeff) in coeffs.iter().enumerate() { coeffs_padded[n / self.npoints][n % self.npoints] = - coeff / (2.0 * self.npoints as PrcFmt); + coeff / (self.npoints as PrcFmt); } for (segment, segment_f) in coeffs_padded.iter_mut().zip(coeffs_f.iter_mut()) { From 566284329ff9edef9d63e1262cd971ed14d8e379 Mon Sep 17 00:00:00 2001 From: HEnquist Date: Fri, 18 Sep 2020 00:55:47 +0200 Subject: [PATCH 61/61] Remove libpulse from arm crosscompile --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 854b487a..f7d31226 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,5 +5,4 @@ ENV PKG_CONFIG_PATH /usr/lib/arm-linux-gnueabihf/pkgconfig/ RUN dpkg --add-architecture armhf && \ apt-get update && \ - apt-get install libasound2-dev:armhf -y && \ - apt-get install libpulse0 libpulse-dev:armhf -y \ \ No newline at end of file + apt-get install libasound2-dev:armhf -y \ \ No newline at end of file