diff --git a/Cargo.lock b/Cargo.lock index c93b887..3cd8eff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,9 +35,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bindgen" @@ -77,15 +77,15 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytes" -version = "1.7.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.1.21" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" dependencies = [ "jobserver", "libc", @@ -189,12 +189,125 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "dasp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7381b67da416b639690ac77c73b86a7b5e64a29e31d1f75fb3b1102301ef355a" +dependencies = [ + "dasp_envelope", + "dasp_frame", + "dasp_interpolate", + "dasp_peak", + "dasp_ring_buffer", + "dasp_rms", + "dasp_sample", + "dasp_signal", + "dasp_slice", + "dasp_window", +] + +[[package]] +name = "dasp_envelope" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec617ce7016f101a87fe85ed44180839744265fae73bb4aa43e7ece1b7668b6" +dependencies = [ + "dasp_frame", + "dasp_peak", + "dasp_ring_buffer", + "dasp_rms", + "dasp_sample", +] + +[[package]] +name = "dasp_frame" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a3937f5fe2135702897535c8d4a5553f8b116f76c1529088797f2eee7c5cd6" +dependencies = [ + "dasp_sample", +] + +[[package]] +name = "dasp_interpolate" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc975a6563bb7ca7ec0a6c784ead49983a21c24835b0bc96eea11ee407c7486" +dependencies = [ + "dasp_frame", + "dasp_ring_buffer", + "dasp_sample", +] + +[[package]] +name = "dasp_peak" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf88559d79c21f3d8523d91250c397f9a15b5fc72fbb3f87fdb0a37b79915bf" +dependencies = [ + "dasp_frame", + "dasp_sample", +] + +[[package]] +name = "dasp_ring_buffer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07d79e19b89618a543c4adec9c5a347fe378a19041699b3278e616e387511ea1" + +[[package]] +name = "dasp_rms" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6c5dcb30b7e5014486e2822537ea2beae50b19722ffe2ed7549ab03774575aa" +dependencies = [ + "dasp_frame", + "dasp_ring_buffer", + "dasp_sample", +] + [[package]] name = "dasp_sample" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" +[[package]] +name = "dasp_signal" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa1ab7d01689c6ed4eae3d38fe1cea08cba761573fbd2d592528d55b421077e7" +dependencies = [ + "dasp_envelope", + "dasp_frame", + "dasp_interpolate", + "dasp_peak", + "dasp_ring_buffer", + "dasp_rms", + "dasp_sample", + "dasp_window", +] + +[[package]] +name = "dasp_slice" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e1c7335d58e7baedafa516cb361360ff38d6f4d3f9d9d5ee2a2fc8e27178fa1" +dependencies = [ + "dasp_frame", + "dasp_sample", +] + +[[package]] +name = "dasp_window" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ded7b88821d2ce4e8b842c9f1c86ac911891ab89443cc1de750cae764c5076" +dependencies = [ + "dasp_sample", +] + [[package]] name = "either" version = "1.13.0" @@ -215,9 +328,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hound" @@ -230,6 +343,7 @@ name = "impulse-engine" version = "0.1.0" dependencies = [ "cpal", + "dasp", "hound", "rtrb", "simple-left-right", @@ -238,9 +352,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -288,24 +402,25 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.159" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -443,9 +558,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "pkg-config" @@ -464,9 +579,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -482,9 +597,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -494,9 +609,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -505,9 +620,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rtrb" @@ -542,9 +657,9 @@ version = "0.2.0" [[package]] name = "syn" -version = "2.0.77" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -553,18 +668,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -579,9 +694,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.22.21" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "toml_datetime", @@ -599,9 +714,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "walkdir" @@ -615,9 +730,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -626,13 +741,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -641,21 +755,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -663,9 +778,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -676,15 +791,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -869,9 +984,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] diff --git a/tracker-engine/Cargo.toml b/tracker-engine/Cargo.toml index fcbd9c3..9367f24 100644 --- a/tracker-engine/Cargo.toml +++ b/tracker-engine/Cargo.toml @@ -11,11 +11,16 @@ readme = "README.md" keywords = ["audio"] categories = ["audio"] +[features] +default = ["cpal"] +cpal = ["dep:cpal"] + [dependencies] # added a couple features to it. pull requests don't really progress # basedrop = "0.1.2" # miri reports Race Condition. Wait for fix or pull inside # basedrop = { git = "https://github.com/luca3s/basedrop/", branch = "all-patches"} -cpal = "0.15.3" +cpal = { version = "0.15.3", optional = true } +dasp = { version = "0.11.0", default-features = false, features = ["std"] } rtrb = "0.3.1" simple-left-right = { path = "../simple-left-right" } triple_buffer = "8.0.0" diff --git a/tracker-engine/examples/live_note.rs b/tracker-engine/examples/live_note.rs index b7944e4..ad92e77 100644 --- a/tracker-engine/examples/live_note.rs +++ b/tracker-engine/examples/live_note.rs @@ -29,7 +29,8 @@ fn main() { }; manager - .try_edit_song().unwrap() + .try_edit_song() + .unwrap() .apply_operation(SongOperation::SetSample(1, meta, sample)) .unwrap(); @@ -44,7 +45,7 @@ fn main() { sample_rate: default_config.sample_rate().0, }; - manager.init_audio(default_device, config).unwrap(); + manager.init_audio(&default_device, config).unwrap(); let note_event = NoteEvent { note: Note::new(90).unwrap(), @@ -52,9 +53,13 @@ fn main() { vol: VolumeEffect::None, command: NoteCommand::None, }; - manager.try_msg_worker(ToWorkerMsg::PlayEvent(note_event)).unwrap(); + manager + .try_msg_worker(ToWorkerMsg::PlayEvent(note_event)) + .unwrap(); std::thread::sleep(Duration::from_secs(1)); - manager.try_msg_worker(ToWorkerMsg::PlayEvent(note_event)).unwrap(); + manager + .try_msg_worker(ToWorkerMsg::PlayEvent(note_event)) + .unwrap(); std::thread::sleep(Duration::from_secs(1)); println!("{:?}", manager.playback_status()); } diff --git a/tracker-engine/examples/pattern_playback.rs b/tracker-engine/examples/pattern_playback.rs index e0de07b..61c26b7 100644 --- a/tracker-engine/examples/pattern_playback.rs +++ b/tracker-engine/examples/pattern_playback.rs @@ -69,9 +69,11 @@ fn main() { sample_rate: default_config.sample_rate().0, }; - manager.init_audio(default_device, config).unwrap(); + manager.init_audio(&default_device, config).unwrap(); - manager.try_msg_worker(ToWorkerMsg::Playback(PlaybackSettings::default())).unwrap(); + manager + .try_msg_worker(ToWorkerMsg::Playback(PlaybackSettings::default())) + .unwrap(); std::thread::sleep(Duration::from_secs(5)); manager.deinit_audio(); diff --git a/tracker-engine/src/audio_processing/mod.rs b/tracker-engine/src/audio_processing/mod.rs index b285a79..c574222 100644 --- a/tracker-engine/src/audio_processing/mod.rs +++ b/tracker-engine/src/audio_processing/mod.rs @@ -1,11 +1,10 @@ use std::{array, ops::IndexMut}; -use cpal::Sample; +use dasp::sample::{FromSample, ToSample}; pub(crate) mod instrument; pub mod playback; pub(crate) mod sample; -// pub mod resample; #[repr(transparent)] #[derive(Clone, Copy, Default, Debug, PartialEq)] @@ -75,34 +74,37 @@ impl From for Frame { impl From for Frame { fn from(value: i16) -> Self { - let value = f32::from_sample(value); + let value = f32::from_sample_(value); Self([value, value]) } } impl From<[i16; 2]> for Frame { fn from(value: [i16; 2]) -> Self { - Self([f32::from_sample(value[0]), f32::from_sample(value[1])]) + Self([f32::from_sample_(value[0]), f32::from_sample_(value[1])]) } } impl From for Frame { fn from(value: i8) -> Self { - let value = f32::from_sample(value); + let value = f32::from_sample_(value); Self([value, value]) } } impl From<[i8; 2]> for Frame { fn from(value: [i8; 2]) -> Self { - Self([f32::from_sample(value[0]), f32::from_sample(value[1])]) + Self([f32::from_sample_(value[0]), f32::from_sample_(value[1])]) } } impl Frame { // split into left and right. pub fn split_array(value: [Frame; N]) -> ([f32; N], [f32; N]) { - (array::from_fn(|i| value[i].0[0]), array::from_fn(|i| value[i].0[1])) + ( + array::from_fn(|i| value[i].0[0]), + array::from_fn(|i| value[i].0[1]), + ) } pub fn sum_to_mono(self) -> f32 { @@ -125,8 +127,8 @@ impl Frame { unsafe { std::mem::transmute::<&'a [f32; 2], &'a Self>(value) } } - pub fn to_sample>(self) -> [S; 2] { - [self.0[0].to_sample(), self.0[1].to_sample()] + pub fn to_sample>(self) -> [S; 2] { + [self.0[0].to_sample_(), self.0[1].to_sample_()] } pub fn to_raw<'a>(into: &mut [Self]) -> &'a mut [[f32; 2]] { diff --git a/tracker-engine/src/audio_processing/playback.rs b/tracker-engine/src/audio_processing/playback.rs index ef8e80f..e0f3788 100644 --- a/tracker-engine/src/audio_processing/playback.rs +++ b/tracker-engine/src/audio_processing/playback.rs @@ -248,8 +248,11 @@ macro_rules! create_sample_players { &$sel.song.patterns[$sel.state.position.pattern][$sel.state.position.row] { if let Some((meta, sample)) = &$sel.song.samples[usize::from(event.sample_instr)] { - let player = - SamplePlayer::new((*meta, sample.get_handle()), $sel.state.samplerate, event.note); + let player = SamplePlayer::new( + (*meta, sample.get_handle()), + $sel.state.samplerate, + event.note, + ); $sel.state.voices[usize::from(positions.channel)] = Some(player); } } diff --git a/tracker-engine/src/audio_processing/sample.rs b/tracker-engine/src/audio_processing/sample.rs index d873a89..a657ea0 100644 --- a/tracker-engine/src/audio_processing/sample.rs +++ b/tracker-engine/src/audio_processing/sample.rs @@ -51,7 +51,6 @@ pub struct SamplePlayer<'sample, const GC: bool> { // f32 ranges 0..1 position: (usize, f32), // is_done: bool, - out_rate: u32, // how much the position is advanced for each output sample. // computed from in and out rate @@ -155,7 +154,7 @@ impl<'sample, const GC: bool> SamplePlayer<'sample, GC> { let diff = data[1] - data[0]; (diff * self.position.1) + data[0] }, - self.position.0 + self.position.0, ) } diff --git a/tracker-engine/src/file/impulse_format/instrument.rs b/tracker-engine/src/file/impulse_format/instrument.rs index 6562047..10c4e8e 100644 --- a/tracker-engine/src/file/impulse_format/instrument.rs +++ b/tracker-engine/src/file/impulse_format/instrument.rs @@ -1,5 +1,4 @@ use std::array; -use std::io::Read; use crate::file::err; use crate::file::err::LoadDefect; diff --git a/tracker-engine/src/live_audio.rs b/tracker-engine/src/live_audio.rs index 21d0988..1833e7a 100644 --- a/tracker-engine/src/live_audio.rs +++ b/tracker-engine/src/live_audio.rs @@ -6,31 +6,31 @@ use crate::audio_processing::sample::SamplePlayer; use crate::audio_processing::Frame; use crate::manager::{OutputConfig, ToWorkerMsg}; use crate::project::song::Song; -use cpal::{Sample, SampleFormat}; +use dasp::sample::ToSample; use simple_left_right::Reader; -pub type LiveAudioStatus = (Option, Option); +// pub type LiveAudioStatus = (Option, Option); -pub(crate) struct LiveAudio { +pub(crate) struct LiveAudio { song: Reader>, playback_state: Option>, live_note: Option>, manager: rtrb::Consumer, // gets created in the first callback. could maybe do with an MaybeUninit - state_sender: triple_buffer::Input, + state_sender: triple_buffer::Input<(Option, Option)>, config: OutputConfig, buffer: Box<[Frame]>, } -impl LiveAudio { - const INTERPOLATION: u8 = Interpolation::Linear as u8; +const INTERPOLATION: u8 = Interpolation::Linear as u8; +impl LiveAudio { /// Not realtime safe. pub fn new( song: Reader>, manager: rtrb::Consumer, - state_sender: triple_buffer::Input, + state_sender: triple_buffer::Input<(Option, Option)>, config: OutputConfig, ) -> Self { Self { @@ -44,10 +44,10 @@ impl LiveAudio { } } - fn send_state(&mut self, info: Option<&cpal::OutputCallbackInfo>) { + fn send_state(&mut self, info: Option) { self.state_sender.write(( self.playback_state.as_ref().map(|s| s.get_status()), - info.map(|info| info.timestamp()), + info, )); } @@ -89,7 +89,7 @@ impl LiveAudio { // process live_note if let Some(live_note) = &mut self.live_note { - let note_iter = live_note.iter::<{ Self::INTERPOLATION }>(); + let note_iter = live_note.iter::<{ INTERPOLATION }>(); self.buffer .iter_mut() .zip(note_iter) @@ -102,7 +102,7 @@ impl LiveAudio { // process song playback if let Some(playback) = &mut self.playback_state { - let playback_iter = playback.iter::<{ Self::INTERPOLATION }>(&song); + let playback_iter = playback.iter::<{ INTERPOLATION }>(&song); self.buffer .iter_mut() .zip(playback_iter) @@ -119,15 +119,15 @@ impl LiveAudio { /// converts the internal buffer to any possible output format and channel count /// sums stereo to mono and fills channels 3 and up with silence #[inline] - fn fill_from_internal>( + fn fill_from_internal>( &mut self, - data: &mut [S], + data: &mut [Sample], ) { // convert the internal buffer and move it to the out_buffer if self.config.channel_count.get() == 1 { data.iter_mut() .zip(self.buffer.iter()) - .for_each(|(out, buf)| *out = buf.sum_to_mono().to_sample()); + .for_each(|(out, buf)| *out = buf.sum_to_mono().to_sample_()); } else { data.chunks_exact_mut(usize::from(self.config.channel_count.get())) .map(|frame| frame.split_first_chunk_mut::<2>().unwrap().0) @@ -136,49 +136,49 @@ impl LiveAudio { } } - pub fn get_generic_callback( + // pub fn get_generic_callback( + // mut self, + // ) -> impl FnMut(&mut cpal::Data, &cpal::OutputCallbackInfo) { + // move |data, info| { + // assert_eq!( + // data.len(), + // usize::try_from(self.config.buffer_size).unwrap() + // * usize::from(self.config.channel_count.get()) + // ); + + // // actual audio work, if false no work was done + // if !self.fill_internal_buffer() { + // return; + // } + + // // convert to the right output format + // match data.sample_format() { + // SampleFormat::I8 => self.fill_from_internal::(data.as_slice_mut().unwrap()), + // SampleFormat::I16 => self.fill_from_internal::(data.as_slice_mut().unwrap()), + // SampleFormat::I32 => self.fill_from_internal::(data.as_slice_mut().unwrap()), + // SampleFormat::I64 => self.fill_from_internal::(data.as_slice_mut().unwrap()), + // SampleFormat::U8 => self.fill_from_internal::(data.as_slice_mut().unwrap()), + // SampleFormat::U16 => self.fill_from_internal::(data.as_slice_mut().unwrap()), + // SampleFormat::U32 => self.fill_from_internal::(data.as_slice_mut().unwrap()), + // SampleFormat::U64 => self.fill_from_internal::(data.as_slice_mut().unwrap()), + // SampleFormat::F32 => self.fill_from_internal::(data.as_slice_mut().unwrap()), + // SampleFormat::F64 => self.fill_from_internal::(data.as_slice_mut().unwrap()), + // /* + // I want to support all formats. This panic being triggered means that there is a version + // mismatch between cpal and this library. + // */ + // _ => panic!("Sample Format not supported."), + // } + + // self.send_state(Some(info.timestamp())); + // } + // } + + // // unsure wether i want to use this or untyped_callback + // // also relevant when cpal gets made into a generic that maybe this gets useful + pub fn get_typed_callback>( mut self, - ) -> impl FnMut(&mut cpal::Data, &cpal::OutputCallbackInfo) { - move |data, info| { - assert_eq!( - data.len(), - usize::try_from(self.config.buffer_size).unwrap() - * usize::from(self.config.channel_count.get()) - ); - - // actual audio work, if false no work was done - if !self.fill_internal_buffer() { - return; - } - - // convert to the right output format - match data.sample_format() { - SampleFormat::I8 => self.fill_from_internal::(data.as_slice_mut().unwrap()), - SampleFormat::I16 => self.fill_from_internal::(data.as_slice_mut().unwrap()), - SampleFormat::I32 => self.fill_from_internal::(data.as_slice_mut().unwrap()), - SampleFormat::I64 => self.fill_from_internal::(data.as_slice_mut().unwrap()), - SampleFormat::U8 => self.fill_from_internal::(data.as_slice_mut().unwrap()), - SampleFormat::U16 => self.fill_from_internal::(data.as_slice_mut().unwrap()), - SampleFormat::U32 => self.fill_from_internal::(data.as_slice_mut().unwrap()), - SampleFormat::U64 => self.fill_from_internal::(data.as_slice_mut().unwrap()), - SampleFormat::F32 => self.fill_from_internal::(data.as_slice_mut().unwrap()), - SampleFormat::F64 => self.fill_from_internal::(data.as_slice_mut().unwrap()), - /* - I want to support all formats. This panic being triggered means that there is a version - mismatch between cpal and this library. - */ - _ => panic!("Sample Format not supported."), - } - - self.send_state(Some(info)); - } - } - - // unsure wether i want to use this or untyped_callback - // also relevant when cpal gets made into a generic that maybe this gets useful - pub fn get_typed_callback>( - mut self, - ) -> impl FnMut(&mut [S], &cpal::OutputCallbackInfo) { + ) -> impl FnMut(&mut [Sample], Info) { move |data, info| { assert_eq!( data.len(), @@ -192,6 +192,16 @@ impl LiveAudio { self.send_state(Some(info)); } } + + // pub fn get_callback(mut self) -> impl FnMut(&mut [Frame], S::BufferInformation) { + // move |data, info| { + // assert_eq!(data.len(), self.config.buffer_size as usize * self.config.channel_count.get() as usize) + + // if self.fill_internal_buffer() { + // self.fill_from_internal(data); + // } + // } + // } } // only used for testing diff --git a/tracker-engine/src/manager.rs b/tracker-engine/src/manager.rs index 0510591..cf48a25 100644 --- a/tracker-engine/src/manager.rs +++ b/tracker-engine/src/manager.rs @@ -1,15 +1,15 @@ use std::{fmt::Debug, num::NonZeroU16, sync::Arc, time::Duration}; -use cpal::traits::{DeviceTrait, StreamTrait}; use simple_left_right::{WriteGuard, Writer}; use crate::{ - audio_processing::playback::PlaybackPosition, - live_audio::{LiveAudio, LiveAudioStatus}, + audio_processing::playback::PlaybackStatus, + live_audio::LiveAudio, project::{ note_event::NoteEvent, song::{Song, SongOperation, ValidOperation}, - }, sample::{OwnedSample, SharedSample}, + }, + sample::{OwnedSample, SharedSample}, }; #[derive(Debug, Clone, Copy)] @@ -25,7 +25,7 @@ pub enum ToWorkerMsg { pub enum SendResult { Success, BufferFull, - AudioInactive + AudioInactive, } impl SendResult { @@ -43,16 +43,129 @@ impl SendResult { } } -struct ActiveStream { - stream: cpal::Stream, +pub trait OutputStream { + /// Information that is passed to the callback each time it is called + type BufferInformation: Send + Clone + 'static; + type PauseErr; + type PlayErr; + fn pause(&mut self) -> Result<(), Self::PauseErr> { + // what i want + // Result::<(), !>::Ok(()) + Ok(()) + } + fn play(&mut self) -> Result<(), Self::PlayErr> { + Ok(()) + } +} + +pub trait StreamBuilder { + /// Error when creating the stream + type CreateErr; + /// Error while the stream is running + type StreamErr: Debug; + type Stream: OutputStream; + fn create( + self, + data_callback: impl FnMut(&mut [f32], ::BufferInformation) + + Send + + 'static, + err_callback: impl FnMut(Self::StreamErr) + Send + 'static, + config: OutputConfig, + ) -> Result; +} + +#[cfg(feature = "cpal")] +mod cpal { + use std::num::NonZeroU16; + + use super::{OutputConfig, OutputStream, StreamBuilder}; + + + impl OutputStream for Stream { + type BufferInformation = cpal::OutputStreamTimestamp; + + type PauseErr = cpal::PauseStreamError; + + type PlayErr = cpal::PlayStreamError; + + fn pause(&mut self) -> Result<(), Self::PauseErr> { + ::pause(self) + } + + fn play(&mut self) -> Result<(), Self::PlayErr> { + ::play(self) + } + } + + impl StreamBuilder for &Device { + type CreateErr = cpal::BuildStreamError; + type StreamErr = cpal::StreamError; + type Stream = Device::Stream; + fn create( + self, + mut data_callback: impl FnMut(&mut [f32], <::Stream as OutputStream>::BufferInformation) + + Send + + 'static, + err_callback: impl FnMut(Self::StreamErr) + Send + 'static, + config: OutputConfig, + ) -> Result { + self.build_output_stream_raw( + &config.into(), + cpal::SampleFormat::F32, + move |d, i| { + let d = d.as_slice_mut().unwrap(); + data_callback(d, i.timestamp()) + }, + err_callback, + None, + ) + } + } + + impl From for cpal::StreamConfig { + fn from(value: OutputConfig) -> Self { + cpal::StreamConfig { + channels: value.channel_count.into(), + sample_rate: cpal::SampleRate(value.sample_rate), + buffer_size: cpal::BufferSize::Fixed(value.buffer_size), + } + } + } + + impl TryFrom for OutputConfig { + type Error = (); + + /// fails if BufferSize isn't explicit or zero output channels are specified. + fn try_from(value: cpal::StreamConfig) -> Result { + match value.buffer_size { + cpal::BufferSize::Default => Err(()), + cpal::BufferSize::Fixed(size) => Ok(OutputConfig { + buffer_size: size, + channel_count: NonZeroU16::try_from(value.channels).map_err(|_| ())?, + sample_rate: value.sample_rate.0, + }), + } + } + } +} + +struct ActiveStream { + stream: S, buffer_time: Duration, send: rtrb::Producer, - status: triple_buffer::Output, + status: triple_buffer::Output<(Option, Option)>, } -impl Debug for ActiveStream { +impl Debug for ActiveStream +where + S::BufferInformation: Debug +{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ActiveStream").field("buffer_time", &self.buffer_time).field("send", &self.send).field("status", &self.status).finish() + f.debug_struct("ActiveStream") + .field("buffer_time", &self.buffer_time) + .field("send", &self.send) + .field("status", &self.status) + .finish() } } @@ -98,13 +211,13 @@ impl Collector { /// You will need to write your own spin loops. /// For that you can and maybe should use AudioManager::buffer_time. -pub struct AudioManager { +pub struct AudioManager { song: Writer, ValidOperation>, gc: Collector, - stream: Option, + stream: Option>, } -impl AudioManager { +impl AudioManager { pub fn new(song: Song) -> Self { let mut gc = Collector::default(); let left_right = simple_left_right::Writer::new(song.to_gc(&mut gc)); @@ -117,7 +230,10 @@ impl AudioManager { } pub fn try_edit_song(&mut self) -> Option> { - self.song.try_lock().map(|song| SongEdit { song, gc: &mut self.gc }) + self.song.try_lock().map(|song| SongEdit { + song, + gc: &mut self.gc, + }) } pub fn get_song(&self) -> &Song { @@ -128,52 +244,6 @@ impl AudioManager { self.gc.collect(); } - /// If the config specifies more than two channels only the first two will be filled with audio. - /// The rest gets silence. - pub fn init_audio( - &mut self, - device: cpal::Device, - config: OutputConfig, - ) -> Result<(), cpal::BuildStreamError> { - const TO_WORKER_CAPACITY: usize = 5; - - let from_worker = triple_buffer::triple_buffer(&(None, None)); - let to_worker = rtrb::RingBuffer::new(TO_WORKER_CAPACITY); - let reader = self.song.build_reader().unwrap(); - - let audio_worker = LiveAudio::new(reader, to_worker.1, from_worker.0, config); - - let stream = device.build_output_stream_raw( - &config.into(), - cpal::SampleFormat::F32, - audio_worker.get_generic_callback(), - |err| println!("{err}"), - None, - )?; - let buffer_time = - Duration::from_millis((config.buffer_size * 1000 / config.buffer_size).into()); - - stream.play().unwrap(); - - self.stream = Some(ActiveStream { stream, buffer_time, send: to_worker.0, status: from_worker.1 }); - - Ok(()) - } - - /// pauses the audio. only works on some platforms (look at cpal docs) - pub fn pause_audio(&mut self) { - if let Some(stream) = &mut self.stream { - stream.stream.pause().unwrap(); - } - } - - /// resume the audio. playback is in the same state as before the pause. (only available on some platforms, see cpal docs for stream.pause()) - pub fn resume_audio(&self) { - if let Some(stream) = &self.stream { - stream.stream.play().unwrap(); - } - } - /// None if there is no active stream. /// Some(Err) if the buffer is full. pub fn try_msg_worker(&mut self, msg: ToWorkerMsg) -> SendResult { @@ -194,21 +264,75 @@ impl AudioManager { } /// last playback status sent by the audio worker - pub fn playback_status(&mut self) -> Option<&LiveAudioStatus> { + pub fn playback_status(&mut self) -> Option<&(Option, Option)> { self.stream.as_mut().map(|s| s.status.read()) } /// Some if a stream is active. /// Returns the approximate time it takes to process an audio buffer based on the used settings. - /// + /// /// Useful for implementing spin_loops on collect_garbage or for locking a SongEdit as every time a buffer is finished /// garbage could be releases and a lock could be made available pub fn buffer_time(&self) -> Option { self.stream.as_ref().map(|s| s.buffer_time) } + + /// pauses the audio. only works on some platforms (look at cpal docs) + pub fn pause_audio(&mut self) -> Option> { + self.stream.as_mut().map(|s| s.stream.pause()) + // if let Some(stream) = &mut self.stream { + // stream.stream.pause(); + // } + } + + /// resume the audio. playback is in the same state as before the pause. (only available on some platforms, see cpal docs for stream.pause()) + pub fn resume_audio(&mut self) -> Option::PlayErr>> { + self.stream.as_mut().map(|s| s.stream.play()) + } + + /// If the config specifies more than two channels only the first two will be filled with audio. + /// The rest gets silence. + pub fn init_audio( + &mut self, + create_stream: Builder, + config: OutputConfig, + ) -> Result<(), Builder::CreateErr> + where + Builder: StreamBuilder + { + const TO_WORKER_CAPACITY: usize = 5; + + let from_worker = triple_buffer::triple_buffer(&(None, None)); + let to_worker = rtrb::RingBuffer::new(TO_WORKER_CAPACITY); + let reader = self.song.build_reader().unwrap(); + + let audio_worker = LiveAudio::::new(reader, to_worker.1, from_worker.0, config); + + // let stream = device.build_output_stream_raw( + // &config.into(), + // cpal::SampleFormat::F32, + // audio_worker.get_generic_callback(), + // |err| println!("{err}"), + // None, + // )?; + let stream = create_stream.create(audio_worker.get_typed_callback(), |err| eprintln!("{err:?}"), config)?; + let buffer_time = + Duration::from_millis((config.buffer_size * 1000 / config.buffer_size).into()); + + // stream.play(); + + self.stream = Some(ActiveStream { + stream, + buffer_time, + send: to_worker.0, + status: from_worker.1, + }); + + Ok(()) + } } -impl Drop for AudioManager { +impl Drop for AudioManager { /// if this panics the drop implementation isn't right and the Audio Callback isn't cleaned up properly fn drop(&mut self) { self.deinit_audio(); @@ -263,32 +387,6 @@ pub struct OutputConfig { pub sample_rate: u32, } -impl From for cpal::StreamConfig { - fn from(value: OutputConfig) -> Self { - cpal::StreamConfig { - channels: value.channel_count.into(), - sample_rate: cpal::SampleRate(value.sample_rate), - buffer_size: cpal::BufferSize::Fixed(value.buffer_size), - } - } -} - -impl TryFrom for OutputConfig { - type Error = (); - - /// fails if BufferSize isn't explicit or zero output channels are specified. - fn try_from(value: cpal::StreamConfig) -> Result { - match value.buffer_size { - cpal::BufferSize::Default => Err(()), - cpal::BufferSize::Fixed(size) => Ok(OutputConfig { - buffer_size: size, - channel_count: NonZeroU16::try_from(value.channels).map_err(|_| ())?, - sample_rate: value.sample_rate.0, - }), - } - } -} - #[derive(Debug, Clone, Copy)] pub enum PlaybackSettings { Pattern { idx: usize, should_loop: bool }, @@ -303,10 +401,3 @@ impl Default for PlaybackSettings { } } } - -#[derive(Debug, Clone, Copy)] -pub enum FromWorkerMsg { - BufferFinished(cpal::OutputStreamTimestamp), - CurrentPlaybackPosition(PlaybackPosition), - PlaybackStopped, -} diff --git a/tracker-engine/src/project/song.rs b/tracker-engine/src/project/song.rs index 05228ac..0e1c752 100644 --- a/tracker-engine/src/project/song.rs +++ b/tracker-engine/src/project/song.rs @@ -1,12 +1,12 @@ use std::array; use std::fmt::{Debug, Formatter}; +use super::pattern::{Pattern, PatternOperation}; use crate::channel::Pan; use crate::file::impulse_format; use crate::file::impulse_format::header::PatternOrder; use crate::manager::Collector; use crate::sample::{OwnedSample, Sample, SampleMetaData, SharedSample}; -use super::pattern::{Pattern, PatternOperation}; /// Playback Speed in Schism is determined by two values: Tempo and Speed. /// Speed specifies how many ticks are in one row. This reduces tempo, but increases resolution of some effects. diff --git a/tracker-engine/src/sample.rs b/tracker-engine/src/sample.rs index 0ac96b7..cd3ae69 100644 --- a/tracker-engine/src/sample.rs +++ b/tracker-engine/src/sample.rs @@ -1,19 +1,28 @@ use std::{array, fmt::Debug, mem::ManuallyDrop, ops::Deref, sync::Arc}; -use cpal::FromSample; +use dasp::sample::FromSample; -use crate::{audio_processing::Frame, file::impulse_format::sample::VibratoWave, manager::Collector, project::note_event::Note}; +use crate::{ + audio_processing::Frame, file::impulse_format::sample::VibratoWave, manager::Collector, + project::note_event::Note, +}; pub(crate) union SampleHandle<'a, const GC: bool> { gc: ManuallyDrop, - reference: ManuallyDrop> + reference: ManuallyDrop>, } impl Debug for SampleHandle<'_, GC> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match GC { - true => f.debug_struct("GC Sample Handle").field("data", unsafe { &self.gc }).finish(), - false => f.debug_struct("Ref Sample").field("data", unsafe { &self.reference }).finish(), + true => f + .debug_struct("GC Sample Handle") + .field("data", unsafe { &self.gc }) + .finish(), + false => f + .debug_struct("Ref Sample") + .field("data", unsafe { &self.reference }) + .finish(), } } } @@ -21,8 +30,12 @@ impl Debug for SampleHandle<'_, GC> { impl Clone for SampleHandle<'_, GC> { fn clone(&self) -> Self { match GC { - true => Self { gc: unsafe { self.gc.clone() } }, - false => Self { reference: unsafe { self.reference } }, + true => Self { + gc: unsafe { self.gc.clone() }, + }, + false => Self { + reference: unsafe { self.reference }, + }, } } } @@ -52,7 +65,9 @@ pub union Sample { impl Sample { pub(crate) fn new(value: SharedSample) -> Self { - Self { gc: ManuallyDrop::new(value) } + Self { + gc: ManuallyDrop::new(value), + } } pub(crate) fn get_handle(&self) -> SampleHandle<'static, true> { @@ -69,18 +84,24 @@ impl Sample { pub(crate) fn from_owned(mut owned: Sample, handle: &mut Collector) -> Self { let data = unsafe { ManuallyDrop::take(&mut owned.owned) }; std::mem::forget(owned); - Sample { gc: ManuallyDrop::new(handle.add_sample(data)) } + Sample { + gc: ManuallyDrop::new(handle.add_sample(data)), + } } } impl Sample { pub fn new(value: OwnedSample) -> Self { - Self { owned: ManuallyDrop::new(value) } + Self { + owned: ManuallyDrop::new(value), + } } pub(crate) fn get_handle(&self) -> SampleHandle<'_, false> { let data = unsafe { self.owned.deref().borrow() }; - SampleHandle { reference: ManuallyDrop::new(data) } + SampleHandle { + reference: ManuallyDrop::new(data), + } } pub fn take(mut self) -> OwnedSample { @@ -102,8 +123,12 @@ impl Sample { impl Clone for Sample { fn clone(&self) -> Self { match GC { - true => Self { gc: unsafe { self.gc.clone() } }, - false => Self { owned: unsafe { self.owned.clone() } }, + true => Self { + gc: unsafe { self.gc.clone() }, + }, + false => Self { + owned: unsafe { self.owned.clone() }, + }, } } } @@ -111,8 +136,14 @@ impl Clone for Sample { impl Debug for Sample { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match GC { - true => f.debug_struct("Shared Sample").field("data", unsafe { &self.gc }).finish(), - false => f.debug_struct("Boxed Sample").field("data", unsafe { &self.owned }).finish(), + true => f + .debug_struct("Shared Sample") + .field("data", unsafe { &self.gc }) + .finish(), + false => f + .debug_struct("Boxed Sample") + .field("data", unsafe { &self.owned }) + .finish(), } } } @@ -153,17 +184,17 @@ impl SampleRef<'_> { SampleRef::StereoF32(d) => d[index].into(), SampleRef::StereoI16(d) => d[index].into(), SampleRef::StereoI8(d) => d[index].into(), - _ => unreachable!() + _ => unreachable!(), } } /// index..index + N pub fn index_stereo_array(&self, index: usize) -> [Frame; N] { - match self { + match self { SampleRef::StereoF32(d) => array::from_fn(|i| d[index + i].into()), SampleRef::StereoI16(d) => array::from_fn(|i| d[index + i].into()), SampleRef::StereoI8(d) => array::from_fn(|i| d[index + i].into()), - _ => unreachable!() + _ => unreachable!(), } } @@ -172,13 +203,17 @@ impl SampleRef<'_> { SampleRef::MonoF32(d) => array::from_fn(|i| d[index + i]), SampleRef::MonoI16(d) => array::from_fn(|i| f32::from_sample_(d[index + i])), SampleRef::MonoI8(d) => array::from_fn(|i| f32::from_sample_(d[index + i])), - _ => unreachable!() + _ => unreachable!(), } } // index..index+N /// calls process once or twice depending on stereo or mono - pub fn compute f32>(&self, process: F, index: usize) -> Frame { + pub fn compute f32>( + &self, + process: F, + index: usize, + ) -> Frame { if self.is_mono() { let arr = self.index_mono_array(index); Frame::from(process(arr)) @@ -194,7 +229,7 @@ impl SampleRef<'_> { SampleRef::MonoF32(d) => d[index], SampleRef::MonoI16(d) => d[index].into(), SampleRef::MonoI8(d) => d[index].into(), - _ => unreachable!() + _ => unreachable!(), } }