Initial commit

This commit is contained in:
geens 2025-05-25 23:53:46 +02:00
commit 5ab36a7d02
9 changed files with 1739 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

657
Cargo.lock generated Normal file
View File

@ -0,0 +1,657 @@
# This file is automatically @generated by Cargo.
# 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",
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "ttf-parser"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
[[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",
]

16
Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "looper"
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"

BIN
FCB1010.pdf Normal file

Binary file not shown.

240
FCB1010_SysEx.md Normal file
View File

@ -0,0 +1,240 @@
You're absolutely right - the current document lacks the specific technical details needed for implementation. Let me examine the repositories more closely for implementation details.
I'll need to search for the specific Python file in the repository.
I need to look at the actual code to get the detailed specification. Let me search for the source code directly.
Let me try to find a specific PDF that might contain detailed SysEx specifications:
Now I have the detailed specification we need! Let me update our documentation with this complete information:
# FCB1010 SysEx Protocol Documentation
## 1. Introduction
This document describes the System Exclusive (SysEx) protocol used by the Behringer FCB1010 MIDI Foot Controller. The protocol allows configuration of the device's global settings and individual presets through MIDI SysEx messages. This documentation is based on official Behringer documentation and reverse-engineered implementations.
## 2. SysEx Message Structure
### 2.1 General Format
FCB1010 SysEx messages follow this general structure:
```
F0 00 20 32 <global-channel> 0C <data...> F7
```
Where:
- `F0`: SysEx Start byte
- `00 20 32`: Behringer's manufacturer ID
- `<global-channel>`: Global MIDI channel
- `0C`: Device ID for FCB1010
- `<data...>`: Message payload (in 7-bit format)
- `F7`: SysEx End byte
### 2.2 Data Encoding Scheme
Due to MIDI's 7-bit transmission limitation, the FCB1010 uses a special encoding scheme for 8-bit data:
- Data is transmitted in packets of 8 bytes
- The first 7 bytes contain the lower 7 bits of each data byte
- The 8th byte contains the most significant bits (MSBs) of the preceding 7 bytes
- This allows reconstructing the full 8-bit values from the 7-bit MIDI data
For example, to transmit 7 bytes with the following MSBs: 0,1,0,0,1,1,0:
- The 8th byte would be: `01001100` (binary) or `4C` (hex)
- Each bit position in this byte corresponds to the MSB of one of the preceding 7 bytes
## 3. Memory Map
The FCB1010 memory is organized as follows:
### 3.1 Preset Data
- **Addresses**: `0x000` to `0x640`
- **Structure**: 100 presets (10 banks of 10 presets each)
- **Preset Size**: 16 bytes per preset
### 3.2 Global Configuration
- **Addresses**: `0x7E0` to `0x7E9`
- Contains global MIDI channel assignments for each function
## 4. Preset Structure
Each preset occupies 16 bytes and contains the following data:
| Offset | Description | Notes |
|--------|-------------|-------|
| 0x0 | Program Change 1 | Program number |
| 0x1 | Program Change 2 | Program number |
| 0x2 | Program Change 3 | Program number |
| 0x3 | Program Change 4 | Program number |
| 0x4 | Program Change 5 | Program number |
| 0x5 | Controller 1 | Controller number |
| 0x6 | Controller 1 Value | Value with Relay 1 status in MSB |
| 0x7 | Controller 2 | Controller number |
| 0x8 | Controller 2 Value | Value with Relay 2 status in MSB |
| 0x9 | Expression Pedal A | Controller number |
| 0xA | Expression Pedal A Lower Value | Minimum value |
| 0xB | Expression Pedal A Upper Value | Maximum value |
| 0xC | Expression Pedal B | Controller number |
| 0xD | Expression Pedal B Lower Value | Minimum value |
| 0xE | Expression Pedal B Upper Value | Maximum value |
| 0xF | Note | MIDI note number |
### 4.1 Special Fields
- **Switch Relay Status**: The MSB of Controller 1 Value contains Relay 1 status; the MSB of Controller 2 Value contains Relay 2 status
- **Program Change Values**: Range 0-127, correspond to MIDI program changes 1-128
- **Controller Numbers**: Range 0-127, standard MIDI CC values
- **Note Values**: Range 0-127, standard MIDI note numbers
## 5. Memory Addressing
Each preset's starting address is calculated as:
```
Address = 0x000 + (preset_number * 0x10)
```
Where `preset_number` ranges from 0 to 99:
- Bank 0, Preset 1 = Preset 0 = Address 0x000
- Bank 0, Preset 2 = Preset 1 = Address 0x010
- Bank 0, Preset 10 = Preset 9 = Address 0x090
- Bank 1, Preset 1 = Preset 10 = Address 0x100
- ...and so on
## 6. Global MIDI Channel Configuration
The MIDI channels for each function are stored at addresses 0x7E0 to 0x7E9:
| Address | Function |
|---------|----------|
| 0x7E0 | Program Change 1 Channel |
| 0x7E1 | Program Change 2 Channel |
| 0x7E2 | Program Change 3 Channel |
| 0x7E3 | Program Change 4 Channel |
| 0x7E4 | Program Change 5 Channel |
| 0x7E5 | Controller 1 Channel |
| 0x7E6 | Controller 2 Channel |
| 0x7E7 | Expression Pedal A Channel |
| 0x7E8 | Expression Pedal B Channel |
| 0x7E9 | Note Channel |
Values range from 0-15, corresponding to MIDI channels 1-16.
## 7. Sending SysEx to FCB1010
To send configuration data to the FCB1010:
1. Put the FCB1010 in SysEx receive mode:
- Hold DOWN button while powering on
- Press DOWN again to enter CONFIG mode
- Press footswitch 7 (SYSEX RCV)
2. Send the SysEx message with the following structure:
```
F0 00 20 32 <global-channel> 0C <encoded-data> F7
```
3. The FCB1010 will automatically exit receive mode once data is received successfully
## 8. Receiving SysEx from FCB1010
To retrieve the current configuration from the FCB1010:
1. Put the FCB1010 in SysEx send mode:
- Hold DOWN button while powering on
- Press UP until CONFIG LED lights
- Press footswitch 6 (SYSEX SEND)
2. Capture the SysEx data sent from the FCB1010
3. Decode the received data using the 7-bit to 8-bit conversion method described above
## 9. Implementation Example (Pseudocode)
### 9.1 Encoding Data for SysEx Transmission
```
function encode_data_for_sysex(data):
encoded_data = []
for i in range(0, len(data), 7):
chunk = data[i:i+7]
while len(chunk) < 7:
chunk.append(0) # Pad last chunk if needed
msb_byte = 0
for j in range(7):
if j < len(chunk):
# Extract MSB from each byte and put in msb_byte
msb_byte |= ((chunk[j] & 0x80) >> 7) << j
# Clear MSB from original byte
encoded_data.append(chunk[j] & 0x7F)
encoded_data.append(msb_byte)
return encoded_data
```
### 9.2 Decoding Received SysEx Data
```
function decode_sysex_data(encoded_data):
decoded_data = []
for i in range(0, len(encoded_data), 8):
if i + 7 >= len(encoded_data):
break # Not enough data for a complete chunk
msb_byte = encoded_data[i+7]
for j in range(7):
if i + j < len(encoded_data) - 1: # Skip the MSB byte
byte_value = encoded_data[i+j]
# Apply MSB from msb_byte
if (msb_byte & (1 << j)) != 0:
byte_value |= 0x80
decoded_data.append(byte_value)
return decoded_data
```
### 9.3 Creating a Preset Configuration
```
function create_preset(bank, preset_number, config):
preset_index = (bank * 10) + preset_number - 1 # Convert to 0-99 index
preset_address = 0x000 + (preset_index * 0x10)
preset_data = [
config.program_change_1,
config.program_change_2,
config.program_change_3,
config.program_change_4,
config.program_change_5,
config.controller_1_number,
config.controller_1_value | (config.relay_1_status << 7),
config.controller_2_number,
config.controller_2_value | (config.relay_2_status << 7),
config.expression_pedal_a_controller,
config.expression_pedal_a_min,
config.expression_pedal_a_max,
config.expression_pedal_b_controller,
config.expression_pedal_b_min,
config.expression_pedal_b_max,
config.note
]
return (preset_address, preset_data)
```
## 10. Compatibility Notes
- This protocol specification is based on FCB1010 firmware version 2.5
- The protocol may differ for other firmware versions or third-party firmware
- Large SysEx dumps may not work reliably with some MIDI-USB interfaces due to buffer limitations
## 11. References
1. Behringer FCB1010 SysEx File Structure (2003), Behringer Spezielle Studiotechnik GmbH
2. riban-bw/fcb1010 GitHub repository - Python implementation for FCB1010 SysEx communication
3. trafficpest/fcbtool GitHub repository - C-based tool for managing FCB1010 presets

261
FCB1010_integration.md Normal file
View File

@ -0,0 +1,261 @@
# FCB1010 MIDI Integration Technical Reference
## 1. MIDI Controller Configuration
The FCB1010 will be configured with a consistent set of Control Change (CC) messages for all buttons and pedals:
| Control | CC Number | Value | Description |
|---------|-----------|-------|-------------|
| **Button 1** | CC 1 | 127/0 | Value 127 when pressed, 0 when released |
| **Button 2** | CC 2 | 127/0 | Value 127 when pressed, 0 when released |
| **Button 3** | CC 3 | 127/0 | Value 127 when pressed, 0 when released |
| **Button 4** | CC 4 | 127/0 | Value 127 when pressed, 0 when released |
| **Button 5** | CC 5 | 127/0 | Value 127 when pressed, 0 when released |
| **Button 6** | CC 6 | 127/0 | Value 127 when pressed, 0 when released |
| **Button 7** | CC 7 | 127/0 | Value 127 when pressed, 0 when released |
| **Button 8** | CC 8 | 127/0 | Value 127 when pressed, 0 when released |
| **Button 9** | CC 9 | 127/0 | Value 127 when pressed, 0 when released |
| **Button 10** | CC 10 | 127/0 | Value 127 when pressed, 0 when released |
| **UP** | CC 11 | 127/0 | Value 127 when pressed, 0 when released |
| **DOWN** | CC 12 | 127/0 | Value 127 when pressed, 0 when released |
| **Expression A** | CC 16 | 0-127 | Continuous controller |
| **Expression B** | CC 17 | 0-127 | Continuous controller |
| **MIDI Channel** | All on Channel 1 | | Single channel for simplicity |
## 2. System Architecture
### 2.1 Integration Diagram
```
+-------------+ +----------------+ +-------------+ +--------------+
| JACK Thread | --> | Lock-free MIDI | --> | MIDI Thread | --> | Application |
| (real-time) | | Input Queue | | | | Logic Thread |
+-------------+ +----------------+ +-------------+ +--------------+
^ |
| v
+----------------+ +----------------+
| Lock-free MIDI | | Configuration |
| Output Queue | <----------------------| Commands |
+----------------+ +----------------+
```
### 2.2 Communication Channels
The system uses Crossbeam channels for thread communication:
1. **MIDI Input Channel**: Transfers raw MIDI data from JACK thread to MIDI processing thread
2. **MIDI Output Channel**: Transfers SysEx configuration messages from MIDI thread to JACK thread
3. **Event Channel**: Delivers controller events from MIDI thread to application logic
4. **Command Channel**: Sends configuration commands from application to MIDI thread
### 2.3 MIDI Message Flow
1. **JACK Callback (Real-time Thread)**:
- Receives MIDI data from FCB1010
- Copies data to `midi_input_sender`
- Reads from `midi_output_receiver` for SysEx configuration
2. **MIDI Processing Thread**:
- Reads from `midi_input_receiver`
- Translates MIDI messages to `ControllerEvent` instances
- Sends events via `event_sender`
- Processes configuration commands from `command_receiver`
- Sends SysEx messages via `midi_output_sender`
3. **Application Logic Thread**:
- Reads from `event_receiver`
- Sends configuration commands via `command_sender`
## 3. Message Translation
### 3.1 MIDI to ControllerEvent Translation
The MIDI processing thread translates FCB1010 MIDI messages to controller-independent events according to the following mapping:
| MIDI Message | Condition | Translated Event |
|--------------|-----------|------------------|
| CC 1, Value 127 | Channel 1 | ButtonPress(1) |
| CC 2, Value 127 | Channel 1 | ButtonPress(2) |
| CC 3, Value 127 | Channel 1 | ButtonPress(3) |
| CC 4, Value 127 | Channel 1 | ButtonPress(4) |
| CC 5, Value 127 | Channel 1 | ButtonPress(5) |
| CC 6, Value 127 | Channel 1 | ButtonPress(6) |
| CC 7, Value 127 | Channel 1 | ButtonPress(7) |
| CC 8, Value 127 | Channel 1 | ButtonPress(8) |
| CC 9, Value 127 | Channel 1 | ButtonPress(9) |
| CC 10, Value 127 | Channel 1 | ButtonPress(10) |
| CC 11, Value 127 | Channel 1 | Navigation(NavigationButton::Up) |
| CC 12, Value 127 | Channel 1 | Navigation(NavigationButton::Down) |
| CC 16, Any Value | Channel 1 | ExpressionPedal(ExpressionPedal::A, value) |
| CC 17, Any Value | Channel 1 | ExpressionPedal(ExpressionPedal::B, value) |
Notes on translation:
- Only button press events (value 127) are translated for footswitches and navigation buttons
- Button release events (value 0) are ignored
- All expression pedal value changes (0-127) are translated
- Messages on MIDI channels other than Channel 1 are ignored
- Other MIDI message types (Program Change, Note On/Off, etc.) are ignored
### 3.2 Event Types
```rust
pub enum ControllerEvent {
/// Footswitch button press (1-10)
ButtonPress(u8),
/// Navigation button press
Navigation(NavigationButton),
/// Expression pedal value change
ExpressionPedal(ExpressionPedal, u8), // value 0-127
}
pub enum NavigationButton {
Up,
Down,
}
pub enum ExpressionPedal {
A,
B,
}
pub enum FcbCommand {
/// Configure the FCB1010 for our application
Initialize,
}
```
## 4. FCB1010 Initialization
The FCB1010 is configured at startup using SysEx messages:
```rust
fn initialize_fcb1010(command_sender: &crossbeam::channel::Sender<FcbCommand>) {
// Send initialization command to MIDI processing thread
command_sender.send(FcbCommand::Initialize).unwrap_or_else(|e| {
error!("Failed to send FCB1010 initialization command: {}", e);
});
}
```
The MIDI processing thread handles the initialization by sending SysEx messages:
```rust
fn handle_fcb_command(
command: FcbCommand,
midi_output_sender: &crossbeam::channel::Sender<Vec<u8>>
) {
match command {
FcbCommand::Initialize => {
info!("Initializing FCB1010");
// Get SysEx messages from FCB1010 SysEx generator
let sysex_messages = generate_fcb1010_configuration();
// Send each SysEx message with a delay to avoid buffer overruns
for message in sysex_messages {
midi_output_sender.send(message).unwrap_or_else(|e| {
error!("Failed to send SysEx message: {}", e);
});
// Allow time for the message to be processed
std::thread::sleep(std::time::Duration::from_millis(50));
}
info!("FCB1010 initialization complete");
}
}
}
```
## 5. JACK Integration
### 5.1 JACK Process Callback
The JACK process callback runs in a real-time thread with strict timing constraints. To maintain real-time safety:
1. **No Memory Allocations**: The callback must never allocate memory (no `Vec`, `String`, `Box`, etc.)
2. **No Blocking Operations**: The callback must not block (no file I/O, mutex locks, etc.)
3. **Minimal Processing**: Only the bare minimum work should be done in this thread
For FCB1010 integration, the JACK callback should:
1. **Read MIDI Input**: Copy raw MIDI data bytes to a pre-allocated lock-free ring buffer
2. **Write MIDI Output**: Read from a lock-free buffer and send to the MIDI output port
#### Avoiding Allocations
To avoid allocations while handling MIDI data:
1. **Pre-allocated Buffers**: Use fixed-size arrays or pre-allocated ring buffers
2. **Zero-Copy Access**: Access MIDI data directly without copying when possible
3. **Message Passing**: Use lock-free data structures that don't require allocation
4. **Byte References**: Pass references to MIDI data rather than copying the data
### 5.2 MIDI Processing Thread
The MIDI processing thread runs continuously outside of JACK's real-time constraints, allowing for:
1. Receiving MIDI data from the lock-free input buffer
2. Translating MIDI messages to controller events
3. Sending events to the application logic
4. Processing configuration commands
5. Sending SysEx messages to the FCB1010
This thread can safely perform memory allocations and more complex processing since it's not subject to real-time constraints.
## 6. Testing Approach
### 6.1 Mock FCB Implementation
For testing without hardware, a mock FCB implementation will use the same MIDI message format as the physical FCB1010:
1. **JACK-Based MIDI Device**: The mock will appear as a standard MIDI device in the JACK system
2. **Message Format Consistency**: It will generate identical CC messages following the table in section 1
3. **Bidirectional Communication**: It will receive and verify SysEx configuration messages
4. **Programmatic Control**: It can be triggered by test code to simulate button presses and pedal movements
This approach allows testing the complete integration without requiring the physical FCB1010 hardware, while ensuring message format compatibility.
The mock FCB will translate controller-independent events back to MIDI messages, effectively doing the reverse of the translation described in section 3.1. This symmetrical translation enables comprehensive testing of the entire message pipeline.
### 6.2 Integration Testing
Integration tests will verify the complete flow from MIDI messages to application events:
1. Create a mock FCB instance
2. Start the MIDI processing thread
3. Generate MIDI messages from the mock FCB
4. Verify correct `ControllerEvent` instances are produced
5. Test error conditions and recovery
### 6.3 SysEx Testing
SysEx testing will verify the FCB1010 configuration messages:
1. Create a mock FCB that captures SysEx messages
2. Send the initialization command
3. Verify the SysEx messages match the expected format
4. Test with corrupted or incomplete SysEx responses
## 7. Logging
The integration will use the Rust `log` crate for detailed logging:
```rust
// MIDI message logging
debug!("Received MIDI message: {:?}", data);
// Controller event logging
info!("Translated to controller event: {:?}", event);
// SysEx logging
info!("Sending FCB1010 configuration SysEx");
debug!("SysEx message: {:?}", sysex_message);
// Error logging
error!("Failed to process MIDI message: {}", error);
```
Initial logs will go to standard output, with a future extension to log to a UI window.

462
display_mockup.html Normal file
View File

@ -0,0 +1,462 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Looper Pedal Display</title>
<style>
body {
margin: 0;
padding: 20px;
background: #111;
font-family: 'Courier New', monospace;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.display-container {
width: 600px;
height: 380px;
background: linear-gradient(145deg, #1a1a1a, #0f0f0f);
border: 3px solid #333;
border-radius: 8px;
padding: 15px;
box-shadow: 0 0 20px rgba(0,0,0,0.8);
}
.metronome-section {
height: 60px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 15px;
position: relative;
}
.metronome-track {
width: 200px;
height: 4px;
background: #333;
border-radius: 2px;
position: relative;
}
.metronome-indicator {
width: 20px;
height: 20px;
background: #00ff88;
border-radius: 50%;
position: absolute;
top: -8px;
left: 90px;
box-shadow: 0 0 15px #00ff88;
animation: metronome 1s ease-in-out infinite alternate;
}
@keyframes metronome {
0% { left: 10px; background: #666; box-shadow: 0 0 5px #666; }
50% { background: #00ff88; box-shadow: 0 0 15px #00ff88; }
100% { left: 170px; background: #666; box-shadow: 0 0 5px #666; }
}
.tempo-display {
position: absolute;
right: 0;
color: #00ff88;
font-size: 16px;
font-weight: bold;
}
.columns-section {
height: 45px;
margin-bottom: 3px;
}
.column-header {
display: flex;
justify-content: space-between;
}
.column {
flex: 1;
margin: 0 3px;
text-align: center;
}
.column.active .progress-bar {
border: 2px solid #00ff88;
}
.progress-bar {
height: 40px;
background: #333;
border-radius: 6px;
position: relative;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #0066cc, #00aaff);
border-radius: 6px;
position: relative;
}
.bar-markers {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
}
.bar-marker {
flex: 1;
border-right: 1px solid #666;
}
.bar-marker:last-child {
border-right: none;
}
.tracks-section {
height: 250px;
}
.tracks-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(5, 1fr);
gap: 4px;
height: 250px;
}
.track-cell {
border: 1px solid #333;
border-radius: 4px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
font-size: 10px;
}
.track-cell.empty {
background: #1a1a1a;
border-color: #333;
}
.track-cell.ready {
background: #0a3020;
border-color: #00aa66;
}
.track-cell.playing {
background: #002a40;
border-color: #0088cc;
}
.track-cell.active-row {
border-width: 2px;
}
.track-cell.selected.track-cell.recording {
background: #cc4444;
border-color: #440a0a;
color: #440a0a;
}
.track-state {
font-size: 10px;
font-weight: bold;
}
.track-cell.empty .track-state {
color: #666;
border-color: #666;
}
.track-cell.ready .track-state {
color: #00aa66;
border-color: #00aa66;
}
.track-cell.playing .track-state {
color: #0088cc;
border-color: #0088cc;
}
.volume-bar {
position: absolute;
bottom: 2px;
left: 2px;
right: 2px;
height: 2px;
background: #333;
border-radius: 1px;
}
.volume-fill {
height: 100%;
background: #00ff88;
border-radius: 1px;
}
.pedal-info {
position: absolute;
bottom: 10px;
left: 15px;
right: 15px;
display: flex;
justify-content: space-between;
color: #888;
font-size: 11px;
}
</style>
</head>
<body>
<div class="display-container">
<!-- Metronome Section -->
<div class="metronome-section">
<div class="metronome-track">
<div class="metronome-indicator"></div>
</div>
<div class="tempo-display">120 BPM</div>
</div>
<!-- Columns Section -->
<div class="columns-section">
<div class="column-header">
<div class="column">
<div class="progress-bar">
<div class="progress-fill" style="width: 75%;">
<div class="bar-markers">
<div class="bar-marker"></div>
<div class="bar-marker"></div>
<div class="bar-marker"></div>
<div class="bar-marker"></div>
</div>
</div>
</div>
</div>
<div class="column">
<div class="progress-bar">
<div class="progress-fill" style="width: 25%;">
<div class="bar-markers">
<div class="bar-marker"></div>
<div class="bar-marker"></div>
</div>
</div>
</div>
</div>
<div class="column">
<div class="progress-bar">
<div class="progress-fill" style="width: 0%;">
<div class="bar-markers">
<div class="bar-marker"></div>
</div>
</div>
</div>
</div>
<div class="column">
<div class="progress-bar">
<div class="progress-fill" style="width: 90%;">
<div class="bar-markers">
<div class="bar-marker"></div>
<div class="bar-marker"></div>
<div class="bar-marker"></div>
<div class="bar-marker"></div>
<div class="bar-marker"></div>
<div class="bar-marker"></div>
<div class="bar-marker"></div>
<div class="bar-marker"></div>
</div>
</div>
</div>
</div>
<div class="column">
<div class="progress-bar">
<div class="progress-fill" style="width: 0%;">
<div class="bar-markers">
<div class="bar-marker"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Tracks Section -->
<div class="tracks-section">
<div class="tracks-grid">
<!-- Row 1 -->
<div class="track-cell ready">
<div class="track-state">READY</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 80%;"></div>
</div>
</div>
<div class="track-cell playing">
<div class="track-state">PLAY</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 75%;"></div>
</div>
</div>
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<div class="track-cell ready">
<div class="track-state">READY</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 60%;"></div>
</div>
</div>
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<!-- Row 2 (Current) -->
<div class="track-cell ready active-row">
<div class="track-state">READY</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 65%;"></div>
</div>
</div>
<div class="track-cell recording selected active-row">
<div class="track-state">REC</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 100%;"></div>
</div>
</div>
<div class="track-cell empty active-row">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<div class="track-cell playing active-row">
<div class="track-state">PLAY</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 45%;"></div>
</div>
</div>
<div class="track-cell empty active-row">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<!-- Row 3 -->
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<div class="track-cell ready">
<div class="track-state">READY</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 70%;"></div>
</div>
</div>
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<!-- Row 4 -->
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<!-- Row 5 -->
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
<div class="track-cell empty">
<div class="track-state">---</div>
<div class="volume-bar">
<div class="volume-fill" style="width: 0%;"></div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

99
project_spec.md Normal file
View File

@ -0,0 +1,99 @@
# FCB1010 Looper Pedal - Complete Project Specification
## System Overview
A looper pedal system using a Behringer FCB1010 MIDI foot controller, Behringer UMC404HD audio interface, Raspberry Pi with TFT display (215x135mm).
The system provides a 5x5 matrix of loop tracks organized in columns and rows, with real-time visual feedback.
## Hardware Components
- **Behringer FCB1010**: MIDI foot controller with 10 footswitches (1-10), UP/DOWN buttons, and 2 expression pedals
- **Behringer UMC404HD**: Audio interface for input/output
- **Raspberry Pi**: Running Linux
- **TFT Display**: 215x135mm color display, no touchscreen
- **Audio**: JACK audio connect kit
## System Architecture
### Two-State Operation
1. **Menu State**: Configuration and management functions
2. **Performance State**: Recording and playback matrix
### Matrix Organization
- **5 Columns**: Synchronized recording and playback for each column
- **5 Rows**: Individual tracks within each column
- **25 Total Cells**: Each cell can contain one audio loop
- **Column-based Timing**: First recording in a column sets the bar count for all tracks in that column
## Interface Design
- **Metronome**: Moving indicator that pulses when in center
- **Progress Bars**: Show current position in each column with bar divisions
- **Active Row**: Increased border with
- **Selected Cell**: Switched border and background color
- **Track States**:
- `---`: Empty cell (dark background)
- `READY`: Recorded, ready to play (green)
- `PLAY`: Currently playing (blue)
- `REC`: Currently recording (red)
- `SOLO`: Solo mode active (orange/amber)
- **Volume Bars**: Small bars under each cell showing track volume
## Button Mappings
### Menu State
- **Button 1**: Tap Tempo
- **Button 2**: Enable/Disable Click Track
- **Button 3**: Clear selected Column
- **Button 4**: Quantization Settings (Off, 1/4, 1/8, 1/16 note)
- **Button 5**: Save/Load Project (use buttons 6-10 for slot selection)
- **Buttons 6-10**: Column selection (for clear/save/load operations)
- **UP/DOWN**: Switch to Performance Mode
- **Expression Pedal A**: Click track volume
- **Expression Pedal B**: Master volume
### Performance State
- **Button 1**: Record/Arm
- **Button 2**: Play/Mute current track
- **Button 3**: Solo current track
- **Button 4**: Overdub on current track
- **Button 5**: Clear current track
- **Buttons 6-10**: Column selection (1-5)
- **UP/DOWN**: Navigate rows (switch between track rows), return to Menu when at top or bottom
- **Expression Pedal A**: Current track volume
- **Expression Pedal B**: Master volume
## Operational Behavior
### Recording Workflow
- **First Recording in Column**: Sets bar count for entire column, records until stopped
- **Subsequent Recordings**: Auto-sync to established column length
- **Record Button Stop**: Recording stops, cell starts playing
- **Stop Button Stop**: Recording cancelled, previous state restored
- **Overdub Button Stop**: Recording continues for next loop, overdubs on previous content
### Timing and Synchronization
- **All actions sync to the beat**: Start/stop occurs at metronome center position
- **Column Switching**: Only when not recording, immediate switching to selected column
- **Tempo Changes**: Only allowed when all tracks are cleared
- **Recording Start**: Synced to column start if column playing, otherwise next bar
### Solo Functionality
- **Solo Activation**: Press Solo button on any track
- **Solo Indication**: Cell shows `SOLO` state in orange/amber color
- **Multiple Solo**: Additional tracks can be added to solo group
- **Solo Play**: Activates solo tracks + any tracks showing `PLAY` state
- **Solo Stop**: Stops entire column
- **All Solo Actions**: Synced to next beat
### MIDI Implementation
- **Startup**: Send SysEx configuration to FCB1010 for proper button/pedal mapping
- **Expression Pedals**: Set volume on change
- **MIDI Feedback**: Send click track to sync with other systems
### State Management
- **Save in Numbered Slots**: 5 project slots accessible via buttons 6-10 in save/load mode
- **Startup Behavior**: Load last used project and configure FCB1010
- **Column Consistency**: Recordings automatically match column bar count
- **State Validation**: Prevent invalid operations (e.g., switching columns while recording)

3
src/main.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}