Added audio_chunk

This commit is contained in:
Geens 2025-06-03 19:51:39 +02:00
parent 8bb24b097a
commit 880046c1ce
5 changed files with 390 additions and 621 deletions

628
Cargo.lock generated
View File

@ -2,442 +2,13 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "ab_glyph"
version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser",
]
[[package]]
name = "ab_glyph_rasterizer"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crossbeam"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[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 = "ecolor"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1"
dependencies = [
"emath",
]
[[package]]
name = "egui"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3"
dependencies = [
"ahash",
"bitflags 2.9.1",
"emath",
"epaint",
"nohash-hasher",
"profiling",
]
[[package]]
name = "emath"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b"
[[package]]
name = "epaint"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562"
dependencies = [
"ab_glyph",
"ahash",
"ecolor",
"emath",
"epaint_default_fonts",
"nohash-hasher",
"parking_lot",
"profiling",
]
[[package]]
name = "epaint_default_fonts"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f"
[[package]]
name = "hound"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jack"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f70ca699f44c04a32d419fc9ed699aaea89657fc09014bf3fa238e91d13041b9"
dependencies = [
"bitflags 2.9.1",
"jack-sys",
"lazy_static",
"libc",
"log",
]
[[package]]
name = "jack-sys"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6013b7619b95a22b576dfb43296faa4ecbe40abbdb97dfd22ead520775fc86ab"
dependencies = [
"bitflags 1.3.2",
"lazy_static",
"libc",
"libloading",
"log",
"pkg-config",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "looper"
version = "0.1.0"
dependencies = [
"bincode",
"crossbeam",
"dasp",
"egui",
"hound",
"jack",
"log",
"parking_lot",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "nohash-hasher"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "owned_ttf_parser"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4"
dependencies = [
"ttf-parser",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "proc-macro2"
version = "1.0.95"
@ -447,12 +18,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "profiling"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
[[package]]
name = "quote"
version = "1.0.40"
@ -462,65 +27,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
dependencies = [
"bitflags 2.9.1",
]
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "smallvec"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
[[package]]
name = "syn"
version = "2.0.101"
@ -533,125 +39,27 @@ dependencies = [
]
[[package]]
name = "ttf-parser"
version = "0.25.1"
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zerocopy"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@ -4,13 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
bincode = "1"
crossbeam = "0.8"
dasp = "0.11"
egui = "0.31"
hound = "3"
jack = "0.13"
log = "0.4.27"
parking_lot = "0.12"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "1.0"

344
src/audio_chunk.rs Normal file
View File

@ -0,0 +1,344 @@
use std::sync::Arc;
use crate::*;
/// A chunk of audio data that can be linked to form longer recordings.
/// Designed for lock-free sharing between real-time and I/O threads.
#[derive(Debug)]
pub struct AudioChunk {
/// Fixed-size audio buffer
pub samples: Box<[f32]>,
/// Number of valid samples (≤ samples.len())
pub sample_count: usize,
/// Next chunk in linked list
pub next: Option<Arc<AudioChunk>>,
}
impl AudioChunk {
/// Create a new empty chunk with specified capacity (for buffer pool)
pub fn allocate(size: usize) -> Arc<Self> {
Arc::new(Self {
samples: vec![0.0; size].into_boxed_slice(),
sample_count: 0,
next: None,
})
}
/// Consolidate a linked list of chunks into a single optimized chunk
pub fn consolidate(this: &Arc<AudioChunk>) -> Arc<Self> {
// Calculate total sample count
let mut total_samples = 0;
let mut current = Some(this.clone());
while let Some(chunk) = current {
total_samples += chunk.sample_count;
current = chunk.next.clone();
}
// Create consolidated buffer
let mut consolidated_samples = Vec::with_capacity(total_samples);
let mut current = Some(this.clone());
while let Some(chunk) = current {
consolidated_samples.extend_from_slice(&chunk.samples[..chunk.sample_count]);
current = chunk.next.clone();
}
Arc::new(Self {
samples: consolidated_samples.into_boxed_slice(),
sample_count: total_samples,
next: None,
})
}
/// Write samples into this chunk and additional chunks as needed.
/// Uses the factory to get pre-allocated chunks when current chunk fills up.
pub fn write_samples<F>(this: &mut Arc<Self>, samples: &[f32], chunk_factory: F) -> Result<(), LooperError>
where
F: Fn() -> Result<Arc<AudioChunk>, LooperError>
{
let sample_count = samples.len();
if sample_count > 0 {
let available_space = this.samples.len();
let samples_to_write = sample_count.min(available_space);
// Get mutable access to write samples
let chunk_mut = Arc::get_mut(this).ok_or(LooperError::ChunkOwnership)?;
// Write what fits
if let Some(dest) = chunk_mut.samples.get_mut(..samples_to_write) {
dest.copy_from_slice(&samples[..samples_to_write]);
chunk_mut.sample_count = samples_to_write;
}
// Handle remaining samples if any
if samples_to_write < sample_count {
// Need a new chunk for remaining samples
let mut new_chunk = chunk_factory()?;
let remaining_samples = &samples[samples_to_write..];
// Recurse on the new chunk
Self::write_samples(&mut new_chunk, remaining_samples, chunk_factory)?;
// Link the new chunk
chunk_mut.next = Some(new_chunk);
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_allocate_creates_empty_chunk() {
let chunk = AudioChunk::allocate(1024);
assert_eq!(chunk.samples.len(), 1024);
assert_eq!(chunk.sample_count, 0);
assert!(chunk.next.is_none());
// All samples should be initialized to 0.0
assert!(chunk.samples.iter().all(|&x| x == 0.0));
}
#[test]
fn test_consolidate_single_chunk() {
let mut chunk = AudioChunk::allocate(10);
// Write some sample data
let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let factory = || panic!("Factory should not be called");
let result = AudioChunk::write_samples(&mut chunk, &samples, factory);
assert!(result.is_ok());
let consolidated = AudioChunk::consolidate(&chunk);
assert_eq!(consolidated.samples.len(), 5);
assert_eq!(consolidated.sample_count, 5);
assert!(consolidated.next.is_none());
assert_eq!(consolidated.samples[0], 1.0);
assert_eq!(consolidated.samples[4], 5.0);
}
#[test]
fn test_consolidate_linked_chunks() {
// Create first chunk
let chunk1 = Arc::new(AudioChunk {
samples: vec![1.0, 2.0, 3.0].into_boxed_slice(),
sample_count: 3,
next: None,
});
// Create second chunk
let chunk2 = Arc::new(AudioChunk {
samples: vec![4.0, 5.0].into_boxed_slice(),
sample_count: 2,
next: None,
});
// Create third chunk
let chunk3 = Arc::new(AudioChunk {
samples: vec![6.0, 7.0, 8.0, 9.0].into_boxed_slice(),
sample_count: 4,
next: None,
});
// Link them together
let chunk1 = Arc::new(AudioChunk {
samples: chunk1.samples.clone(),
sample_count: chunk1.sample_count,
next: Some(Arc::new(AudioChunk {
samples: chunk2.samples.clone(),
sample_count: chunk2.sample_count,
next: Some(chunk3),
})),
});
let consolidated = AudioChunk::consolidate(&chunk1);
assert_eq!(consolidated.samples.len(), 9);
assert_eq!(consolidated.sample_count, 9);
assert!(consolidated.next.is_none());
let expected = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
assert_eq!(consolidated.samples.as_ref(), expected.as_slice());
}
#[test]
fn test_consolidate_partial_chunks() {
// Test chunks where sample_count < samples.len()
let chunk1 = Arc::new(AudioChunk {
samples: vec![1.0, 2.0, 3.0, 0.0, 0.0].into_boxed_slice(),
sample_count: 3, // Only first 3 samples are valid
next: None,
});
let chunk2 = Arc::new(AudioChunk {
samples: vec![4.0, 5.0, 0.0].into_boxed_slice(),
sample_count: 2, // Only first 2 samples are valid
next: None,
});
let chunk1 = Arc::new(AudioChunk {
samples: chunk1.samples.clone(),
sample_count: chunk1.sample_count,
next: Some(chunk2),
});
let consolidated = AudioChunk::consolidate(&chunk1);
assert_eq!(consolidated.samples.len(), 5);
assert_eq!(consolidated.sample_count, 5);
let expected = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert_eq!(consolidated.samples.as_ref(), expected.as_slice());
}
#[test]
fn test_write_samples_empty() {
let mut chunk = AudioChunk::allocate(5);
let factory = || panic!("Factory should not be called for empty samples");
let result = AudioChunk::write_samples(&mut chunk, &[], factory);
assert!(result.is_ok());
assert_eq!(chunk.sample_count, 0);
assert!(chunk.next.is_none());
}
#[test]
fn test_write_samples_fits_in_one_chunk() {
let mut chunk = AudioChunk::allocate(5);
let factory = || panic!("Factory should not be called when samples fit");
let samples = vec![1.0, 2.0, 3.0];
let result = AudioChunk::write_samples(&mut chunk, &samples, factory);
assert!(result.is_ok());
assert_eq!(chunk.sample_count, 3);
assert_eq!(chunk.samples[0], 1.0);
assert_eq!(chunk.samples[1], 2.0);
assert_eq!(chunk.samples[2], 3.0);
assert!(chunk.next.is_none());
}
#[test]
fn test_write_samples_exactly_fills_chunk() {
let mut chunk = AudioChunk::allocate(3);
let factory = || panic!("Factory should not be called when samples exactly fit");
let samples = vec![1.0, 2.0, 3.0];
let result = AudioChunk::write_samples(&mut chunk, &samples, factory);
assert!(result.is_ok());
assert_eq!(chunk.sample_count, 3);
assert_eq!(chunk.samples[0], 1.0);
assert_eq!(chunk.samples[1], 2.0);
assert_eq!(chunk.samples[2], 3.0);
assert!(chunk.next.is_none());
}
#[test]
fn test_write_samples_requires_new_chunk() {
let mut chunk = AudioChunk::allocate(2);
use std::cell::RefCell;
let available_chunks = RefCell::new(vec![AudioChunk::allocate(3)]);
let factory = || available_chunks.borrow_mut().pop().ok_or(LooperError::ChunkAllocation);
let samples = vec![1.0, 2.0, 3.0, 4.0]; // 4 samples, first chunk holds 2
let result = AudioChunk::write_samples(&mut chunk, &samples, factory);
assert!(result.is_ok());
// First chunk: 2 samples
assert_eq!(chunk.sample_count, 2);
assert_eq!(chunk.samples[0], 1.0);
assert_eq!(chunk.samples[1], 2.0);
// Second chunk: remaining 2 samples
let chunk2 = chunk.next.as_ref().unwrap();
assert_eq!(chunk2.sample_count, 2);
assert_eq!(chunk2.samples[0], 3.0);
assert_eq!(chunk2.samples[1], 4.0);
assert!(chunk2.next.is_none());
}
#[test]
fn test_write_samples_multiple_chunks_cascade() {
let mut chunk = AudioChunk::allocate(2);
use std::cell::RefCell;
let available_chunks = RefCell::new(vec![
AudioChunk::allocate(2),
AudioChunk::allocate(2),
]);
let factory = || available_chunks.borrow_mut().pop().ok_or(LooperError::ChunkAllocation);
let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; // 6 samples, each chunk holds 2
let result = AudioChunk::write_samples(&mut chunk, &samples, factory);
assert!(result.is_ok());
// First chunk
assert_eq!(chunk.sample_count, 2);
assert_eq!(chunk.samples[0], 1.0);
assert_eq!(chunk.samples[1], 2.0);
// Second chunk
let chunk2 = chunk.next.as_ref().unwrap();
assert_eq!(chunk2.sample_count, 2);
assert_eq!(chunk2.samples[0], 3.0);
assert_eq!(chunk2.samples[1], 4.0);
// Third chunk
let chunk3 = chunk2.next.as_ref().unwrap();
assert_eq!(chunk3.sample_count, 2);
assert_eq!(chunk3.samples[0], 5.0);
assert_eq!(chunk3.samples[1], 6.0);
assert!(chunk3.next.is_none());
}
#[test]
fn test_write_samples_factory_failure() {
let mut chunk = AudioChunk::allocate(2);
let factory = || Err(LooperError::ChunkAllocation);
let samples = vec![1.0, 2.0, 3.0]; // 3 samples, chunk only holds 2
let result = AudioChunk::write_samples(&mut chunk, &samples, factory);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), LooperError::ChunkAllocation));
// First chunk should have been written before factory failure
assert_eq!(chunk.sample_count, 2);
assert_eq!(chunk.samples[0], 1.0);
assert_eq!(chunk.samples[1], 2.0);
assert!(chunk.next.is_none());
}
#[test]
fn test_write_samples_ownership_failure() {
let chunk = AudioChunk::allocate(3);
let chunk_clone = chunk.clone(); // Create another reference
let mut chunk_original = chunk;
let factory = || panic!("Factory should not be called");
let samples = vec![1.0, 2.0];
let result = AudioChunk::write_samples(&mut chunk_original, &samples, factory);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), LooperError::ChunkOwnership));
// Chunk should be unchanged
assert_eq!(chunk_original.sample_count, 0);
// Clean up the clone reference to avoid unused variable warning
drop(chunk_clone);
}
}

8
src/looper_error.rs Normal file
View File

@ -0,0 +1,8 @@
#[derive(Debug, thiserror::Error)]
pub enum LooperError {
#[error("Failed to allocate new audio chunk")]
ChunkAllocation,
#[error("Cannot modify chunk with multiple references")]
ChunkOwnership,
}

View File

@ -1,3 +1,21 @@
mod audio_chunk;
mod looper_error;
use audio_chunk::AudioChunk;
use looper_error::LooperError;
fn main() {
println!("Hello, world!");
let mut chunk = AudioChunk::allocate(2);
use std::cell::RefCell;
let available_chunks = RefCell::new(vec![
AudioChunk::allocate(2),
AudioChunk::allocate(2),
]);
let factory = || available_chunks.borrow_mut().pop().ok_or(LooperError::ChunkAllocation);
let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; // 6 samples, each chunk holds 2
AudioChunk::write_samples(&mut chunk, &samples, factory).unwrap();
AudioChunk::consolidate(&chunk);
println!("Audio chunk allocated with size: {}", chunk.samples.len());
}