From 34addbfb48f37f77b23fd77b47cb2a7364c400bc Mon Sep 17 00:00:00 2001
From: ArthurGibert <110692829+ArthurGibert@users.noreply.github.com>
Date: Thu, 26 Feb 2026 15:07:11 +0100
Subject: [PATCH 01/22] fix(clock): fix hanging notes when pausing clock (#448)
---
faderpunk/src/apps/clk_div.rs | 5 +++++
faderpunk/src/apps/euclid.rs | 10 ++++++++++
faderpunk/src/apps/notefader.rs | 10 ++++++++++
faderpunk/src/apps/probatrigger.rs | 5 +++++
faderpunk/src/apps/seq8.rs | 31 +++++++++++++++++-------------
5 files changed, 48 insertions(+), 13 deletions(-)
diff --git a/faderpunk/src/apps/clk_div.rs b/faderpunk/src/apps/clk_div.rs
index 4b682bdd..5b56f655 100644
--- a/faderpunk/src/apps/clk_div.rs
+++ b/faderpunk/src/apps/clk_div.rs
@@ -191,6 +191,11 @@ pub async fn run(
note_on = false;
jack.set_low().await;
}
+ ClockEvent::Stop => {
+ midi.send_note_off(note).await;
+ note_on = false;
+ jack.set_low().await;
+ }
ClockEvent::Tick => {
let muted = glob_muted.get();
let div = div_glob.get();
diff --git a/faderpunk/src/apps/euclid.rs b/faderpunk/src/apps/euclid.rs
index 37daf329..43d3b264 100644
--- a/faderpunk/src/apps/euclid.rs
+++ b/faderpunk/src/apps/euclid.rs
@@ -220,7 +220,17 @@ pub async fn run(
midi.send_note_off(note).await;
midi.send_note_off(note2).await;
note_on = false;
+ aux_on = false;
jack[0].set_low().await;
+ jack[1].set_low().await;
+ }
+ ClockEvent::Stop => {
+ midi.send_note_off(note).await;
+ midi.send_note_off(note2).await;
+ note_on = false;
+ aux_on = false;
+ jack[0].set_low().await;
+ jack[1].set_low().await;
}
ClockEvent::Tick => {
let muted = glob_muted.get();
diff --git a/faderpunk/src/apps/notefader.rs b/faderpunk/src/apps/notefader.rs
index 93738350..d656a6ab 100644
--- a/faderpunk/src/apps/notefader.rs
+++ b/faderpunk/src/apps/notefader.rs
@@ -233,6 +233,16 @@ pub async fn run(
clkn = 0;
midi.send_note_off(note).await;
note_on = false;
+ if outmode == 1 {
+ jack.set_value(0);
+ }
+ }
+ ClockEvent::Stop => {
+ midi.send_note_off(note).await;
+ note_on = false;
+ if outmode == 1 {
+ jack.set_value(0);
+ }
}
ClockEvent::Tick => {
let muted = glob_muted.get();
diff --git a/faderpunk/src/apps/probatrigger.rs b/faderpunk/src/apps/probatrigger.rs
index df04ab0a..426f0e71 100644
--- a/faderpunk/src/apps/probatrigger.rs
+++ b/faderpunk/src/apps/probatrigger.rs
@@ -187,6 +187,11 @@ pub async fn run(
note_on = false;
jack.set_low().await;
}
+ ClockEvent::Stop => {
+ midi.send_note_off(note).await;
+ note_on = false;
+ jack.set_low().await;
+ }
ClockEvent::Tick => {
let muted = glob_muted.get();
let val = storage.query(|s| s.prob_saved);
diff --git a/faderpunk/src/apps/seq8.rs b/faderpunk/src/apps/seq8.rs
index a63821b0..70a6eec3 100644
--- a/faderpunk/src/apps/seq8.rs
+++ b/faderpunk/src/apps/seq8.rs
@@ -504,6 +504,12 @@ pub async fn run(
gate_out[n].set_low().await;
}
}
+ ClockEvent::Stop => {
+ for n in 0..4 {
+ midi[n].send_note_off(lastnote[n]).await;
+ gate_out[n].set_low().await;
+ }
+ }
ClockEvent::Tick => {
for n in 0..=3 {
if clockn.is_multiple_of(clockres[n]) {
@@ -520,8 +526,7 @@ pub async fn run(
as u32)
* 410
/ 4095) as u16
- + (storage.query(|s| s.oct_fader[n]) / 1000)
- * 410,
+ + (storage.query(|s| s.oct_fader[n]) / 1000) * 410,
)
.await;
lastnote[n] = out.as_midi();
@@ -634,23 +639,20 @@ struct AltUpdateContext<'a> {
resolution: &'a [usize; 8],
}
-fn apply_alt_update(
- chan: usize,
- seq_idx: usize,
- value: u16,
- ctx: &AltUpdateContext,
-) {
+fn apply_alt_update(chan: usize, seq_idx: usize, value: u16, ctx: &AltUpdateContext) {
match chan {
0 => {
// Sequence length
- ctx.storage.modify_and_save(|s| s.length_fader[seq_idx] = value);
+ ctx.storage
+ .modify_and_save(|s| s.length_fader[seq_idx] = value);
let mut arr = ctx.seq_length_glob.get();
arr[seq_idx] = (value / 256 + 1) as u8;
ctx.seq_length_glob.set(arr);
}
1 => {
// Gate length
- ctx.storage.modify_and_save(|s| s.gate_fader[seq_idx] = value);
+ ctx.storage
+ .modify_and_save(|s| s.gate_fader[seq_idx] = value);
let clockres = ctx.clockres_glob.get();
let mut arr = ctx.gatelength_glob.get();
arr[seq_idx] = (clockres[seq_idx] * (value as usize) / 4096) as u8;
@@ -659,15 +661,18 @@ fn apply_alt_update(
}
2 => {
// Octave
- ctx.storage.modify_and_save(|s| s.oct_fader[seq_idx] = value);
+ ctx.storage
+ .modify_and_save(|s| s.oct_fader[seq_idx] = value);
}
3 => {
// Range
- ctx.storage.modify_and_save(|s| s.range_fader[seq_idx] = value);
+ ctx.storage
+ .modify_and_save(|s| s.range_fader[seq_idx] = value);
}
4 => {
// Resolution
- ctx.storage.modify_and_save(|s| s.res_fader[seq_idx] = value);
+ ctx.storage
+ .modify_and_save(|s| s.res_fader[seq_idx] = value);
let res_index = (value / 512) as usize;
let mut arr = ctx.clockres_glob.get();
arr[seq_idx] = ctx.resolution[res_index];
From 45921282ff1484b9f6a486824450f8e417290fe6 Mon Sep 17 00:00:00 2001
From: Christian Maniewski
Date: Sun, 1 Mar 2026 16:24:34 +0200
Subject: [PATCH 02/22] fix(configurator): add markdown parser for app entries
(#451)
* fix(configurator): add markdown parser for app entries
* fix(configurator): add more chapter marks to manual
---
configurator/package.json | 1 +
configurator/pnpm-lock.yaml | 673 ++++++++++++++++++
configurator/src/components/ManualTab.tsx | 57 ++
.../src/components/manual/Configurator.tsx | 22 +-
.../src/components/manual/Interface.tsx | 12 +-
.../src/components/manual/ManualApp.tsx | 31 +-
configurator/src/components/manual/Md.tsx | 45 ++
configurator/src/components/manual/Shared.tsx | 12 +-
8 files changed, 822 insertions(+), 31 deletions(-)
create mode 100644 configurator/src/components/manual/Md.tsx
diff --git a/configurator/package.json b/configurator/package.json
index b825ed2f..386e84c8 100644
--- a/configurator/package.json
+++ b/configurator/package.json
@@ -38,6 +38,7 @@
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-hook-form": "^7.62.0",
+ "react-markdown": "^10.1.0",
"react-router-dom": "^7.9.3",
"semver": "^7.7.3",
"zod": "^4.1.9",
diff --git a/configurator/pnpm-lock.yaml b/configurator/pnpm-lock.yaml
index bd85aefc..fa1ab4a5 100644
--- a/configurator/pnpm-lock.yaml
+++ b/configurator/pnpm-lock.yaml
@@ -83,6 +83,9 @@ importers:
react-hook-form:
specifier: ^7.62.0
version: 7.62.0(react@19.1.1)
+ react-markdown:
+ specifier: ^10.1.0
+ version: 10.1.0(@types/react@19.1.9)(react@19.1.1)
react-router-dom:
specifier: ^7.9.3
version: 7.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -1579,12 +1582,27 @@ packages:
'@tanstack/virtual-core@3.11.3':
resolution: {integrity: sha512-v2mrNSnMwnPJtcVqNvV0c5roGCBqeogN8jDtgtuHCphdwBasOZ17x8UV8qpHUh+u0MLfX43c0uUHKje0s+Zb0w==}
+ '@types/debug@4.1.12':
+ resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
+
+ '@types/estree-jsx@1.0.5':
+ resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
+
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+ '@types/hast@3.0.4':
+ resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
+
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+ '@types/mdast@4.0.4':
+ resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
+
+ '@types/ms@2.1.0':
+ resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
+
'@types/react-dom@19.1.7':
resolution: {integrity: sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==}
peerDependencies:
@@ -1596,6 +1614,12 @@ packages:
'@types/semver@7.7.1':
resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
+ '@types/unist@2.0.11':
+ resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
+
+ '@types/unist@3.0.3':
+ resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
+
'@types/w3c-web-usb@1.0.12':
resolution: {integrity: sha512-GD9XFhJZFtCbspsB3t1vD3SgkWVInIMoL1g1CcE0p3DD7abgLrQ2Ws22RS38CXPUCQXgyKjUAGKdy5d0CLT5jw==}
@@ -1658,6 +1682,9 @@ packages:
resolution: {integrity: sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@ungap/structured-clone@1.3.0':
+ resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+
'@vitejs/plugin-react-swc@3.11.0':
resolution: {integrity: sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==}
peerDependencies:
@@ -1683,6 +1710,9 @@ packages:
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ bail@2.0.2:
+ resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
+
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -1700,10 +1730,25 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
+ ccount@2.0.1:
+ resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
+
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
+ character-entities-html4@2.1.0:
+ resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
+
+ character-entities-legacy@3.0.0:
+ resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
+
+ character-entities@2.0.2:
+ resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
+
+ character-reference-invalid@2.0.1:
+ resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
+
chownr@3.0.0:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
@@ -1736,6 +1781,9 @@ packages:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
+ comma-separated-tokens@2.0.3:
+ resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
+
compute-scroll-into-view@3.1.1:
resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==}
@@ -1765,6 +1813,9 @@ packages:
decimal.js@10.6.0:
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+ decode-named-character-reference@1.3.0:
+ resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==}
+
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -1772,10 +1823,17 @@ packages:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
detect-libc@2.0.4:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
+ devlop@1.1.0:
+ resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+
enhanced-resolve@5.18.3:
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
engines: {node: '>=10.13.0'}
@@ -1858,10 +1916,16 @@ packages:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
+ estree-util-is-identifier-name@3.0.0:
+ resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==}
+
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
+ extend@3.0.2:
+ resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -1958,6 +2022,15 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
+ hast-util-to-jsx-runtime@2.3.6:
+ resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
+
+ hast-util-whitespace@3.0.0:
+ resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+
+ html-url-attributes@3.0.1:
+ resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
+
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -1974,12 +2047,24 @@ packages:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
+ inline-style-parser@0.2.7:
+ resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==}
+
intl-messageformat@10.7.16:
resolution: {integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==}
+ is-alphabetical@2.0.1:
+ resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
+
+ is-alphanumerical@2.0.1:
+ resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
+
is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
+ is-decimal@2.0.1:
+ resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
+
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@@ -1988,10 +2073,17 @@ packages:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
+ is-hexadecimal@2.0.1:
+ resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
+
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
+ is-plain-obj@4.1.0:
+ resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
+ engines: {node: '>=12'}
+
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -2090,13 +2182,103 @@ packages:
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+ longest-streak@3.1.0:
+ resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
+
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
+ mdast-util-from-markdown@2.0.3:
+ resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==}
+
+ mdast-util-mdx-expression@2.0.1:
+ resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
+
+ mdast-util-mdx-jsx@3.2.0:
+ resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==}
+
+ mdast-util-mdxjs-esm@2.0.1:
+ resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==}
+
+ mdast-util-phrasing@4.1.0:
+ resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
+
+ mdast-util-to-hast@13.2.1:
+ resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==}
+
+ mdast-util-to-markdown@2.1.2:
+ resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==}
+
+ mdast-util-to-string@4.0.0:
+ resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
+
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
+ micromark-core-commonmark@2.0.3:
+ resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
+
+ micromark-factory-destination@2.0.1:
+ resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
+
+ micromark-factory-label@2.0.1:
+ resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==}
+
+ micromark-factory-space@2.0.1:
+ resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==}
+
+ micromark-factory-title@2.0.1:
+ resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==}
+
+ micromark-factory-whitespace@2.0.1:
+ resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==}
+
+ micromark-util-character@2.1.1:
+ resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
+
+ micromark-util-chunked@2.0.1:
+ resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==}
+
+ micromark-util-classify-character@2.0.1:
+ resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==}
+
+ micromark-util-combine-extensions@2.0.1:
+ resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==}
+
+ micromark-util-decode-numeric-character-reference@2.0.2:
+ resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==}
+
+ micromark-util-decode-string@2.0.1:
+ resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==}
+
+ micromark-util-encode@2.0.1:
+ resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
+
+ micromark-util-html-tag-name@2.0.1:
+ resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==}
+
+ micromark-util-normalize-identifier@2.0.1:
+ resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==}
+
+ micromark-util-resolve-all@2.0.1:
+ resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==}
+
+ micromark-util-sanitize-uri@2.0.1:
+ resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}
+
+ micromark-util-subtokenize@2.1.0:
+ resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==}
+
+ micromark-util-symbol@2.0.1:
+ resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}
+
+ micromark-util-types@2.0.2:
+ resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}
+
+ micromark@4.0.2:
+ resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==}
+
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
@@ -2154,6 +2336,9 @@ packages:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
+ parse-entities@4.0.2:
+ resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
+
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -2251,6 +2436,9 @@ packages:
engines: {node: '>=14'}
hasBin: true
+ property-information@7.1.0:
+ resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -2269,6 +2457,12 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
+ react-markdown@10.1.0:
+ resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==}
+ peerDependencies:
+ '@types/react': '>=18'
+ react: '>=18'
+
react-router-dom@7.9.3:
resolution: {integrity: sha512-1QSbA0TGGFKTAc/aWjpfW/zoEukYfU4dc1dLkT/vvf54JoGMkW+fNA+3oyo2gWVW1GM7BxjJVHz5GnPJv40rvg==}
engines: {node: '>=20.0.0'}
@@ -2296,6 +2490,12 @@ packages:
resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
engines: {node: '>=0.10.0'}
+ remark-parse@11.0.0:
+ resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
+
+ remark-rehype@11.1.2:
+ resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
+
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -2341,10 +2541,22 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
+ space-separated-tokens@2.0.2:
+ resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+
+ stringify-entities@4.0.4:
+ resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
+
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ style-to-js@1.1.21:
+ resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==}
+
+ style-to-object@1.0.14:
+ resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==}
+
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
@@ -2385,6 +2597,12 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
+ trim-lines@3.0.1:
+ resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+
+ trough@2.2.0:
+ resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
+
ts-api-utils@2.1.0:
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
engines: {node: '>=18.12'}
@@ -2410,6 +2628,24 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
+ unified@11.0.5:
+ resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
+
+ unist-util-is@6.0.1:
+ resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
+
+ unist-util-position@5.0.0:
+ resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
+
+ unist-util-stringify-position@4.0.0:
+ resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
+
+ unist-util-visit-parents@6.0.2:
+ resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==}
+
+ unist-util-visit@5.1.0:
+ resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==}
+
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@@ -2445,6 +2681,12 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ vfile-message@4.0.3:
+ resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
+
+ vfile@6.0.3:
+ resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
+
vite@7.1.5:
resolution: {integrity: sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -2523,6 +2765,9 @@ packages:
use-sync-external-store:
optional: true
+ zwitch@2.0.4:
+ resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+
snapshots:
'@ampproject/remapping@2.3.0':
@@ -4348,10 +4593,28 @@ snapshots:
'@tanstack/virtual-core@3.11.3': {}
+ '@types/debug@4.1.12':
+ dependencies:
+ '@types/ms': 2.1.0
+
+ '@types/estree-jsx@1.0.5':
+ dependencies:
+ '@types/estree': 1.0.8
+
'@types/estree@1.0.8': {}
+ '@types/hast@3.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
'@types/json-schema@7.0.15': {}
+ '@types/mdast@4.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
+ '@types/ms@2.1.0': {}
+
'@types/react-dom@19.1.7(@types/react@19.1.9)':
dependencies:
'@types/react': 19.1.9
@@ -4362,6 +4625,10 @@ snapshots:
'@types/semver@7.7.1': {}
+ '@types/unist@2.0.11': {}
+
+ '@types/unist@3.0.3': {}
+
'@types/w3c-web-usb@1.0.12': {}
'@typescript-eslint/eslint-plugin@8.39.0(@typescript-eslint/parser@8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)':
@@ -4457,6 +4724,8 @@ snapshots:
'@typescript-eslint/types': 8.39.0
eslint-visitor-keys: 4.2.1
+ '@ungap/structured-clone@1.3.0': {}
+
'@vitejs/plugin-react-swc@3.11.0(@swc/helpers@0.5.17)(vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1))':
dependencies:
'@rolldown/pluginutils': 1.0.0-beta.27
@@ -4484,6 +4753,8 @@ snapshots:
argparse@2.0.1: {}
+ bail@2.0.2: {}
+
balanced-match@1.0.2: {}
brace-expansion@1.1.12:
@@ -4501,11 +4772,21 @@ snapshots:
callsites@3.1.0: {}
+ ccount@2.0.1: {}
+
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
+ character-entities-html4@2.1.0: {}
+
+ character-entities-legacy@3.0.0: {}
+
+ character-entities@2.0.2: {}
+
+ character-reference-invalid@2.0.1: {}
+
chownr@3.0.0: {}
classnames@2.5.1: {}
@@ -4532,6 +4813,8 @@ snapshots:
color-convert: 2.0.1
color-string: 1.9.1
+ comma-separated-tokens@2.0.3: {}
+
compute-scroll-into-view@3.1.1: {}
concat-map@0.0.1: {}
@@ -4552,12 +4835,22 @@ snapshots:
decimal.js@10.6.0: {}
+ decode-named-character-reference@1.3.0:
+ dependencies:
+ character-entities: 2.0.2
+
deep-is@0.1.4: {}
deepmerge@4.3.1: {}
+ dequal@2.0.3: {}
+
detect-libc@2.0.4: {}
+ devlop@1.1.0:
+ dependencies:
+ dequal: 2.0.3
+
enhanced-resolve@5.18.3:
dependencies:
graceful-fs: 4.2.11
@@ -4682,8 +4975,12 @@ snapshots:
estraverse@5.3.0: {}
+ estree-util-is-identifier-name@3.0.0: {}
+
esutils@2.0.3: {}
+ extend@3.0.2: {}
+
fast-deep-equal@3.1.3: {}
fast-diff@1.3.0: {}
@@ -4760,6 +5057,32 @@ snapshots:
has-flag@4.0.0: {}
+ hast-util-to-jsx-runtime@2.3.6:
+ dependencies:
+ '@types/estree': 1.0.8
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ comma-separated-tokens: 2.0.3
+ devlop: 1.1.0
+ estree-util-is-identifier-name: 3.0.0
+ hast-util-whitespace: 3.0.0
+ mdast-util-mdx-expression: 2.0.1
+ mdast-util-mdx-jsx: 3.2.0
+ mdast-util-mdxjs-esm: 2.0.1
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+ style-to-js: 1.1.21
+ unist-util-position: 5.0.0
+ vfile-message: 4.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ hast-util-whitespace@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ html-url-attributes@3.0.1: {}
+
ignore@5.3.2: {}
ignore@7.0.5: {}
@@ -4771,6 +5094,8 @@ snapshots:
imurmurhash@0.1.4: {}
+ inline-style-parser@0.2.7: {}
+
intl-messageformat@10.7.16:
dependencies:
'@formatjs/ecma402-abstract': 2.3.4
@@ -4778,16 +5103,29 @@ snapshots:
'@formatjs/icu-messageformat-parser': 2.11.2
tslib: 2.8.1
+ is-alphabetical@2.0.1: {}
+
+ is-alphanumerical@2.0.1:
+ dependencies:
+ is-alphabetical: 2.0.1
+ is-decimal: 2.0.1
+
is-arrayish@0.3.2: {}
+ is-decimal@2.0.1: {}
+
is-extglob@2.1.1: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
+ is-hexadecimal@2.0.1: {}
+
is-number@7.0.0: {}
+ is-plain-obj@4.1.0: {}
+
isexe@2.0.0: {}
jiti@2.5.1: {}
@@ -4862,12 +5200,236 @@ snapshots:
lodash.merge@4.6.2: {}
+ longest-streak@3.1.0: {}
+
magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.4
+ mdast-util-from-markdown@2.0.3:
+ dependencies:
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ decode-named-character-reference: 1.3.0
+ devlop: 1.1.0
+ mdast-util-to-string: 4.0.0
+ micromark: 4.0.2
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-decode-string: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+ unist-util-stringify-position: 4.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdx-expression@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.3
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdx-jsx@3.2.0:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ ccount: 2.0.1
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.3
+ mdast-util-to-markdown: 2.1.2
+ parse-entities: 4.0.2
+ stringify-entities: 4.0.4
+ unist-util-stringify-position: 4.0.0
+ vfile-message: 4.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdxjs-esm@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.3
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-phrasing@4.1.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ unist-util-is: 6.0.1
+
+ mdast-util-to-hast@13.2.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@ungap/structured-clone': 1.3.0
+ devlop: 1.1.0
+ micromark-util-sanitize-uri: 2.0.1
+ trim-lines: 3.0.1
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.1.0
+ vfile: 6.0.3
+
+ mdast-util-to-markdown@2.1.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ longest-streak: 3.1.0
+ mdast-util-phrasing: 4.1.0
+ mdast-util-to-string: 4.0.0
+ micromark-util-classify-character: 2.0.1
+ micromark-util-decode-string: 2.0.1
+ unist-util-visit: 5.1.0
+ zwitch: 2.0.4
+
+ mdast-util-to-string@4.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+
merge2@1.4.1: {}
+ micromark-core-commonmark@2.0.3:
+ dependencies:
+ decode-named-character-reference: 1.3.0
+ devlop: 1.1.0
+ micromark-factory-destination: 2.0.1
+ micromark-factory-label: 2.0.1
+ micromark-factory-space: 2.0.1
+ micromark-factory-title: 2.0.1
+ micromark-factory-whitespace: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-chunked: 2.0.1
+ micromark-util-classify-character: 2.0.1
+ micromark-util-html-tag-name: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-subtokenize: 2.1.0
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-destination@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-label@2.0.1:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-space@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-title@2.0.1:
+ dependencies:
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-whitespace@2.0.1:
+ dependencies:
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-character@2.1.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-chunked@2.0.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-classify-character@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-combine-extensions@2.0.1:
+ dependencies:
+ micromark-util-chunked: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-decode-numeric-character-reference@2.0.2:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-decode-string@2.0.1:
+ dependencies:
+ decode-named-character-reference: 1.3.0
+ micromark-util-character: 2.1.1
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-encode@2.0.1: {}
+
+ micromark-util-html-tag-name@2.0.1: {}
+
+ micromark-util-normalize-identifier@2.0.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-resolve-all@2.0.1:
+ dependencies:
+ micromark-util-types: 2.0.2
+
+ micromark-util-sanitize-uri@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-encode: 2.0.1
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-subtokenize@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-chunked: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-symbol@2.0.1: {}
+
+ micromark-util-types@2.0.2: {}
+
+ micromark@4.0.2:
+ dependencies:
+ '@types/debug': 4.1.12
+ debug: 4.4.1
+ decode-named-character-reference: 1.3.0
+ devlop: 1.1.0
+ micromark-core-commonmark: 2.0.3
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-chunked: 2.0.1
+ micromark-util-combine-extensions: 2.0.1
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-encode: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-subtokenize: 2.1.0
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
micromatch@4.0.8:
dependencies:
braces: 3.0.3
@@ -4922,6 +5484,16 @@ snapshots:
dependencies:
callsites: 3.1.0
+ parse-entities@4.0.2:
+ dependencies:
+ '@types/unist': 2.0.11
+ character-entities-legacy: 3.0.0
+ character-reference-invalid: 2.0.1
+ decode-named-character-reference: 1.3.0
+ is-alphanumerical: 2.0.1
+ is-decimal: 2.0.1
+ is-hexadecimal: 2.0.1
+
path-exists@4.0.0: {}
path-key@3.1.1: {}
@@ -4950,6 +5522,8 @@ snapshots:
prettier@3.6.2: {}
+ property-information@7.1.0: {}
+
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
@@ -4963,6 +5537,24 @@ snapshots:
dependencies:
react: 19.1.1
+ react-markdown@10.1.0(@types/react@19.1.9)(react@19.1.1):
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@types/react': 19.1.9
+ devlop: 1.1.0
+ hast-util-to-jsx-runtime: 2.3.6
+ html-url-attributes: 3.0.1
+ mdast-util-to-hast: 13.2.1
+ react: 19.1.1
+ remark-parse: 11.0.0
+ remark-rehype: 11.1.2
+ unified: 11.0.5
+ unist-util-visit: 5.1.0
+ vfile: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
react-router-dom@7.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
react: 19.1.1
@@ -4988,6 +5580,23 @@ snapshots:
react@19.1.1: {}
+ remark-parse@11.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-from-markdown: 2.0.3
+ micromark-util-types: 2.0.2
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-rehype@11.1.2:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ mdast-util-to-hast: 13.2.1
+ unified: 11.0.5
+ vfile: 6.0.3
+
resolve-from@4.0.0: {}
reusify@1.1.0: {}
@@ -5046,8 +5655,23 @@ snapshots:
source-map-js@1.2.1: {}
+ space-separated-tokens@2.0.2: {}
+
+ stringify-entities@4.0.4:
+ dependencies:
+ character-entities-html4: 2.1.0
+ character-entities-legacy: 3.0.0
+
strip-json-comments@3.1.1: {}
+ style-to-js@1.1.21:
+ dependencies:
+ style-to-object: 1.0.14
+
+ style-to-object@1.0.14:
+ dependencies:
+ inline-style-parser: 0.2.7
+
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
@@ -5086,6 +5710,10 @@ snapshots:
dependencies:
is-number: 7.0.0
+ trim-lines@3.0.1: {}
+
+ trough@2.2.0: {}
+
ts-api-utils@2.1.0(typescript@5.8.3):
dependencies:
typescript: 5.8.3
@@ -5109,6 +5737,39 @@ snapshots:
typescript@5.8.3: {}
+ unified@11.0.5:
+ dependencies:
+ '@types/unist': 3.0.3
+ bail: 2.0.2
+ devlop: 1.1.0
+ extend: 3.0.2
+ is-plain-obj: 4.1.0
+ trough: 2.2.0
+ vfile: 6.0.3
+
+ unist-util-is@6.0.1:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-position@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-stringify-position@4.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-visit-parents@6.0.2:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.1
+
+ unist-util-visit@5.1.0:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.1
+ unist-util-visit-parents: 6.0.2
+
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
@@ -5136,6 +5797,16 @@ snapshots:
dependencies:
react: 19.1.1
+ vfile-message@4.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-stringify-position: 4.0.0
+
+ vfile@6.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ vfile-message: 4.0.3
+
vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1):
dependencies:
esbuild: 0.25.10
@@ -5166,3 +5837,5 @@ snapshots:
'@types/react': 19.1.9
react: 19.1.1
use-sync-external-store: 1.5.0(react@19.1.1)
+
+ zwitch@2.0.4: {}
diff --git a/configurator/src/components/ManualTab.tsx b/configurator/src/components/ManualTab.tsx
index 8db25c93..77cc5d6f 100644
--- a/configurator/src/components/ManualTab.tsx
+++ b/configurator/src/components/ManualTab.tsx
@@ -902,9 +902,66 @@ export const ManualTab = () => {
+
+
+
+
Here you can configure the clock behavior:
Configure the internal quantizer used across all apps:
Refer to each app's manual to check if it uses the global quantizer.
-Here you can configure which MIDI data is transmitted to each MIDI output, essentially allowing Faderpunk to function as a MIDI router. You can also @@ -281,7 +281,7 @@ export const Configurator = () => ( the selected source are sent to this output. -
Faderpunk can operate as either a Leader or{" "} Follower on the I²C bus. @@ -289,7 +289,7 @@ export const Configurator = () => ( You can set this behavior in the Settings tab.
-Configure AUX jacks as clock outputs or{" "} reset outputs. @@ -310,7 +310,7 @@ export const Configurator = () => (
At the bottom of the Settings tab, you'll find controls for saving and recalling your Faderpunk setup. Keep in mind that scenes are currently{" "} diff --git a/configurator/src/components/manual/Interface.tsx b/configurator/src/components/manual/Interface.tsx index 04694abf..c580c4d0 100644 --- a/configurator/src/components/manual/Interface.tsx +++ b/configurator/src/components/manual/Interface.tsx @@ -12,7 +12,7 @@ import { H2, H3, List } from "./Shared"; export const Interface = () => ( <>
You can adjust several global settings on the Faderpunk by holding the{" "} Scene button and moving specific faders: @@ -330,7 +330,7 @@ export const Interface = () => ( -
Faderpunk features a set of connectors on the rear panel, designed to support power, communication, and integration with other gear. The ability @@ -400,7 +400,7 @@ export const Interface = () => ( -
On the back of the Faderpunk PCB, you'll find a set of user-accessible connectors designed to expand functionality and integration: @@ -441,7 +441,7 @@ export const Interface = () => ( -
{app.text}
+
+
+ {children}
+
+ ),
+ ul: ({ children }) => (
+
Date: Sun, 1 Mar 2026 22:42:21 +0200
Subject: [PATCH 03/22] fix(faderpunk): add public tick counter, reset delay
(#449)
* fix(clock): add public tick counter, reset delay
* feat(clock): implement public tick counter in apps
* fix: use is_multiple_of where possible
---------
Co-authored-by: ArthurGibert
---
faderpunk/src/app.rs | 24 ++++++++++++++----------
faderpunk/src/apps/clk_div.rs | 7 ++-----
faderpunk/src/apps/euclid.rs | 8 ++++----
faderpunk/src/apps/notefader.rs | 10 ++++------
faderpunk/src/apps/probatrigger.rs | 19 ++++++++++++-------
faderpunk/src/apps/rndcvcc.rs | 18 +++++++++---------
faderpunk/src/apps/seq8.rs | 14 +++-----------
faderpunk/src/apps/turing.rs | 8 +++-----
faderpunk/src/tasks/clock.rs | 16 ++++++++++++----
9 files changed, 63 insertions(+), 61 deletions(-)
diff --git a/faderpunk/src/app.rs b/faderpunk/src/app.rs
index 7954e036..14cb0cf4 100644
--- a/faderpunk/src/app.rs
+++ b/faderpunk/src/app.rs
@@ -22,7 +22,7 @@ use crate::{
events::{EventPubSubChannel, InputEvent},
tasks::{
buttons::{is_channel_button_pressed, is_shift_button_pressed},
- clock::{ClockSubscriber, CLOCK_PUBSUB},
+ clock::{ClockSubscriber, CLOCK_PUBSUB, TICK_COUNTER},
global_config::get_global_config,
i2c::{I2cLeaderMessage, I2cLeaderSender, I2C_CONNECTED},
leds::{set_led_mode, LedMode, LedMsg},
@@ -299,25 +299,20 @@ impl Faders<1> {
pub struct Clock {
subscriber: ClockSubscriber,
- tick_count: u16,
}
impl Clock {
pub fn new() -> Self {
let subscriber = CLOCK_PUBSUB.subscriber().unwrap();
- Self {
- subscriber,
- tick_count: 0,
- }
+ Self { subscriber }
}
pub async fn wait_for_event(&mut self, division: ClockDivision) -> ClockEvent {
loop {
match self.subscriber.next_message_pure().await {
ClockEvent::Tick => {
- self.tick_count += 1;
- if self.tick_count >= division as u16 {
- self.tick_count = 0;
+ let ticks = TICK_COUNTER.load(Ordering::Relaxed);
+ if ticks.is_multiple_of(division as u64) {
return ClockEvent::Tick;
}
}
@@ -325,12 +320,21 @@ impl Clock {
return ClockEvent::Stop;
}
clock_event @ ClockEvent::Start | clock_event @ ClockEvent::Reset => {
- self.tick_count = 0;
return clock_event;
}
}
}
}
+
+ #[allow(dead_code)]
+ pub fn get_ticker(&self) -> fn() -> u64 {
+ ticks
+ }
+}
+
+#[allow(dead_code)]
+fn ticks() -> u64 {
+ TICK_COUNTER.load(Ordering::Relaxed)
}
pub enum SceneEvent {
diff --git a/faderpunk/src/apps/clk_div.rs b/faderpunk/src/apps/clk_div.rs
index 5b56f655..27daa524 100644
--- a/faderpunk/src/apps/clk_div.rs
+++ b/faderpunk/src/apps/clk_div.rs
@@ -146,6 +146,7 @@ pub async fn run(
params.query(|p| (p.midi_out, p.midi_channel, p.note, p.gatel as u32, p.color));
let mut clock = app.use_clock();
+ let ticks = clock.get_ticker();
let fader = app.use_faders();
let buttons = app.use_buttons();
let leds = app.use_leds();
@@ -162,8 +163,6 @@ pub async fn run(
let resolution = [384, 192, 96, 48, 24, 16, 12, 8, 6, 4, 3, 2];
- let mut clkn: u32 = 0;
-
let (res, mute, min, max) =
storage.query(|s| (s.fader_saved, s.mute_saved, s.min_div, s.max_div));
@@ -186,7 +185,6 @@ pub async fn run(
loop {
match clock.wait_for_event(ClockDivision::_1).await {
ClockEvent::Reset => {
- clkn = 0;
midi.send_note_off(note).await;
note_on = false;
jack.set_low().await;
@@ -199,6 +197,7 @@ pub async fn run(
ClockEvent::Tick => {
let muted = glob_muted.get();
let div = div_glob.get();
+ let clkn = ticks() as u32;
if clkn.is_multiple_of(div) && !muted {
jack.set_high().await;
@@ -240,8 +239,6 @@ pub async fn run(
leds.set(0, Led::Bottom, Color::Red, LED_BRIGHTNESS);
}
}
-
- clkn += 1;
}
_ => {}
}
diff --git a/faderpunk/src/apps/euclid.rs b/faderpunk/src/apps/euclid.rs
index 43d3b264..19ad2a46 100644
--- a/faderpunk/src/apps/euclid.rs
+++ b/faderpunk/src/apps/euclid.rs
@@ -157,6 +157,7 @@ pub async fn run(
storage: &ManagedStorage,
) {
let mut clock = app.use_clock();
+ let ticks = clock.get_ticker();
let die = app.use_die();
let faders = app.use_faders();
let buttons = app.use_buttons();
@@ -190,8 +191,6 @@ pub async fn run(
let resolution = [384, 192, 96, 48, 24, 16, 12, 8, 6, 4, 3, 2];
- let mut clkn: u32 = 0;
-
let (fader_saved, shift_fader_saved, mute) =
storage.query(|s| (s.fader_saved, s.shift_fader_saved, s.mute_saved));
@@ -212,11 +211,12 @@ pub async fn run(
let fut1 = async {
let mut note_on = false;
let mut aux_on = false;
+ let mut tick_origin = ticks() as u32;
loop {
match clock.wait_for_event(ClockDivision::_1).await {
ClockEvent::Reset => {
- clkn = 0;
+ tick_origin = ticks() as u32;
midi.send_note_off(note).await;
midi.send_note_off(note2).await;
note_on = false;
@@ -233,6 +233,7 @@ pub async fn run(
jack[1].set_low().await;
}
ClockEvent::Tick => {
+ let clkn = (ticks() as u32).wrapping_sub(tick_origin);
let muted = glob_muted.get();
let div = div_glob.get();
@@ -314,7 +315,6 @@ pub async fn run(
),
);
}
- clkn += 1;
}
_ => {}
}
diff --git a/faderpunk/src/apps/notefader.rs b/faderpunk/src/apps/notefader.rs
index d656a6ab..be74889d 100644
--- a/faderpunk/src/apps/notefader.rs
+++ b/faderpunk/src/apps/notefader.rs
@@ -165,7 +165,7 @@ pub async fn run(
(
p.midi_out,
p.midi_channel,
- p.gatel,
+ p.gatel as u32,
p.midi_note,
p.span,
p.outmode,
@@ -174,6 +174,7 @@ pub async fn run(
});
let mut clock = app.use_clock();
+ let ticks = clock.get_ticker();
let quantizer = app.use_quantizer(range);
let fader = app.use_faders();
@@ -191,8 +192,6 @@ pub async fn run(
let resolution = [384, 192, 96, 48, 24, 16, 12, 8, 6, 4, 3, 2];
- let mut clkn = 0;
-
let (res, mute) = storage.query(|s| (s.fader_saved, s.mute_saved));
glob_muted.set(mute);
@@ -230,7 +229,6 @@ pub async fn run(
loop {
match clock.wait_for_event(ClockDivision::_1).await {
ClockEvent::Reset => {
- clkn = 0;
midi.send_note_off(note).await;
note_on = false;
if outmode == 1 {
@@ -248,8 +246,9 @@ pub async fn run(
let muted = glob_muted.get();
let div = div_glob.get();
+ let clkn = ticks() as u32;
- if clkn % div == 0 && storage.query(|s| s.clocked) {
+ if clkn.is_multiple_of(div) && storage.query(|s| s.clocked) {
if !muted {
if note_on {
midi.send_note_off(note).await;
@@ -273,7 +272,6 @@ pub async fn run(
leds.set(0, Led::Bottom, led_color, Brightness::Off);
}
- clkn += 1;
}
_ => {}
}
diff --git a/faderpunk/src/apps/probatrigger.rs b/faderpunk/src/apps/probatrigger.rs
index 426f0e71..70156ad2 100644
--- a/faderpunk/src/apps/probatrigger.rs
+++ b/faderpunk/src/apps/probatrigger.rs
@@ -140,11 +140,19 @@ pub async fn run(
params: &ParamStore,
storage: &ManagedStorage,
) {
- let (midi_out, midi_chan, note, gatel, led_color) =
- params.query(|p| (p.midi_out, p.midi_channel, p.midi_note, p.gatel, p.color));
+ let (midi_out, midi_chan, note, gatel, led_color) = params.query(|p| {
+ (
+ p.midi_out,
+ p.midi_channel,
+ p.midi_note,
+ p.gatel as u32,
+ p.color,
+ )
+ });
let curve = Curve::Exponential;
let mut clock = app.use_clock();
+ let ticks = clock.get_ticker();
let die = app.use_die();
let fader = app.use_faders();
let buttons = app.use_buttons();
@@ -160,8 +168,6 @@ pub async fn run(
let resolution = [384, 192, 96, 48, 24, 16, 12, 8, 6, 4, 3, 2];
- let mut clkn = 0;
-
let mut rndval = die.roll();
let (res, mute) = storage.query(|s| (s.fader_saved, s.mute_saved));
@@ -182,7 +188,6 @@ pub async fn run(
loop {
match clock.wait_for_event(ClockDivision::_1).await {
ClockEvent::Reset => {
- clkn = 0;
midi.send_note_off(note).await;
note_on = false;
jack.set_low().await;
@@ -196,8 +201,9 @@ pub async fn run(
let muted = glob_muted.get();
let val = storage.query(|s| s.prob_saved);
let div = div_glob.get();
+ let clkn = ticks() as u32;
- if clkn % div == 0 {
+ if clkn.is_multiple_of(div) {
if curve.at(val) >= rndval && !muted {
jack.set_high().await;
leds.set(0, Led::Top, led_color, LED_BRIGHTNESS);
@@ -223,7 +229,6 @@ pub async fn run(
leds.set(0, Led::Bottom, led_color, Brightness::Off);
}
- clkn += 1;
}
_ => {}
}
diff --git a/faderpunk/src/apps/rndcvcc.rs b/faderpunk/src/apps/rndcvcc.rs
index 948e2178..a8b2d5a8 100644
--- a/faderpunk/src/apps/rndcvcc.rs
+++ b/faderpunk/src/apps/rndcvcc.rs
@@ -134,6 +134,7 @@ pub async fn run(
params.query(|p| (p.bipolar, p.midi_out, p.midi_channel, p.midi_cc));
let mut clock = app.use_clock();
+ let ticks = clock.get_ticker();
let rnd = app.use_die();
let fader = app.use_faders();
let buttons = app.use_buttons();
@@ -157,8 +158,6 @@ pub async fn run(
let resolution = [384, 192, 96, 48, 24, 16, 12, 8, 6, 4, 3, 2];
- let mut clkn = 0;
-
let curve = Curve::Exponential;
let fader_curve = Curve::Exponential;
@@ -178,14 +177,13 @@ pub async fn run(
let fut1 = async {
loop {
match clock.wait_for_event(ClockDivision::_1).await {
- ClockEvent::Reset => {
- clkn = 0;
- }
+ ClockEvent::Reset => {}
ClockEvent::Tick => {
let muted = glob_muted.get();
-
+ let clkn = ticks() as u32;
let div = div_glob.get();
- if clkn % div == 0 && !muted && storage.query(|s: &Storage| s.clocked) {
+ if clkn.is_multiple_of(div) && !muted && storage.query(|s: &Storage| s.clocked)
+ {
val_glob.set(rnd.roll());
let color = if !glob_muted.get() {
@@ -202,7 +200,10 @@ pub async fn run(
leds.set(0, Led::Button, color, Brightness::Mid);
}
- if clkn % div == 0 && storage.query(|s: &Storage| s.clocked) && buttons.is_shift_pressed() {
+ if clkn.is_multiple_of(div)
+ && storage.query(|s: &Storage| s.clocked)
+ && buttons.is_shift_pressed()
+ {
leds.set(0, Led::Bottom, Color::Red, Brightness::High);
}
if clkn % div == (div * 50 / 100).clamp(1, div - 1)
@@ -210,7 +211,6 @@ pub async fn run(
{
leds.unset(0, Led::Bottom);
}
- clkn += 1;
}
_ => {}
}
diff --git a/faderpunk/src/apps/seq8.rs b/faderpunk/src/apps/seq8.rs
index 70a6eec3..eaf59d97 100644
--- a/faderpunk/src/apps/seq8.rs
+++ b/faderpunk/src/apps/seq8.rs
@@ -156,6 +156,7 @@ pub async fn run(
let buttons = app.use_buttons();
let faders = app.use_faders();
let mut clk = app.use_clock();
+ let ticks = clk.get_ticker();
let led = app.use_leds();
let midi = [
@@ -165,8 +166,6 @@ pub async fn run(
app.use_midi_output(midi_out, midi_chan4),
];
- let clockn_glob = app.make_global(0);
-
let cv_out = [
app.make_out_jack(0, Range::_0_10V).await,
app.make_out_jack(2, Range::_0_10V).await,
@@ -372,10 +371,9 @@ pub async fn run(
let colors = [Color::Yellow, Color::Pink, Color::Cyan, Color::White];
app.delay_millis(16).await;
let clockres = clockres_glob.get();
+ let clockn = ticks() as usize;
if buttons.is_shift_pressed() {
- let clockn = clockn_glob.get();
-
let seq_length = seq_length_glob.get();
let page = page_glob.get();
@@ -415,7 +413,6 @@ pub async fn run(
let seq_length = seq_length_glob.get();
let mut color = colors[0];
- let clockn = clockn_glob.get(); // this should go
if page / 2 == 0 {
color = colors[0];
@@ -494,11 +491,8 @@ pub async fn run(
let clockres = clockres_glob.get();
let legato_seq = legatoseq_glob.get();
- let mut clockn = clockn_glob.get();
-
match clk.wait_for_event(ClockDivision::_1).await {
ClockEvent::Reset => {
- clockn = 0;
for n in 0..4 {
midi[n].send_note_off(lastnote[n]).await;
gate_out[n].set_low().await;
@@ -511,6 +505,7 @@ pub async fn run(
}
}
ClockEvent::Tick => {
+ let clockn = ticks() as usize;
for n in 0..=3 {
if clockn.is_multiple_of(clockres[n]) {
let clkindex =
@@ -548,12 +543,9 @@ pub async fn run(
}
}
}
- clockn += 1;
}
_ => {}
}
-
- clockn_glob.set(clockn);
}
};
diff --git a/faderpunk/src/apps/turing.rs b/faderpunk/src/apps/turing.rs
index cb89d24a..e4956e20 100644
--- a/faderpunk/src/apps/turing.rs
+++ b/faderpunk/src/apps/turing.rs
@@ -171,7 +171,7 @@ pub async fn run(
p.color,
p.midi_channel,
p.midi_note,
- p.gatel,
+ p.gatel as u32,
p.range,
)
});
@@ -180,6 +180,7 @@ pub async fn run(
let fader = app.use_faders();
let leds = app.use_leds();
let mut clock = app.use_clock();
+ let ticks = clock.get_ticker();
let die = app.use_die();
let quantizer = app.use_quantizer(range);
@@ -207,7 +208,6 @@ pub async fn run(
div_glob.set(resolution[res as usize / 512]);
let fut1 = async {
- let mut clkn: usize = 0;
let mut att_reg: u16;
loop {
let div = div_glob.get();
@@ -215,13 +215,13 @@ pub async fn run(
match clock.wait_for_event(ClockDivision::_1).await {
ClockEvent::Reset => {
- clkn = 0;
if midi_mode == MidiMode::Note {
midi.send_note_off(midi_note.get()).await;
}
register = storage.query(|s| s.register_saved);
}
ClockEvent::Tick => {
+ let clkn = ticks() as usize;
if clkn.is_multiple_of(div) {
if (clkn / div).is_multiple_of(length as usize) {
let reg_old = storage.query(|s| s.register_saved);
@@ -281,8 +281,6 @@ pub async fn run(
midi.send_note_off(midi_note.get()).await;
}
}
-
- clkn += 1;
}
ClockEvent::Stop => {
if midi_mode == MidiMode::Note {
diff --git a/faderpunk/src/tasks/clock.rs b/faderpunk/src/tasks/clock.rs
index 2d1bbd41..e9be4ed4 100644
--- a/faderpunk/src/tasks/clock.rs
+++ b/faderpunk/src/tasks/clock.rs
@@ -12,9 +12,9 @@ use embassy_sync::{
channel::Channel,
pubsub::{PubSubChannel, Subscriber},
};
-use embassy_time::{Instant, Timer};
+use embassy_time::{Duration, Instant, Timer};
use midly::live::SystemRealtime;
-use portable_atomic::Ordering;
+use portable_atomic::{AtomicU64, Ordering};
use libfp::{
utils::bpm_to_clock_duration, AuxJackMode, ClockSrc, GlobalConfig, MidiOut, MidiOutConfig,
@@ -34,6 +34,10 @@ const CLOCK_PUBSUB_SIZE: usize = 16;
const CLOCK_PUBSUB_SUBSCRIBERS: usize = 16;
// 3 Ext clocks, internal clock, midi
const CLOCK_PUBSUB_PUBLISHERS: usize = 5;
+// Add a slight delay before the very first tick (to offset it to reset)
+const TICK_RESET_DELAY: u8 = 2;
+
+pub static TICK_COUNTER: AtomicU64 = AtomicU64::new(0);
type AuxInputs = (
Peri<'static, PIN_1>,
@@ -240,6 +244,7 @@ async fn run_clock_gatekeeper() {
if is_running
|| matches!(source, ClockSrc::Atom | ClockSrc::Meteor | ClockSrc::Cube)
{
+ TICK_COUNTER.fetch_add(1, Ordering::Relaxed);
clock_publisher.publish(ClockEvent::Tick).await;
send_analog_ticks(&spawner, &config, &mut analog_tick_counters).await;
midi_rt_event = Some(SystemRealtime::TimingClock);
@@ -253,6 +258,7 @@ async fn run_clock_gatekeeper() {
}
// (Re-)start the clock. Full phase reset
ClockInEvent::Start(_) => {
+ TICK_COUNTER.store(0, Ordering::Relaxed);
is_running = true;
clock_publisher.publish(ClockEvent::Reset).await;
clock_publisher.publish(ClockEvent::Start).await;
@@ -268,6 +274,7 @@ async fn run_clock_gatekeeper() {
}
// Reset the phase without affecting the run state
ClockInEvent::Reset(_) => {
+ TICK_COUNTER.store(0, Ordering::Relaxed);
clock_publisher.publish(ClockEvent::Reset).await;
analog_tick_counters = [0; 3];
send_analog_reset(&spawner, &config).await;
@@ -321,7 +328,7 @@ async fn run_clock_sources(aux_inputs: AuxInputs) {
let config = config_receiver.get().await;
let mut is_running = is_clock_running().await;
let mut tick_duration = bpm_to_clock_duration(config.clock.internal_bpm, INTERNAL_PPQN);
- let mut next_tick_at = Instant::now();
+ let mut next_tick_at = Instant::now() + Duration::from_millis(TICK_RESET_DELAY as u64);
if is_running {
// If we're starting up and the clock should already be running,
@@ -393,7 +400,8 @@ async fn run_clock_sources(aux_inputs: AuxInputs) {
if next_is_running {
// Schedule the first tick immediately. The main loop will
// handle publishing it and scheduling the subsequent tick.
- next_tick_at = Instant::now();
+ next_tick_at =
+ Instant::now() + Duration::from_millis(TICK_RESET_DELAY as u64);
clock_in_sender
.send(ClockInEvent::Start(ClockSrc::Internal))
.await;
From 73fc74e2c304a4b1622b48e6f4d877f4e222f025 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 2 Mar 2026 23:28:18 +0100
Subject: [PATCH 04/22] build(deps): bump keccak from 0.1.5 to 0.1.6 (#433)
Bumps [keccak](https://github.com/RustCrypto/sponges) from 0.1.5 to 0.1.6.
- [Commits](https://github.com/RustCrypto/sponges/compare/keccak-v0.1.5...keccak-v0.1.6)
---
updated-dependencies:
- dependency-name: keccak
dependency-version: 0.1.6
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
Cargo.lock | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 3c601867..649ef53b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -934,9 +934,9 @@ dependencies = [
[[package]]
name = "keccak"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
+checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
dependencies = [
"cpufeatures",
]
From c8cb798ba0dfa8f6eace305894f5554e00a7c62a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 2 Mar 2026 23:32:02 +0100
Subject: [PATCH 05/22] build(deps): bump actions/upload-artifact from 4 to 6
(#428)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v6)
---
updated-dependencies:
- dependency-name: actions/upload-artifact
dependency-version: '6'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/beta.yml | 10 +++++-----
.github/workflows/picotool.yml | 2 +-
.github/workflows/release.yml | 12 ++++++------
3 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml
index 0273da5b..d643364e 100644
--- a/.github/workflows/beta.yml
+++ b/.github/workflows/beta.yml
@@ -97,7 +97,7 @@ jobs:
cmake ..
make
- name: Upload picotool artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: picotool-artifact
path: picotool/build/picotool
@@ -121,7 +121,7 @@ jobs:
- run: cargo install flip-link
- run: cargo build --bin faderpunk --release --target thumbv8m.main-none-eabihf
- name: Upload firmware build artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: firmware-artifact
path: target/thumbv8m.main-none-eabihf/release/faderpunk
@@ -148,7 +148,7 @@ jobs:
VERSION="v${{ needs.prepare-beta.outputs.faderpunk_version }}"
picotool_executable/picotool uf2 convert artifacts/faderpunk.elf artifacts/faderpunk-${VERSION}.uf2
- name: Upload release artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: release-artifacts-firmware
path: artifacts
@@ -189,12 +189,12 @@ jobs:
cd configurator/dist
zip -r ../../artifacts/configurator.zip .
- name: Upload configurator release artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: release-artifacts-configurator
path: artifacts
- name: Upload configurator artifact for GitHub Pages
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: configurator-pages-artifact
path: configurator/dist
diff --git a/.github/workflows/picotool.yml b/.github/workflows/picotool.yml
index cb758c7b..8f65bab9 100644
--- a/.github/workflows/picotool.yml
+++ b/.github/workflows/picotool.yml
@@ -49,7 +49,7 @@ jobs:
# Upload the picotool binary as an artifact
- name: Upload picotool artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: picotool
path: picotool/build/picotool
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index fb340efd..c7e32412 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -95,7 +95,7 @@ jobs:
cmake ..
make
- name: Upload picotool artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: picotool-artifact
path: picotool/build/picotool
@@ -119,7 +119,7 @@ jobs:
- run: cargo install flip-link
- run: cargo build --bin faderpunk --release --target thumbv8m.main-none-eabihf
- name: Upload firmware build artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: firmware-artifact
path: target/thumbv8m.main-none-eabihf/release/faderpunk
@@ -148,7 +148,7 @@ jobs:
VERSION="v${{ needs.detect-changes.outputs.faderpunk_version }}"
picotool_executable/picotool uf2 convert artifacts/faderpunk.elf artifacts/faderpunk-${VERSION}.uf2
- name: Upload release artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: release-artifacts-firmware
path: artifacts
@@ -188,7 +188,7 @@ jobs:
cd configurator/dist
zip -r ../../artifacts/configurator.zip .
- name: Upload configurator release artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: release-artifacts-configurator
path: artifacts
@@ -196,12 +196,12 @@ jobs:
run: pnpm run build:landing
working-directory: ./configurator
- name: Upload configurator artifact for GitHub Pages
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: configurator-pages-artifact
path: configurator/dist
- name: Upload landing page artifact for GitHub Pages
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: landing-pages-artifact
path: configurator/dist-landing
From bec92a9d66bbb064231beec17241ff6881023eea Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 2 Mar 2026 23:36:16 +0100
Subject: [PATCH 06/22] build(deps): bump actions/download-artifact from 5 to 7
(#427)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v5...v7)
---
updated-dependencies:
- dependency-name: actions/download-artifact
dependency-version: '7'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/beta.yml | 10 +++++-----
.github/workflows/release.yml | 12 ++++++------
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml
index d643364e..b5be4c27 100644
--- a/.github/workflows/beta.yml
+++ b/.github/workflows/beta.yml
@@ -131,12 +131,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download picotool artifact
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v8
with:
name: picotool-artifact
path: picotool_executable
- name: Download firmware artifact
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v8
with:
name: firmware-artifact
path: firmware_build
@@ -208,12 +208,12 @@ jobs:
ref: ${{ needs.prepare-beta.outputs.sha }}
fetch-depth: 0
- name: Download firmware artifacts
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v8
with:
name: release-artifacts-firmware
path: artifacts
- name: Download configurator artifacts
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v8
with:
name: release-artifacts-configurator
path: artifacts
@@ -239,7 +239,7 @@ jobs:
ref: gh-pages
path: gh-pages
- name: Download configurator artifact
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v8
with:
name: configurator-pages-artifact
path: configurator-dist
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c7e32412..72abe216 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -131,12 +131,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download picotool artifact
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v8
with:
name: picotool-artifact
path: picotool_executable
- name: Download firmware artifact
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v8
with:
name: firmware-artifact
path: firmware_build
@@ -217,13 +217,13 @@ jobs:
fetch-depth: 0
- name: Download firmware artifacts
if: needs.package-firmware.result == 'success'
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v8
with:
name: release-artifacts-firmware
path: artifacts
- name: Download configurator artifacts
if: needs.build-configurator.result == 'success'
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v8
with:
name: release-artifacts-configurator
path: artifacts
@@ -250,12 +250,12 @@ jobs:
ref: gh-pages
path: gh-pages
- name: Download configurator artifact
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v8
with:
name: configurator-pages-artifact
path: configurator-dist
- name: Download landing page artifact
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v8
with:
name: landing-pages-artifact
path: landing-dist
From 877e0d664b962ce0b51ccc1e3b745600bab53f3d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 2 Mar 2026 23:40:19 +0100
Subject: [PATCH 07/22] build(deps): bump actions/cache from 4 to 5 (#426)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)
---
updated-dependencies:
- dependency-name: actions/cache
dependency-version: '5'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/beta.yml | 2 +-
.github/workflows/release.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml
index b5be4c27..66fc2e0a 100644
--- a/.github/workflows/beta.yml
+++ b/.github/workflows/beta.yml
@@ -67,7 +67,7 @@ jobs:
echo "picotool_hash=${picotool_hash}" >> $GITHUB_ENV
echo "pico_sdk_hash=${pico_sdk_hash}" >> $GITHUB_ENV
- name: Cache picotool build outputs
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: picotool/build
key: ${{ runner.os }}-picotool-${{ env.picotool_hash }}-${{ env.pico_sdk_hash }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 72abe216..5441e297 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -65,7 +65,7 @@ jobs:
echo "picotool_hash=${picotool_hash}" >> $GITHUB_ENV
echo "pico_sdk_hash=${pico_sdk_hash}" >> $GITHUB_ENV
- name: Cache picotool build outputs
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: picotool/build
key: ${{ runner.os }}-picotool-${{ env.picotool_hash }}-${{ env.pico_sdk_hash }}
From db2fa421a1106d308ecf9fd1a8e965353e0f818a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 2 Mar 2026 23:40:59 +0100
Subject: [PATCH 08/22] build(deps-dev): bump vite from 7.1.5 to 7.1.11 in
/configurator (#302)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.5 to 7.1.11.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.11/packages/vite)
---
updated-dependencies:
- dependency-name: vite
dependency-version: 7.1.11
dependency-type: direct:development
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
configurator/package.json | 2 +-
configurator/pnpm-lock.yaml | 445 +++++++++++++++++++-----------------
2 files changed, 235 insertions(+), 212 deletions(-)
diff --git a/configurator/package.json b/configurator/package.json
index 386e84c8..efe3c21b 100644
--- a/configurator/package.json
+++ b/configurator/package.json
@@ -62,7 +62,7 @@
"tailwindcss": "^4.1.11",
"typescript": "~5.8.3",
"typescript-eslint": "^8.39.0",
- "vite": "^7.1.5"
+ "vite": "^7.1.11"
},
"pnpm": {
"onlyBuiltDependencies": [
diff --git a/configurator/pnpm-lock.yaml b/configurator/pnpm-lock.yaml
index fa1ab4a5..670c8aba 100644
--- a/configurator/pnpm-lock.yaml
+++ b/configurator/pnpm-lock.yaml
@@ -104,7 +104,7 @@ importers:
version: 9.33.0
'@tailwindcss/vite':
specifier: ^4.1.11
- version: 4.1.11(vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1))
+ version: 4.1.11(vite@7.1.11(jiti@2.5.1)(lightningcss@1.30.1))
'@types/react':
specifier: ^19.1.9
version: 19.1.9
@@ -116,7 +116,7 @@ importers:
version: 7.7.1
'@vitejs/plugin-react-swc':
specifier: ^3.11.0
- version: 3.11.0(@swc/helpers@0.5.17)(vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1))
+ version: 3.11.0(@swc/helpers@0.5.17)(vite@7.1.11(jiti@2.5.1)(lightningcss@1.30.1))
eslint:
specifier: ^9.32.0
version: 9.33.0(jiti@2.5.1)
@@ -151,8 +151,8 @@ importers:
specifier: ^8.39.0
version: 8.39.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)
vite:
- specifier: ^7.1.5
- version: 7.1.5(jiti@2.5.1)(lightningcss@1.30.1)
+ specifier: ^7.1.11
+ version: 7.1.11(jiti@2.5.1)(lightningcss@1.30.1)
packages:
@@ -192,158 +192,158 @@ packages:
peerDependencies:
react: '>=16.8.0'
- '@esbuild/aix-ppc64@0.25.10':
- resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==}
+ '@esbuild/aix-ppc64@0.25.12':
+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
- '@esbuild/android-arm64@0.25.10':
- resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==}
+ '@esbuild/android-arm64@0.25.12':
+ resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
- '@esbuild/android-arm@0.25.10':
- resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==}
+ '@esbuild/android-arm@0.25.12':
+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
- '@esbuild/android-x64@0.25.10':
- resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==}
+ '@esbuild/android-x64@0.25.12':
+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
- '@esbuild/darwin-arm64@0.25.10':
- resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==}
+ '@esbuild/darwin-arm64@0.25.12':
+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
- '@esbuild/darwin-x64@0.25.10':
- resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==}
+ '@esbuild/darwin-x64@0.25.12':
+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
- '@esbuild/freebsd-arm64@0.25.10':
- resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==}
+ '@esbuild/freebsd-arm64@0.25.12':
+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
- '@esbuild/freebsd-x64@0.25.10':
- resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==}
+ '@esbuild/freebsd-x64@0.25.12':
+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
- '@esbuild/linux-arm64@0.25.10':
- resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==}
+ '@esbuild/linux-arm64@0.25.12':
+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
- '@esbuild/linux-arm@0.25.10':
- resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==}
+ '@esbuild/linux-arm@0.25.12':
+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
- '@esbuild/linux-ia32@0.25.10':
- resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==}
+ '@esbuild/linux-ia32@0.25.12':
+ resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
- '@esbuild/linux-loong64@0.25.10':
- resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==}
+ '@esbuild/linux-loong64@0.25.12':
+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
- '@esbuild/linux-mips64el@0.25.10':
- resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==}
+ '@esbuild/linux-mips64el@0.25.12':
+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
- '@esbuild/linux-ppc64@0.25.10':
- resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==}
+ '@esbuild/linux-ppc64@0.25.12':
+ resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
- '@esbuild/linux-riscv64@0.25.10':
- resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==}
+ '@esbuild/linux-riscv64@0.25.12':
+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
- '@esbuild/linux-s390x@0.25.10':
- resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==}
+ '@esbuild/linux-s390x@0.25.12':
+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
- '@esbuild/linux-x64@0.25.10':
- resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==}
+ '@esbuild/linux-x64@0.25.12':
+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
- '@esbuild/netbsd-arm64@0.25.10':
- resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==}
+ '@esbuild/netbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
- '@esbuild/netbsd-x64@0.25.10':
- resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==}
+ '@esbuild/netbsd-x64@0.25.12':
+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
- '@esbuild/openbsd-arm64@0.25.10':
- resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==}
+ '@esbuild/openbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
- '@esbuild/openbsd-x64@0.25.10':
- resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==}
+ '@esbuild/openbsd-x64@0.25.12':
+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
- '@esbuild/openharmony-arm64@0.25.10':
- resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==}
+ '@esbuild/openharmony-arm64@0.25.12':
+ resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
- '@esbuild/sunos-x64@0.25.10':
- resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==}
+ '@esbuild/sunos-x64@0.25.12':
+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
- '@esbuild/win32-arm64@0.25.10':
- resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==}
+ '@esbuild/win32-arm64@0.25.12':
+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
- '@esbuild/win32-ia32@0.25.10':
- resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==}
+ '@esbuild/win32-ia32@0.25.12':
+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
- '@esbuild/win32-x64@0.25.10':
- resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==}
+ '@esbuild/win32-x64@0.25.12':
+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
@@ -1295,113 +1295,124 @@ packages:
'@rolldown/pluginutils@1.0.0-beta.27':
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
- '@rollup/rollup-android-arm-eabi@4.52.0':
- resolution: {integrity: sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A==}
+ '@rollup/rollup-android-arm-eabi@4.53.1':
+ resolution: {integrity: sha512-bxZtughE4VNVJlL1RdoSE545kc4JxL7op57KKoi59/gwuU5rV6jLWFXXc8jwgFoT6vtj+ZjO+Z2C5nrY0Cl6wA==}
cpu: [arm]
os: [android]
- '@rollup/rollup-android-arm64@4.52.0':
- resolution: {integrity: sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ==}
+ '@rollup/rollup-android-arm64@4.53.1':
+ resolution: {integrity: sha512-44a1hreb02cAAfAKmZfXVercPFaDjqXCK+iKeVOlJ9ltvnO6QqsBHgKVPTu+MJHSLLeMEUbeG2qiDYgbFPU48g==}
cpu: [arm64]
os: [android]
- '@rollup/rollup-darwin-arm64@4.52.0':
- resolution: {integrity: sha512-YCdWlY/8ltN6H78HnMsRHYlPiKvqKagBP1r+D7SSylxX+HnsgXGCmLiV3Y4nSyY9hW8qr8U9LDUx/Lo7M6MfmQ==}
+ '@rollup/rollup-darwin-arm64@4.53.1':
+ resolution: {integrity: sha512-usmzIgD0rf1syoOZ2WZvy8YpXK5G1V3btm3QZddoGSa6mOgfXWkkv+642bfUUldomgrbiLQGrPryb7DXLovPWQ==}
cpu: [arm64]
os: [darwin]
- '@rollup/rollup-darwin-x64@4.52.0':
- resolution: {integrity: sha512-z4nw6y1j+OOSGzuVbSWdIp1IUks9qNw4dc7z7lWuWDKojY38VMWBlEN7F9jk5UXOkUcp97vA1N213DF+Lz8BRg==}
+ '@rollup/rollup-darwin-x64@4.53.1':
+ resolution: {integrity: sha512-is3r/k4vig2Gt8mKtTlzzyaSQ+hd87kDxiN3uDSDwggJLUV56Umli6OoL+/YZa/KvtdrdyNfMKHzL/P4siOOmg==}
cpu: [x64]
os: [darwin]
- '@rollup/rollup-freebsd-arm64@4.52.0':
- resolution: {integrity: sha512-Q/dv9Yvyr5rKlK8WQJZVrp5g2SOYeZUs9u/t2f9cQ2E0gJjYB/BWoedXfUT0EcDJefi2zzVfhcOj8drWCzTviw==}
+ '@rollup/rollup-freebsd-arm64@4.53.1':
+ resolution: {integrity: sha512-QJ1ksgp/bDJkZB4daldVmHaEQkG4r8PUXitCOC2WRmRaSaHx5RwPoI3DHVfXKwDkB+Sk6auFI/+JHacTekPRSw==}
cpu: [arm64]
os: [freebsd]
- '@rollup/rollup-freebsd-x64@4.52.0':
- resolution: {integrity: sha512-kdBsLs4Uile/fbjZVvCRcKB4q64R+1mUq0Yd7oU1CMm1Av336ajIFqNFovByipciuUQjBCPMxwJhCgfG2re3rg==}
+ '@rollup/rollup-freebsd-x64@4.53.1':
+ resolution: {integrity: sha512-J6ma5xgAzvqsnU6a0+jgGX/gvoGokqpkx6zY4cWizRrm0ffhHDpJKQgC8dtDb3+MqfZDIqs64REbfHDMzxLMqQ==}
cpu: [x64]
os: [freebsd]
- '@rollup/rollup-linux-arm-gnueabihf@4.52.0':
- resolution: {integrity: sha512-aL6hRwu0k7MTUESgkg7QHY6CoqPgr6gdQXRJI1/VbFlUMwsSzPGSR7sG5d+MCbYnJmJwThc2ol3nixj1fvI/zQ==}
+ '@rollup/rollup-linux-arm-gnueabihf@4.53.1':
+ resolution: {integrity: sha512-JzWRR41o2U3/KMNKRuZNsDUAcAVUYhsPuMlx5RUldw0E4lvSIXFUwejtYz1HJXohUmqs/M6BBJAUBzKXZVddbg==}
cpu: [arm]
os: [linux]
+ libc: [glibc]
- '@rollup/rollup-linux-arm-musleabihf@4.52.0':
- resolution: {integrity: sha512-BTs0M5s1EJejgIBJhCeiFo7GZZ2IXWkFGcyZhxX4+8usnIo5Mti57108vjXFIQmmJaRyDwmV59Tw64Ap1dkwMw==}
+ '@rollup/rollup-linux-arm-musleabihf@4.53.1':
+ resolution: {integrity: sha512-L8kRIrnfMrEoHLHtHn+4uYA52fiLDEDyezgxZtGUTiII/yb04Krq+vk3P2Try+Vya9LeCE9ZHU8CXD6J9EhzHQ==}
cpu: [arm]
os: [linux]
+ libc: [musl]
- '@rollup/rollup-linux-arm64-gnu@4.52.0':
- resolution: {integrity: sha512-uj672IVOU9m08DBGvoPKPi/J8jlVgjh12C9GmjjBxCTQc3XtVmRkRKyeHSmIKQpvJ7fIm1EJieBUcnGSzDVFyw==}
+ '@rollup/rollup-linux-arm64-gnu@4.53.1':
+ resolution: {integrity: sha512-ysAc0MFRV+WtQ8li8hi3EoFi7us6d1UzaS/+Dp7FYZfg3NdDljGMoVyiIp6Ucz7uhlYDBZ/zt6XI0YEZbUO11Q==}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
- '@rollup/rollup-linux-arm64-musl@4.52.0':
- resolution: {integrity: sha512-/+IVbeDMDCtB/HP/wiWsSzduD10SEGzIZX2945KSgZRNi4TSkjHqRJtNTVtVb8IRwhJ65ssI56krlLik+zFWkw==}
+ '@rollup/rollup-linux-arm64-musl@4.53.1':
+ resolution: {integrity: sha512-UV6l9MJpDbDZZ/fJvqNcvO1PcivGEf1AvKuTcHoLjVZVFeAMygnamCTDikCVMRnA+qJe+B3pSbgX2+lBMqgBhA==}
cpu: [arm64]
os: [linux]
+ libc: [musl]
- '@rollup/rollup-linux-loong64-gnu@4.52.0':
- resolution: {integrity: sha512-U1vVzvSWtSMWKKrGoROPBXMh3Vwn93TA9V35PldokHGqiUbF6erSzox/5qrSMKp6SzakvyjcPiVF8yB1xKr9Pg==}
+ '@rollup/rollup-linux-loong64-gnu@4.53.1':
+ resolution: {integrity: sha512-UDUtelEprkA85g95Q+nj3Xf0M4hHa4DiJ+3P3h4BuGliY4NReYYqwlc0Y8ICLjN4+uIgCEvaygYlpf0hUj90Yg==}
cpu: [loong64]
os: [linux]
+ libc: [glibc]
- '@rollup/rollup-linux-ppc64-gnu@4.52.0':
- resolution: {integrity: sha512-X/4WfuBAdQRH8cK3DYl8zC00XEE6aM472W+QCycpQJeLWVnHfkv7RyBFVaTqNUMsTgIX8ihMjCvFF9OUgeABzw==}
+ '@rollup/rollup-linux-ppc64-gnu@4.53.1':
+ resolution: {integrity: sha512-vrRn+BYhEtNOte/zbc2wAUQReJXxEx2URfTol6OEfY2zFEUK92pkFBSXRylDM7aHi+YqEPJt9/ABYzmcrS4SgQ==}
cpu: [ppc64]
os: [linux]
+ libc: [glibc]
- '@rollup/rollup-linux-riscv64-gnu@4.52.0':
- resolution: {integrity: sha512-xIRYc58HfWDBZoLmWfWXg2Sq8VCa2iJ32B7mqfWnkx5mekekl0tMe7FHpY8I72RXEcUkaWawRvl3qA55og+cwQ==}
+ '@rollup/rollup-linux-riscv64-gnu@4.53.1':
+ resolution: {integrity: sha512-gto/1CxHyi4A7YqZZNznQYrVlPSaodOBPKM+6xcDSCMVZN/Fzb4K+AIkNz/1yAYz9h3Ng+e2fY9H6bgawVq17w==}
cpu: [riscv64]
os: [linux]
+ libc: [glibc]
- '@rollup/rollup-linux-riscv64-musl@4.52.0':
- resolution: {integrity: sha512-mbsoUey05WJIOz8U1WzNdf+6UMYGwE3fZZnQqsM22FZ3wh1N887HT6jAOjXs6CNEK3Ntu2OBsyQDXfIjouI4dw==}
+ '@rollup/rollup-linux-riscv64-musl@4.53.1':
+ resolution: {integrity: sha512-KZ6Vx7jAw3aLNjFR8eYVcQVdFa/cvBzDNRFM3z7XhNNunWjA03eUrEwJYPk0G8V7Gs08IThFKcAPS4WY/ybIrQ==}
cpu: [riscv64]
os: [linux]
+ libc: [musl]
- '@rollup/rollup-linux-s390x-gnu@4.52.0':
- resolution: {integrity: sha512-qP6aP970bucEi5KKKR4AuPFd8aTx9EF6BvutvYxmZuWLJHmnq4LvBfp0U+yFDMGwJ+AIJEH5sIP+SNypauMWzg==}
+ '@rollup/rollup-linux-s390x-gnu@4.53.1':
+ resolution: {integrity: sha512-HvEixy2s/rWNgpwyKpXJcHmE7om1M89hxBTBi9Fs6zVuLU4gOrEMQNbNsN/tBVIMbLyysz/iwNiGtMOpLAOlvA==}
cpu: [s390x]
os: [linux]
+ libc: [glibc]
- '@rollup/rollup-linux-x64-gnu@4.52.0':
- resolution: {integrity: sha512-nmSVN+F2i1yKZ7rJNKO3G7ZzmxJgoQBQZ/6c4MuS553Grmr7WqR7LLDcYG53Z2m9409z3JLt4sCOhLdbKQ3HmA==}
+ '@rollup/rollup-linux-x64-gnu@4.53.1':
+ resolution: {integrity: sha512-E/n8x2MSjAQgjj9IixO4UeEUeqXLtiA7pyoXCFYLuXpBA/t2hnbIdxHfA7kK9BFsYAoNU4st1rHYdldl8dTqGA==}
cpu: [x64]
os: [linux]
+ libc: [glibc]
- '@rollup/rollup-linux-x64-musl@4.52.0':
- resolution: {integrity: sha512-2d0qRo33G6TfQVjaMR71P+yJVGODrt5V6+T0BDYH4EMfGgdC/2HWDVjSSFw888GSzAZUwuska3+zxNUCDco6rQ==}
+ '@rollup/rollup-linux-x64-musl@4.53.1':
+ resolution: {integrity: sha512-IhJ087PbLOQXCN6Ui/3FUkI9pWNZe/Z7rEIVOzMsOs1/HSAECCvSZ7PkIbkNqL/AZn6WbZvnoVZw/qwqYMo4/w==}
cpu: [x64]
os: [linux]
+ libc: [musl]
- '@rollup/rollup-openharmony-arm64@4.52.0':
- resolution: {integrity: sha512-A1JalX4MOaFAAyGgpO7XP5khquv/7xKzLIyLmhNrbiCxWpMlnsTYr8dnsWM7sEeotNmxvSOEL7F65j0HXFcFsw==}
+ '@rollup/rollup-openharmony-arm64@4.53.1':
+ resolution: {integrity: sha512-0++oPNgLJHBblreu0SFM7b3mAsBJBTY0Ksrmu9N6ZVrPiTkRgda52mWR7TKhHAsUb9noCjFvAw9l6ZO1yzaVbA==}
cpu: [arm64]
os: [openharmony]
- '@rollup/rollup-win32-arm64-msvc@4.52.0':
- resolution: {integrity: sha512-YQugafP/rH0eOOHGjmNgDURrpYHrIX0yuojOI8bwCyXwxC9ZdTd3vYkmddPX0oHONLXu9Rb1dDmT0VNpjkzGGw==}
+ '@rollup/rollup-win32-arm64-msvc@4.53.1':
+ resolution: {integrity: sha512-VJXivz61c5uVdbmitLkDlbcTk9Or43YC2QVLRkqp86QoeFSqI81bNgjhttqhKNMKnQMWnecOCm7lZz4s+WLGpQ==}
cpu: [arm64]
os: [win32]
- '@rollup/rollup-win32-ia32-msvc@4.52.0':
- resolution: {integrity: sha512-zYdUYhi3Qe2fndujBqL5FjAFzvNeLxtIqfzNEVKD1I7C37/chv1VxhscWSQHTNfjPCrBFQMnynwA3kpZpZ8w4A==}
+ '@rollup/rollup-win32-ia32-msvc@4.53.1':
+ resolution: {integrity: sha512-NmZPVTUOitCXUH6erJDzTQ/jotYw4CnkMDjCYRxNHVD9bNyfrGoIse684F9okwzKCV4AIHRbUkeTBc9F2OOH5Q==}
cpu: [ia32]
os: [win32]
- '@rollup/rollup-win32-x64-gnu@4.52.0':
- resolution: {integrity: sha512-fGk03kQylNaCOQ96HDMeT7E2n91EqvCDd3RwvT5k+xNdFCeMGnj5b5hEgTGrQuyidqSsD3zJDQ21QIaxXqTBJw==}
+ '@rollup/rollup-win32-x64-gnu@4.53.1':
+ resolution: {integrity: sha512-2SNj7COIdAf6yliSpLdLG8BEsp5lgzRehgfkP0Av8zKfQFKku6JcvbobvHASPJu4f3BFxej5g+HuQPvqPhHvpQ==}
cpu: [x64]
os: [win32]
- '@rollup/rollup-win32-x64-msvc@4.52.0':
- resolution: {integrity: sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ==}
+ '@rollup/rollup-win32-x64-msvc@4.53.1':
+ resolution: {integrity: sha512-rLarc1Ofcs3DHtgSzFO31pZsCh8g05R2azN1q3fF+H423Co87My0R+tazOEvYVKXSLh8C4LerMK41/K7wlklcg==}
cpu: [x64]
os: [win32]
@@ -1428,24 +1439,28 @@ packages:
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@swc/core-linux-arm64-musl@1.13.3':
resolution: {integrity: sha512-bc+CXYlFc1t8pv9yZJGus372ldzOVscBl7encUBlU1m/Sig0+NDJLz6cXXRcFyl6ABNOApWeR4Yl7iUWx6C8og==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@swc/core-linux-x64-gnu@1.13.3':
resolution: {integrity: sha512-dFXoa0TEhohrKcxn/54YKs1iwNeW6tUkHJgXW33H381SvjKFUV53WR231jh1sWVJETjA3vsAwxKwR23s7UCmUA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@swc/core-linux-x64-musl@1.13.3':
resolution: {integrity: sha512-ieyjisLB+ldexiE/yD8uomaZuZIbTc8tjquYln9Quh5ykOBY7LpJJYBWvWtm1g3pHv6AXlBI8Jay7Fffb6aLfA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@swc/core-win32-arm64-msvc@1.13.3':
resolution: {integrity: sha512-elTQpnaX5vESSbhCEgcwXjpMsnUbqqHfEpB7ewpkAsLzKEXZaK67ihSRYAuAx6ewRQTo7DS5iTT6X5aQD3MzMw==}
@@ -1521,24 +1536,28 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@tailwindcss/oxide-linux-arm64-musl@4.1.11':
resolution: {integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@tailwindcss/oxide-linux-x64-gnu@4.1.11':
resolution: {integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@tailwindcss/oxide-linux-x64-musl@4.1.11':
resolution: {integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@tailwindcss/oxide-wasm32-wasi@4.1.11':
resolution: {integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==}
@@ -1838,8 +1857,8 @@ packages:
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
engines: {node: '>=10.13.0'}
- esbuild@0.25.10:
- resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==}
+ esbuild@0.25.12:
+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
engines: {node: '>=18'}
hasBin: true
@@ -2140,24 +2159,28 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
lightningcss-linux-arm64-musl@1.30.1:
resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
lightningcss-linux-x64-gnu@1.30.1:
resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
lightningcss-linux-x64-musl@1.30.1:
resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
+ libc: [musl]
lightningcss-win32-arm64-msvc@1.30.1:
resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
@@ -2504,8 +2527,8 @@ packages:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
- rollup@4.52.0:
- resolution: {integrity: sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g==}
+ rollup@4.53.1:
+ resolution: {integrity: sha512-n2I0V0lN3E9cxxMqBCT3opWOiQBzRN7UG60z/WDKqdX2zHUS/39lezBcsckZFsV6fUTSnfqI7kHf60jDAPGKug==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@@ -2687,8 +2710,8 @@ packages:
vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
- vite@7.1.5:
- resolution: {integrity: sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==}
+ vite@7.1.11:
+ resolution: {integrity: sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
@@ -2809,82 +2832,82 @@ snapshots:
react: 19.1.1
tslib: 2.8.1
- '@esbuild/aix-ppc64@0.25.10':
+ '@esbuild/aix-ppc64@0.25.12':
optional: true
- '@esbuild/android-arm64@0.25.10':
+ '@esbuild/android-arm64@0.25.12':
optional: true
- '@esbuild/android-arm@0.25.10':
+ '@esbuild/android-arm@0.25.12':
optional: true
- '@esbuild/android-x64@0.25.10':
+ '@esbuild/android-x64@0.25.12':
optional: true
- '@esbuild/darwin-arm64@0.25.10':
+ '@esbuild/darwin-arm64@0.25.12':
optional: true
- '@esbuild/darwin-x64@0.25.10':
+ '@esbuild/darwin-x64@0.25.12':
optional: true
- '@esbuild/freebsd-arm64@0.25.10':
+ '@esbuild/freebsd-arm64@0.25.12':
optional: true
- '@esbuild/freebsd-x64@0.25.10':
+ '@esbuild/freebsd-x64@0.25.12':
optional: true
- '@esbuild/linux-arm64@0.25.10':
+ '@esbuild/linux-arm64@0.25.12':
optional: true
- '@esbuild/linux-arm@0.25.10':
+ '@esbuild/linux-arm@0.25.12':
optional: true
- '@esbuild/linux-ia32@0.25.10':
+ '@esbuild/linux-ia32@0.25.12':
optional: true
- '@esbuild/linux-loong64@0.25.10':
+ '@esbuild/linux-loong64@0.25.12':
optional: true
- '@esbuild/linux-mips64el@0.25.10':
+ '@esbuild/linux-mips64el@0.25.12':
optional: true
- '@esbuild/linux-ppc64@0.25.10':
+ '@esbuild/linux-ppc64@0.25.12':
optional: true
- '@esbuild/linux-riscv64@0.25.10':
+ '@esbuild/linux-riscv64@0.25.12':
optional: true
- '@esbuild/linux-s390x@0.25.10':
+ '@esbuild/linux-s390x@0.25.12':
optional: true
- '@esbuild/linux-x64@0.25.10':
+ '@esbuild/linux-x64@0.25.12':
optional: true
- '@esbuild/netbsd-arm64@0.25.10':
+ '@esbuild/netbsd-arm64@0.25.12':
optional: true
- '@esbuild/netbsd-x64@0.25.10':
+ '@esbuild/netbsd-x64@0.25.12':
optional: true
- '@esbuild/openbsd-arm64@0.25.10':
+ '@esbuild/openbsd-arm64@0.25.12':
optional: true
- '@esbuild/openbsd-x64@0.25.10':
+ '@esbuild/openbsd-x64@0.25.12':
optional: true
- '@esbuild/openharmony-arm64@0.25.10':
+ '@esbuild/openharmony-arm64@0.25.12':
optional: true
- '@esbuild/sunos-x64@0.25.10':
+ '@esbuild/sunos-x64@0.25.12':
optional: true
- '@esbuild/win32-arm64@0.25.10':
+ '@esbuild/win32-arm64@0.25.12':
optional: true
- '@esbuild/win32-ia32@0.25.10':
+ '@esbuild/win32-ia32@0.25.12':
optional: true
- '@esbuild/win32-x64@0.25.10':
+ '@esbuild/win32-x64@0.25.12':
optional: true
'@eslint-community/eslint-utils@4.7.0(eslint@9.33.0(jiti@2.5.1))':
@@ -4391,70 +4414,70 @@ snapshots:
'@rolldown/pluginutils@1.0.0-beta.27': {}
- '@rollup/rollup-android-arm-eabi@4.52.0':
+ '@rollup/rollup-android-arm-eabi@4.53.1':
optional: true
- '@rollup/rollup-android-arm64@4.52.0':
+ '@rollup/rollup-android-arm64@4.53.1':
optional: true
- '@rollup/rollup-darwin-arm64@4.52.0':
+ '@rollup/rollup-darwin-arm64@4.53.1':
optional: true
- '@rollup/rollup-darwin-x64@4.52.0':
+ '@rollup/rollup-darwin-x64@4.53.1':
optional: true
- '@rollup/rollup-freebsd-arm64@4.52.0':
+ '@rollup/rollup-freebsd-arm64@4.53.1':
optional: true
- '@rollup/rollup-freebsd-x64@4.52.0':
+ '@rollup/rollup-freebsd-x64@4.53.1':
optional: true
- '@rollup/rollup-linux-arm-gnueabihf@4.52.0':
+ '@rollup/rollup-linux-arm-gnueabihf@4.53.1':
optional: true
- '@rollup/rollup-linux-arm-musleabihf@4.52.0':
+ '@rollup/rollup-linux-arm-musleabihf@4.53.1':
optional: true
- '@rollup/rollup-linux-arm64-gnu@4.52.0':
+ '@rollup/rollup-linux-arm64-gnu@4.53.1':
optional: true
- '@rollup/rollup-linux-arm64-musl@4.52.0':
+ '@rollup/rollup-linux-arm64-musl@4.53.1':
optional: true
- '@rollup/rollup-linux-loong64-gnu@4.52.0':
+ '@rollup/rollup-linux-loong64-gnu@4.53.1':
optional: true
- '@rollup/rollup-linux-ppc64-gnu@4.52.0':
+ '@rollup/rollup-linux-ppc64-gnu@4.53.1':
optional: true
- '@rollup/rollup-linux-riscv64-gnu@4.52.0':
+ '@rollup/rollup-linux-riscv64-gnu@4.53.1':
optional: true
- '@rollup/rollup-linux-riscv64-musl@4.52.0':
+ '@rollup/rollup-linux-riscv64-musl@4.53.1':
optional: true
- '@rollup/rollup-linux-s390x-gnu@4.52.0':
+ '@rollup/rollup-linux-s390x-gnu@4.53.1':
optional: true
- '@rollup/rollup-linux-x64-gnu@4.52.0':
+ '@rollup/rollup-linux-x64-gnu@4.53.1':
optional: true
- '@rollup/rollup-linux-x64-musl@4.52.0':
+ '@rollup/rollup-linux-x64-musl@4.53.1':
optional: true
- '@rollup/rollup-openharmony-arm64@4.52.0':
+ '@rollup/rollup-openharmony-arm64@4.53.1':
optional: true
- '@rollup/rollup-win32-arm64-msvc@4.52.0':
+ '@rollup/rollup-win32-arm64-msvc@4.53.1':
optional: true
- '@rollup/rollup-win32-ia32-msvc@4.52.0':
+ '@rollup/rollup-win32-ia32-msvc@4.53.1':
optional: true
- '@rollup/rollup-win32-x64-gnu@4.52.0':
+ '@rollup/rollup-win32-x64-gnu@4.53.1':
optional: true
- '@rollup/rollup-win32-x64-msvc@4.52.0':
+ '@rollup/rollup-win32-x64-msvc@4.53.1':
optional: true
'@swc/core-darwin-arm64@1.13.3':
@@ -4578,12 +4601,12 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.11
'@tailwindcss/oxide-win32-x64-msvc': 4.1.11
- '@tailwindcss/vite@4.1.11(vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1))':
+ '@tailwindcss/vite@4.1.11(vite@7.1.11(jiti@2.5.1)(lightningcss@1.30.1))':
dependencies:
'@tailwindcss/node': 4.1.11
'@tailwindcss/oxide': 4.1.11
tailwindcss: 4.1.11
- vite: 7.1.5(jiti@2.5.1)(lightningcss@1.30.1)
+ vite: 7.1.11(jiti@2.5.1)(lightningcss@1.30.1)
'@tanstack/react-virtual@3.11.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
@@ -4726,11 +4749,11 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
- '@vitejs/plugin-react-swc@3.11.0(@swc/helpers@0.5.17)(vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1))':
+ '@vitejs/plugin-react-swc@3.11.0(@swc/helpers@0.5.17)(vite@7.1.11(jiti@2.5.1)(lightningcss@1.30.1))':
dependencies:
'@rolldown/pluginutils': 1.0.0-beta.27
'@swc/core': 1.13.3(@swc/helpers@0.5.17)
- vite: 7.1.5(jiti@2.5.1)(lightningcss@1.30.1)
+ vite: 7.1.11(jiti@2.5.1)(lightningcss@1.30.1)
transitivePeerDependencies:
- '@swc/helpers'
@@ -4856,34 +4879,34 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.2.2
- esbuild@0.25.10:
+ esbuild@0.25.12:
optionalDependencies:
- '@esbuild/aix-ppc64': 0.25.10
- '@esbuild/android-arm': 0.25.10
- '@esbuild/android-arm64': 0.25.10
- '@esbuild/android-x64': 0.25.10
- '@esbuild/darwin-arm64': 0.25.10
- '@esbuild/darwin-x64': 0.25.10
- '@esbuild/freebsd-arm64': 0.25.10
- '@esbuild/freebsd-x64': 0.25.10
- '@esbuild/linux-arm': 0.25.10
- '@esbuild/linux-arm64': 0.25.10
- '@esbuild/linux-ia32': 0.25.10
- '@esbuild/linux-loong64': 0.25.10
- '@esbuild/linux-mips64el': 0.25.10
- '@esbuild/linux-ppc64': 0.25.10
- '@esbuild/linux-riscv64': 0.25.10
- '@esbuild/linux-s390x': 0.25.10
- '@esbuild/linux-x64': 0.25.10
- '@esbuild/netbsd-arm64': 0.25.10
- '@esbuild/netbsd-x64': 0.25.10
- '@esbuild/openbsd-arm64': 0.25.10
- '@esbuild/openbsd-x64': 0.25.10
- '@esbuild/openharmony-arm64': 0.25.10
- '@esbuild/sunos-x64': 0.25.10
- '@esbuild/win32-arm64': 0.25.10
- '@esbuild/win32-ia32': 0.25.10
- '@esbuild/win32-x64': 0.25.10
+ '@esbuild/aix-ppc64': 0.25.12
+ '@esbuild/android-arm': 0.25.12
+ '@esbuild/android-arm64': 0.25.12
+ '@esbuild/android-x64': 0.25.12
+ '@esbuild/darwin-arm64': 0.25.12
+ '@esbuild/darwin-x64': 0.25.12
+ '@esbuild/freebsd-arm64': 0.25.12
+ '@esbuild/freebsd-x64': 0.25.12
+ '@esbuild/linux-arm': 0.25.12
+ '@esbuild/linux-arm64': 0.25.12
+ '@esbuild/linux-ia32': 0.25.12
+ '@esbuild/linux-loong64': 0.25.12
+ '@esbuild/linux-mips64el': 0.25.12
+ '@esbuild/linux-ppc64': 0.25.12
+ '@esbuild/linux-riscv64': 0.25.12
+ '@esbuild/linux-s390x': 0.25.12
+ '@esbuild/linux-x64': 0.25.12
+ '@esbuild/netbsd-arm64': 0.25.12
+ '@esbuild/netbsd-x64': 0.25.12
+ '@esbuild/openbsd-arm64': 0.25.12
+ '@esbuild/openbsd-x64': 0.25.12
+ '@esbuild/openharmony-arm64': 0.25.12
+ '@esbuild/sunos-x64': 0.25.12
+ '@esbuild/win32-arm64': 0.25.12
+ '@esbuild/win32-ia32': 0.25.12
+ '@esbuild/win32-x64': 0.25.12
escape-string-regexp@4.0.0: {}
@@ -5601,32 +5624,32 @@ snapshots:
reusify@1.1.0: {}
- rollup@4.52.0:
+ rollup@4.53.1:
dependencies:
'@types/estree': 1.0.8
optionalDependencies:
- '@rollup/rollup-android-arm-eabi': 4.52.0
- '@rollup/rollup-android-arm64': 4.52.0
- '@rollup/rollup-darwin-arm64': 4.52.0
- '@rollup/rollup-darwin-x64': 4.52.0
- '@rollup/rollup-freebsd-arm64': 4.52.0
- '@rollup/rollup-freebsd-x64': 4.52.0
- '@rollup/rollup-linux-arm-gnueabihf': 4.52.0
- '@rollup/rollup-linux-arm-musleabihf': 4.52.0
- '@rollup/rollup-linux-arm64-gnu': 4.52.0
- '@rollup/rollup-linux-arm64-musl': 4.52.0
- '@rollup/rollup-linux-loong64-gnu': 4.52.0
- '@rollup/rollup-linux-ppc64-gnu': 4.52.0
- '@rollup/rollup-linux-riscv64-gnu': 4.52.0
- '@rollup/rollup-linux-riscv64-musl': 4.52.0
- '@rollup/rollup-linux-s390x-gnu': 4.52.0
- '@rollup/rollup-linux-x64-gnu': 4.52.0
- '@rollup/rollup-linux-x64-musl': 4.52.0
- '@rollup/rollup-openharmony-arm64': 4.52.0
- '@rollup/rollup-win32-arm64-msvc': 4.52.0
- '@rollup/rollup-win32-ia32-msvc': 4.52.0
- '@rollup/rollup-win32-x64-gnu': 4.52.0
- '@rollup/rollup-win32-x64-msvc': 4.52.0
+ '@rollup/rollup-android-arm-eabi': 4.53.1
+ '@rollup/rollup-android-arm64': 4.53.1
+ '@rollup/rollup-darwin-arm64': 4.53.1
+ '@rollup/rollup-darwin-x64': 4.53.1
+ '@rollup/rollup-freebsd-arm64': 4.53.1
+ '@rollup/rollup-freebsd-x64': 4.53.1
+ '@rollup/rollup-linux-arm-gnueabihf': 4.53.1
+ '@rollup/rollup-linux-arm-musleabihf': 4.53.1
+ '@rollup/rollup-linux-arm64-gnu': 4.53.1
+ '@rollup/rollup-linux-arm64-musl': 4.53.1
+ '@rollup/rollup-linux-loong64-gnu': 4.53.1
+ '@rollup/rollup-linux-ppc64-gnu': 4.53.1
+ '@rollup/rollup-linux-riscv64-gnu': 4.53.1
+ '@rollup/rollup-linux-riscv64-musl': 4.53.1
+ '@rollup/rollup-linux-s390x-gnu': 4.53.1
+ '@rollup/rollup-linux-x64-gnu': 4.53.1
+ '@rollup/rollup-linux-x64-musl': 4.53.1
+ '@rollup/rollup-openharmony-arm64': 4.53.1
+ '@rollup/rollup-win32-arm64-msvc': 4.53.1
+ '@rollup/rollup-win32-ia32-msvc': 4.53.1
+ '@rollup/rollup-win32-x64-gnu': 4.53.1
+ '@rollup/rollup-win32-x64-msvc': 4.53.1
fsevents: 2.3.3
run-parallel@1.2.0:
@@ -5807,13 +5830,13 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
- vite@7.1.5(jiti@2.5.1)(lightningcss@1.30.1):
+ vite@7.1.11(jiti@2.5.1)(lightningcss@1.30.1):
dependencies:
- esbuild: 0.25.10
+ esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
- rollup: 4.52.0
+ rollup: 4.53.1
tinyglobby: 0.2.15
optionalDependencies:
fsevents: 2.3.3
From 88056371a073801f51e45ef26f8fbe6a6c7a0df0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 2 Mar 2026 23:41:32 +0100
Subject: [PATCH 09/22] build(deps): bump actions/checkout from 5 to 6 (#357)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-version: '6'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/beta.yml | 19 +++++--------------
.github/workflows/ci.yml | 6 +++---
.github/workflows/picotool.yml | 2 +-
.github/workflows/release.yml | 12 ++++++------
4 files changed, 15 insertions(+), 24 deletions(-)
diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml
index 66fc2e0a..84ba8065 100644
--- a/.github/workflows/beta.yml
+++ b/.github/workflows/beta.yml
@@ -1,14 +1,11 @@
name: Beta Release
on:
workflow_dispatch:
-
env:
CARGO_TERM_COLOR: always
-
permissions:
contents: write
pages: write
-
jobs:
prepare-beta:
runs-on: ubuntu-latest
@@ -42,13 +39,12 @@ jobs:
git push
echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
echo "faderpunk_version=$(grep '^version' faderpunk/Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')" >> $GITHUB_OUTPUT
-
build-picotool:
needs: prepare-beta
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
ref: ${{ needs.prepare-beta.outputs.sha }}
- name: Install dependencies
@@ -101,12 +97,11 @@ jobs:
with:
name: picotool-artifact
path: picotool/build/picotool
-
build-firmware:
needs: prepare-beta
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
ref: ${{ needs.prepare-beta.outputs.sha }}
- name: Install System Dependencies
@@ -125,7 +120,6 @@ jobs:
with:
name: firmware-artifact
path: target/thumbv8m.main-none-eabihf/release/faderpunk
-
package-firmware:
needs: [prepare-beta, build-firmware, build-picotool]
runs-on: ubuntu-latest
@@ -152,12 +146,11 @@ jobs:
with:
name: release-artifacts-firmware
path: artifacts
-
build-configurator:
needs: prepare-beta
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
ref: ${{ needs.prepare-beta.outputs.sha }}
- name: Setup Node.js
@@ -198,7 +191,6 @@ jobs:
with:
name: configurator-pages-artifact
path: configurator/dist
-
release:
needs: [prepare-beta, package-firmware, build-configurator]
runs-on: ubuntu-latest
@@ -223,18 +215,17 @@ jobs:
- run: knope release --verbose
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
deploy-beta-configurator:
needs: [prepare-beta, build-configurator]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
ref: ${{ needs.prepare-beta.outputs.sha }}
fetch-depth: 0
- name: Checkout gh-pages branch
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
ref: gh-pages
path: gh-pages
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index efa2bb81..c46dfd3a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,7 +17,7 @@ jobs:
name: Formatting, linting, tests
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
@@ -35,7 +35,7 @@ jobs:
name: Generate bindings and build configurator
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v5
with:
@@ -64,7 +64,7 @@ jobs:
needs: [lint]
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Install System Dependencies
run: |
export DEBIAN_FRONTEND=noninteractive
diff --git a/.github/workflows/picotool.yml b/.github/workflows/picotool.yml
index 8f65bab9..52e59a2e 100644
--- a/.github/workflows/picotool.yml
+++ b/.github/workflows/picotool.yml
@@ -10,7 +10,7 @@ jobs:
steps:
# Checkout your repository code
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
# Install dependencies for building picotool and pico-sdk
- name: Install dependencies
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 5441e297..ba521024 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -48,7 +48,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Install dependencies
run: |
sudo apt update
@@ -106,7 +106,7 @@ jobs:
if: needs.detect-changes.outputs.faderpunk == 'true'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Install System Dependencies
run: |
export DEBIAN_FRONTEND=noninteractive
@@ -159,7 +159,7 @@ jobs:
if: needs.detect-changes.outputs.configurator == 'true'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v5
with:
@@ -241,11 +241,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Checkout gh-pages branch
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
ref: gh-pages
path: gh-pages
@@ -304,7 +304,7 @@ jobs:
if: needs.detect-changes.outputs.libfp == 'true'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
From b0794a59e17ef039c1caeb7ab9057e05878baa85 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 2 Mar 2026 23:49:51 +0100
Subject: [PATCH 10/22] build(deps): bump actions/setup-node from 5 to 6 (#301)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v5...v6)
---
updated-dependencies:
- dependency-name: actions/setup-node
dependency-version: '6'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/beta.yml | 2 +-
.github/workflows/ci.yml | 2 +-
.github/workflows/release.yml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml
index 84ba8065..985cc7c6 100644
--- a/.github/workflows/beta.yml
+++ b/.github/workflows/beta.yml
@@ -154,7 +154,7 @@ jobs:
with:
ref: ${{ needs.prepare-beta.outputs.sha }}
- name: Setup Node.js
- uses: actions/setup-node@v5
+ uses: actions/setup-node@v6
with:
node-version: '22'
- name: Setup pnpm
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c46dfd3a..9228d664 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -37,7 +37,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
- uses: actions/setup-node@v5
+ uses: actions/setup-node@v6
with:
node-version: '22'
- name: Setup pnpm
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ba521024..d5a1f653 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -161,7 +161,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
- uses: actions/setup-node@v5
+ uses: actions/setup-node@v6
with:
node-version: '22'
- name: Setup pnpm
From f67da262cbc7b7b6dfc79f98c05bd9b02ef74c79 Mon Sep 17 00:00:00 2001
From: ArthurGibert <110692829+ArthurGibert@users.noreply.github.com>
Date: Wed, 4 Mar 2026 20:47:46 +0100
Subject: [PATCH 11/22] fix(midi): fix jitter issue (#468)
---
faderpunk/src/tasks/midi.rs | 26 ++++++++++++++++++--------
1 file changed, 18 insertions(+), 8 deletions(-)
diff --git a/faderpunk/src/tasks/midi.rs b/faderpunk/src/tasks/midi.rs
index 16477d1d..e3bf5c04 100644
--- a/faderpunk/src/tasks/midi.rs
+++ b/faderpunk/src/tasks/midi.rs
@@ -38,6 +38,7 @@ midly::stack_buffer! {
const MIDI_CHANNEL_SIZE: usize = 16;
const MIDI_APP_QUEUE_SIZE: usize = 16;
const MIDI_PUBSUB_SIZE: usize = 64;
+const MIDI_BURST_PER_TICK: usize = 8;
// Max apps
const MIDI_PUBSUB_SUBS: usize = GLOBAL_CHANNELS;
// Only one, from here
@@ -221,15 +222,24 @@ pub async fn midi_distributor() {
let _ = app_queues[start_channel].push_back(ev);
}
}
- // The 1ms throttle timer has fired, send one message.
+ // The throttle timer has fired, send a small burst.
Either::Second(_) => {
- // Find the next app with a message in its queue (round-robin)
- for i in 0..16 {
- let app_idx = (last_app_id + 1 + i) % 16;
- if let Some(ev) = app_queues[app_idx].pop_front() {
- midi_out_sender.send(MidiOutEvent::Event(ev)).await;
- last_app_id = app_idx;
- break; // Stop after sending one message
+ for _ in 0..MIDI_BURST_PER_TICK {
+ let mut sent = false;
+
+ // Find the next app with a message in its queue (round-robin)
+ for i in 0..16 {
+ let app_idx = (last_app_id + 1 + i) % 16;
+ if let Some(ev) = app_queues[app_idx].pop_front() {
+ midi_out_sender.send(MidiOutEvent::Event(ev)).await;
+ last_app_id = app_idx;
+ sent = true;
+ break;
+ }
+ }
+
+ if !sent {
+ break;
}
}
}
From 3454df58194e0708b2bb82a2d87a3cd7f9720d6f Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 4 Mar 2026 21:10:59 +0100
Subject: [PATCH 12/22] build(deps): bump actions/checkout from 5 to 6 (#461)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-version: '6'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/beta.yml | 4 ++--
.github/workflows/prepare-release.yml | 2 +-
.github/workflows/release.yml | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml
index 985cc7c6..30970189 100644
--- a/.github/workflows/beta.yml
+++ b/.github/workflows/beta.yml
@@ -18,7 +18,7 @@ jobs:
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
@@ -195,7 +195,7 @@ jobs:
needs: [prepare-beta, package-firmware, build-configurator]
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
ref: ${{ needs.prepare-beta.outputs.sha }}
fetch-depth: 0
diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml
index 9df2d47e..28641834 100644
--- a/.github/workflows/prepare-release.yml
+++ b/.github/workflows/prepare-release.yml
@@ -12,7 +12,7 @@ jobs:
if: "!contains(github.event.head_commit.message, 'chore: prepare release')"
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Configure Git
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d5a1f653..8c1fb998 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -25,7 +25,7 @@ jobs:
configurator: ${{ steps.check.outputs.configurator }}
configurator_minor: ${{ steps.check.outputs.configurator_minor }}
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
fetch-depth: 0
- id: check
@@ -212,7 +212,7 @@ jobs:
if: always() && needs.detect-changes.result == 'success'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Download firmware artifacts
From 4dbb3a6b299ce0751315c37b3f4a8152aa03bdd0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 5 Mar 2026 00:10:20 +0200
Subject: [PATCH 13/22] build(deps): bump knope-dev/action from 2.1.0 to 2.1.2
(#463)
---
.github/workflows/beta.yml | 4 ++--
.github/workflows/prepare-release.yml | 2 +-
.github/workflows/release.yml | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml
index 30970189..7514da51 100644
--- a/.github/workflows/beta.yml
+++ b/.github/workflows/beta.yml
@@ -26,7 +26,7 @@ jobs:
run: |
git config --global user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- - uses: knope-dev/action@v2.1.0
+ - uses: knope-dev/action@v2.1.2
with:
version: 0.22.2
- name: Prepare beta release
@@ -209,7 +209,7 @@ jobs:
with:
name: release-artifacts-configurator
path: artifacts
- - uses: knope-dev/action@v2.1.0
+ - uses: knope-dev/action@v2.1.2
with:
version: 0.22.2
- run: knope release --verbose
diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml
index 28641834..18186d65 100644
--- a/.github/workflows/prepare-release.yml
+++ b/.github/workflows/prepare-release.yml
@@ -22,7 +22,7 @@ jobs:
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- - uses: knope-dev/action@v2.1.0
+ - uses: knope-dev/action@v2.1.2
with:
version: 0.22.2
- run: knope prepare-release --verbose
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8c1fb998..b754825c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -227,7 +227,7 @@ jobs:
with:
name: release-artifacts-configurator
path: artifacts
- - uses: knope-dev/action@v2.1.0
+ - uses: knope-dev/action@v2.1.2
with:
version: 0.22.2
- run: knope release --verbose
From 7e5b53d430e9fa5768f893c7b5794df6450799d9 Mon Sep 17 00:00:00 2001
From: ArthurGibert <110692829+ArthurGibert@users.noreply.github.com>
Date: Wed, 4 Mar 2026 23:49:05 +0100
Subject: [PATCH 14/22] feat(faderpunk): add LED feedback for straight/triplet
division settings (#457)
* feat(led): division LED colors reflects triplet division (orange) or even (blue)
* fix(manual): update manual with triplet division indicators
---
configurator/src/components/ManualTab.tsx | 22 ++++++++++++----------
faderpunk/src/apps/clk_div.rs | 6 +++++-
faderpunk/src/apps/euclid.rs | 6 +++++-
faderpunk/src/apps/notefader.rs | 6 +++++-
faderpunk/src/apps/probatrigger.rs | 6 +++++-
faderpunk/src/apps/seq8.rs | 10 ++++++++--
faderpunk/src/apps/turing.rs | 6 +++++-
7 files changed, 45 insertions(+), 17 deletions(-)
diff --git a/configurator/src/components/ManualTab.tsx b/configurator/src/components/ManualTab.tsx
index 77cc5d6f..a8643a18 100644
--- a/configurator/src/components/ManualTab.tsx
+++ b/configurator/src/components/ManualTab.tsx
@@ -180,7 +180,7 @@ const apps: ManualAppData[] = [
"Ranges",
"Octaves",
],
- text: "4x16 step sequencer app featuring four independent sequencers, each represented by a distinct color. Each sequencer has two pages, and you can navigate between them using Shift + Buttons. The CV/Gate outputs are paired per sequencer: jacks 1&2 for sequencer 1, 3&4 for sequencer 2, and so on. MIDI channels for each sequencer can be set individually in the parameters. Faders are used to set note values, buttons define the gate pattern, and long button presses enable legato. Shift modifies settings for the selected sequencer: Shift + Fader 1 sets step length, Fader 2 sets gate length, Fader 3 selects octave, Fader 4 defines the sequence range (1–5 octaves), and Fader 5 sets the sequence resolution (32ndT, 32nd, 16thT, 16th, 8thT, 8th, 4thT, 4th). Buttons are used to select pages, with two pages available per sequencer. The output of each sequencer is quantized to the scale set in the global quantizer.",
+ text: "4x16 step sequencer app featuring four independent sequencers, each represented by a distinct color. Each sequencer has two pages, and you can navigate between them using Shift + Buttons. The CV/Gate outputs are paired per sequencer: jacks 1&2 for sequencer 1, 3&4 for sequencer 2, and so on. MIDI channels for each sequencer can be set individually in the parameters. Faders are used to set note values, buttons define the gate pattern, and long button presses enable legato. Shift modifies settings for the selected sequencer: Shift + Fader 1 sets step length, Fader 2 sets gate length, Fader 3 selects octave, Fader 4 defines the sequence range (1–5 octaves), and Fader 5 sets the sequence resolution (32ndT, 32nd, 16thT, 16th, 8thT, 8th, 4thT, 4th). In Shift mode, resolution type is color-coded on sequence LEDs: orange for triplet divisions and blue for straight divisions. Buttons are used to select pages, with two pages available per sequencer. The output of each sequencer is quantized to the scale set in the global quantizer.",
channels: [
{
jackTitle: "CV Output",
@@ -324,7 +324,7 @@ const apps: ManualAppData[] = [
"Range",
],
storage: ["Attenuation", "Length", "Register", "Resolution"],
- text: "This app is inspired by the concept of a Turing machine as used in modular synthesizers—a type of probabilistic sequencer that generates evolving patterns based on controlled randomness. It can be set to send either MIDI CC or MIDI notes, while CV output is always active, sending 0–10V. The fader controls the probability of bit flips: when fully down, the sequence loops without changes; when fully up, bit flips occur constantly and the sequence length doubles; in the middle, there’s a 50/50 chance of flipping, resulting in the most randomness. Holding Shift and pressing the button a number of times sets the sequence length—for example, holding Shift and pressing three times sets a 3-step sequence, which is applied upon releasing Shift. The output is quantized for both CV and MIDI notes according to the global quantizer. Parameters include MIDI channel, base note (lowest MIDI note the Turing machine can generate), gate percentage (MIDI only), and color. Main functions include using the fader to set probability, Shift + Fader to set range, Shift + Button to set sequence length, and Button + Fader to set clock resolution (32ndT, 32nd, 16thT, 16th, 8thT, 8th, 4thT, 4th). All app state is stored in scenes, including the sequences themselves—making this, as far as we know, the only Turing machine with preset saving.",
+ text: "This app is inspired by the concept of a Turing machine as used in modular synthesizers—a type of probabilistic sequencer that generates evolving patterns based on controlled randomness. It can be set to send either MIDI CC or MIDI notes, while CV output is always active, sending 0–10V. The fader controls the probability of bit flips: when fully down, the sequence loops without changes; when fully up, bit flips occur constantly and the sequence length doubles; in the middle, there’s a 50/50 chance of flipping, resulting in the most randomness. Holding Shift and pressing the button a number of times sets the sequence length—for example, holding Shift and pressing three times sets a 3-step sequence, which is applied upon releasing Shift. The output is quantized for both CV and MIDI notes according to the global quantizer. Parameters include MIDI channel, base note (lowest MIDI note the Turing machine can generate), gate percentage (MIDI only), and color. Main functions include using the fader to set probability, Shift + Fader to set range, Shift + Button to set sequence length, and Button + Fader to set clock resolution (32ndT, 32nd, 16thT, 16th, 8thT, 8th, 4thT, 4th). While setting clock resolution, the bottom LED is orange for triplet divisions and blue for straight divisions. All app state is stored in scenes, including the sequences themselves—making this, as far as we know, the only Turing machine with preset saving.",
channels: [
{
jackTitle: "Output",
@@ -395,7 +395,7 @@ const apps: ManualAppData[] = [
"Muted",
"Mode",
],
- text: "This app is a Euclidean sequencer with two outputs: Jack 1 delivers the main Euclidean rhythm, while Jack 2 provides either an inverted version or an end-of-rhythm pulse. In inverted mode, if Output 1 sends a pulse, Output 2 does not—and vice versa. Send MIDI triggers, with MIDI channel and MIDI notes. Main functions include Fader 1 for sequence length and Fader 2 for number of beats (fill). Button 1 toggles semitone offset, Button 2 mutes the output. Shift + Fader 1 sets rotation, Shift + Fader 2 sets probability. Button + Fader 1 changes the sequencer speed with available resolutions: 32ndT, 32nd, 16thT, 16th, 8thT, 8th, 4thT, 4th, 2nd, note, half bar, bar.",
+ text: "This app is a Euclidean sequencer with two outputs: Jack 1 delivers the main Euclidean rhythm, while Jack 2 provides either an inverted version or an end-of-rhythm pulse. In inverted mode, if Output 1 sends a pulse, Output 2 does not—and vice versa. Send MIDI triggers, with MIDI channel and MIDI notes. Main functions include Fader 1 for sequence length and Fader 2 for number of beats (fill). Button 1 toggles semitone offset, Button 2 mutes the output. Shift + Fader 1 sets rotation, Shift + Fader 2 sets probability. Button + Fader 1 changes the sequencer speed with available resolutions: 32ndT, 32nd, 16thT, 16th, 8thT, 8th, 4thT, 4th, 2nd, note, half bar, bar. While setting speed, LED color indicates division type: orange for triplet divisions and blue for straight divisions.",
channels: [
{
jackTitle: "Trigger 1 Out",
@@ -411,7 +411,7 @@ const apps: ManualAppData[] = [
fnDescription: "Fn + Fader changes the sequencer speed",
ledTop: "Trigger 1 activity",
ledBottom: "",
- ledBottomPlusFn: "Clock speed",
+ ledBottomPlusFn: "Clock speed (orange: triplet, blue: straight)",
},
{
jackTitle: "Trigger 2 Out",
@@ -438,7 +438,7 @@ const apps: ManualAppData[] = [
icon: "die",
params: ["MIDI Channel", "MIDI NOTE", "GATE %", "Color"],
storage: ["Probability", "Muted", "Resolution"],
- text: "This app sends random trigger signals on clock. It can output MIDI notes and CV triggers. The fader sets the probability of a trigger occurring at each clock pulse. The button acts as a mute toggle. Shift + Fader sets the clock resolution, allowing for rhythmic variation.",
+ text: "This app sends random trigger signals on clock. It can output MIDI notes and CV triggers. The fader sets the probability of a trigger occurring at each clock pulse. The button acts as a mute toggle. Shift + Fader sets the clock resolution, allowing for rhythmic variation. While adjusting resolution, the bottom LED is orange for triplet divisions and blue for straight divisions.",
channels: [
{
jackTitle: "Trigger Output",
@@ -454,6 +454,7 @@ const apps: ManualAppData[] = [
fnPlusShiftDescription: "",
ledTop: "Trigger activity indicator",
ledBottom: "Flashes with clock",
+ ledBottomPlusShift: "Resolution type (orange: triplet, blue: straight)",
},
],
},
@@ -466,7 +467,7 @@ const apps: ManualAppData[] = [
icon: "note",
params: ["MIDI Channel", "Base note", "Span", "GATE %", "Out", "Color"],
storage: ["Note", "Resolution", "Muted", "Clocked"],
- text: "This app sends MIDI notes and V/Oct voltages in a 0–10V range. The outputted notes are filtered by the global quantizer. The note value is tied to the fader position, with the range set by the span parameter. In clocked mode, the button is a toggle and the app outputs notes on regular intervals set by Button + Fader. In direct mode, the MIDI notes are sent when the button is pressed. Main functions: Fader sets the note; Shift + Fader sets clock resolution; Shift + Button toggles mode—Bottom LED is flashing for clocked mode, off for direct mode.",
+ text: "This app sends MIDI notes and V/Oct voltages in a 0–10V range. The outputted notes are filtered by the global quantizer. The note value is tied to the fader position, with the range set by the span parameter. In clocked mode, the button is a toggle and the app outputs notes on regular intervals set by Button + Fader. In direct mode, the MIDI notes are sent when the button is pressed. Main functions: Fader sets the note; Shift + Fader sets clock resolution; Shift + Button toggles mode—Bottom LED is flashing for clocked mode, off for direct mode. Resolution type is color-coded as orange for triplet divisions and blue for straight divisions.",
channels: [
{
jackTitle: "Output",
@@ -481,7 +482,7 @@ const apps: ManualAppData[] = [
fnPlusShiftTitle: "Toggles between clocked and direct mode",
fnPlusShiftDescription: "",
ledTop: "Note output indicator",
- ledBottom: "Flashes in clocked mode",
+ ledBottom: "Flashes in clocked mode (orange: triplet, blue: straight)",
},
],
},
@@ -737,7 +738,7 @@ const apps: ManualAppData[] = [
icon: "note-box",
params: ["MIDI Channel", "MIDI Note", "GATE %", "Color"],
storage: ["Division", "Muted", "Maximum division", "Minimum division"],
- text: "This is a simple clock divider app that was suggested by youtuber and Discord member Synthdad. The app allows for a performative control of clock division/multiplication allowing for 'build ups and drops' for example. The maximum and minimum divisions can be user set using shift + fader and button + fader respectively. These are saved into the scenes allowing you to set different ranges depending on your needs.",
+ text: "This is a simple clock divider app that was suggested by youtuber and Discord member Synthdad. The app allows for a performative control of clock division/multiplication allowing for 'build ups and drops' for example. The maximum and minimum divisions can be user set using shift + fader and button + fader respectively. These are saved into the scenes allowing you to set different ranges depending on your needs. While setting divisions, LEDs indicate the selected type: orange for triplet divisions and blue for straight divisions.",
channels: [
{
jackTitle: "Trigger out",
@@ -751,8 +752,9 @@ const apps: ManualAppData[] = [
fnDescription: "",
fnPlusShiftTitle: "Mute",
ledTop: "Trigger activity indicator",
- ledTopPlusShift: "Maximum division",
- ledBottomPlusShift: "Minimum division",
+ ledTopPlusShift: "Maximum division (orange: triplet, blue: straight)",
+ ledBottomPlusShift:
+ "Minimum division (orange: triplet, blue: straight)",
ledBottom: "",
},
],
diff --git a/faderpunk/src/apps/clk_div.rs b/faderpunk/src/apps/clk_div.rs
index 27daa524..5308b38e 100644
--- a/faderpunk/src/apps/clk_div.rs
+++ b/faderpunk/src/apps/clk_div.rs
@@ -202,7 +202,11 @@ pub async fn run(
if clkn.is_multiple_of(div) && !muted {
jack.set_high().await;
if glob_latch_layer.get() == LatchLayer::Main {
- leds.set(0, Led::Top, led_color, LED_BRIGHTNESS);
+ if matches!(div, 2 | 4 | 8 | 16) {
+ leds.set(0, Led::Bottom, Color::Orange, Brightness::High);
+ } else {
+ leds.set(0, Led::Bottom, Color::Blue, Brightness::High);
+ }
}
midi.send_note_on(note, 4095).await;
note_on = true;
diff --git a/faderpunk/src/apps/euclid.rs b/faderpunk/src/apps/euclid.rs
index 19ad2a46..c127a99b 100644
--- a/faderpunk/src/apps/euclid.rs
+++ b/faderpunk/src/apps/euclid.rs
@@ -266,7 +266,11 @@ pub async fn run(
}
if glob_latch_layer.get() == LatchLayer::Third {
- leds.set(0, Led::Bottom, Color::Red, Brightness::Mid);
+ if matches!(div, 2 | 4 | 8 | 16) {
+ leds.set(0, Led::Bottom, Color::Orange, Brightness::High);
+ } else {
+ leds.set(0, Led::Bottom, Color::Blue, Brightness::High);
+ }
}
}
diff --git a/faderpunk/src/apps/notefader.rs b/faderpunk/src/apps/notefader.rs
index be74889d..88031852 100644
--- a/faderpunk/src/apps/notefader.rs
+++ b/faderpunk/src/apps/notefader.rs
@@ -257,7 +257,11 @@ pub async fn run(
note_on = true;
}
- leds.set(0, Led::Bottom, Color::Red, LED_BRIGHTNESS);
+ if matches!(div, 2 | 4 | 8 | 16) {
+ leds.set(0, Led::Bottom, Color::Orange, Brightness::High);
+ } else {
+ leds.set(0, Led::Bottom, Color::Blue, Brightness::High);
+ }
}
if clkn % div == (div * gatel / 100).clamp(1, div - 1) {
diff --git a/faderpunk/src/apps/probatrigger.rs b/faderpunk/src/apps/probatrigger.rs
index 70156ad2..60593ebb 100644
--- a/faderpunk/src/apps/probatrigger.rs
+++ b/faderpunk/src/apps/probatrigger.rs
@@ -212,7 +212,11 @@ pub async fn run(
}
if glob_latch_layer.get() == LatchLayer::Alt {
- leds.set(0, Led::Bottom, Color::Red, LED_BRIGHTNESS);
+ if matches!(div, 2 | 4 | 8 | 16) {
+ leds.set(0, Led::Bottom, Color::Orange, Brightness::High);
+ } else {
+ leds.set(0, Led::Bottom, Color::Blue, Brightness::High);
+ }
} else {
leds.unset(0, Led::Bottom);
}
diff --git a/faderpunk/src/apps/seq8.rs b/faderpunk/src/apps/seq8.rs
index eaf59d97..6bd7f655 100644
--- a/faderpunk/src/apps/seq8.rs
+++ b/faderpunk/src/apps/seq8.rs
@@ -396,10 +396,16 @@ pub async fn run(
if n >= seq_length[page / 2] {
bright = Brightness::Off;
}
+
+ let led_color = if matches!(clockres[page / 2], 2 | 4 | 8 | 16) {
+ Color::Orange
+ } else {
+ Color::Blue
+ };
if n < 8 {
- led.set(n as usize, Led::Top, Color::Red, bright)
+ led.set(n as usize, Led::Top, led_color, bright)
} else {
- led.set(n as usize - 8, Led::Bottom, Color::Red, bright)
+ led.set(n as usize - 8, Led::Bottom, led_color, bright)
}
}
}
diff --git a/faderpunk/src/apps/turing.rs b/faderpunk/src/apps/turing.rs
index e4956e20..61bd714d 100644
--- a/faderpunk/src/apps/turing.rs
+++ b/faderpunk/src/apps/turing.rs
@@ -271,7 +271,11 @@ pub async fn run(
}
if buttons.is_button_pressed(0) && !buttons.is_shift_pressed() {
- leds.set(0, Led::Bottom, Color::Red, Brightness::High);
+ if matches!(div, 2 | 4 | 8 | 16) {
+ leds.set(0, Led::Bottom, Color::Orange, Brightness::High);
+ } else {
+ leds.set(0, Led::Bottom, Color::Blue, Brightness::High);
+ }
}
}
if clkn % div == (div * gatel as usize / 100).clamp(1, div - 1) {
From be5e32a742664de5da28df364ca4e3bb650c95a5 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 4 Mar 2026 23:53:43 +0100
Subject: [PATCH 15/22] build(deps): bump defmt-rtt from 1.0.0 to 1.1.0 (#464)
Bumps [defmt-rtt](https://github.com/knurling-rs/defmt) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/knurling-rs/defmt/releases)
- [Changelog](https://github.com/knurling-rs/defmt/blob/main/CHANGELOG.md)
- [Commits](https://github.com/knurling-rs/defmt/compare/defmt-rtt-v1.0.0...defmt-rtt-v1.1.0)
---
updated-dependencies:
- dependency-name: defmt-rtt
dependency-version: 1.1.0
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
Cargo.lock | 4 ++--
faderpunk/Cargo.toml | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 649ef53b..116afb63 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -327,9 +327,9 @@ dependencies = [
[[package]]
name = "defmt-rtt"
-version = "1.0.0"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2cac3b8a5644a9e02b75085ebad3b6deafdbdbdec04bb25086523828aa4dfd1"
+checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e"
dependencies = [
"critical-section",
"defmt 1.0.1",
diff --git a/faderpunk/Cargo.toml b/faderpunk/Cargo.toml
index 78a2f810..ad521226 100644
--- a/faderpunk/Cargo.toml
+++ b/faderpunk/Cargo.toml
@@ -38,7 +38,7 @@ cortex-m = { version = "0.7.7", features = ["inline-asm"] }
cortex-m-rt = "0.7.5"
critical-section = "1.2.0"
defmt = "1.0.1"
-defmt-rtt = "1.0.0"
+defmt-rtt = "1.1.0"
embedded-hal = "1.0.0"
embedded-hal-async = "1.0.0"
fm24v10 = "0.1.1"
From e8978c59f3764f2e774c4212daf5e1b471f98768 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 5 Mar 2026 00:13:31 +0100
Subject: [PATCH 16/22] build(deps): bump actions/upload-artifact from 6 to 7
(#462)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)
---
updated-dependencies:
- dependency-name: actions/upload-artifact
dependency-version: '7'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/beta.yml | 10 +++++-----
.github/workflows/picotool.yml | 2 +-
.github/workflows/release.yml | 12 ++++++------
3 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml
index 7514da51..cb43cab8 100644
--- a/.github/workflows/beta.yml
+++ b/.github/workflows/beta.yml
@@ -93,7 +93,7 @@ jobs:
cmake ..
make
- name: Upload picotool artifact
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: picotool-artifact
path: picotool/build/picotool
@@ -116,7 +116,7 @@ jobs:
- run: cargo install flip-link
- run: cargo build --bin faderpunk --release --target thumbv8m.main-none-eabihf
- name: Upload firmware build artifact
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: firmware-artifact
path: target/thumbv8m.main-none-eabihf/release/faderpunk
@@ -142,7 +142,7 @@ jobs:
VERSION="v${{ needs.prepare-beta.outputs.faderpunk_version }}"
picotool_executable/picotool uf2 convert artifacts/faderpunk.elf artifacts/faderpunk-${VERSION}.uf2
- name: Upload release artifacts
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: release-artifacts-firmware
path: artifacts
@@ -182,12 +182,12 @@ jobs:
cd configurator/dist
zip -r ../../artifacts/configurator.zip .
- name: Upload configurator release artifact
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: release-artifacts-configurator
path: artifacts
- name: Upload configurator artifact for GitHub Pages
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: configurator-pages-artifact
path: configurator/dist
diff --git a/.github/workflows/picotool.yml b/.github/workflows/picotool.yml
index 52e59a2e..cf82acf4 100644
--- a/.github/workflows/picotool.yml
+++ b/.github/workflows/picotool.yml
@@ -49,7 +49,7 @@ jobs:
# Upload the picotool binary as an artifact
- name: Upload picotool artifact
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: picotool
path: picotool/build/picotool
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b754825c..eca040ad 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -95,7 +95,7 @@ jobs:
cmake ..
make
- name: Upload picotool artifact
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: picotool-artifact
path: picotool/build/picotool
@@ -119,7 +119,7 @@ jobs:
- run: cargo install flip-link
- run: cargo build --bin faderpunk --release --target thumbv8m.main-none-eabihf
- name: Upload firmware build artifact
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: firmware-artifact
path: target/thumbv8m.main-none-eabihf/release/faderpunk
@@ -148,7 +148,7 @@ jobs:
VERSION="v${{ needs.detect-changes.outputs.faderpunk_version }}"
picotool_executable/picotool uf2 convert artifacts/faderpunk.elf artifacts/faderpunk-${VERSION}.uf2
- name: Upload release artifacts
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: release-artifacts-firmware
path: artifacts
@@ -188,7 +188,7 @@ jobs:
cd configurator/dist
zip -r ../../artifacts/configurator.zip .
- name: Upload configurator release artifact
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: release-artifacts-configurator
path: artifacts
@@ -196,12 +196,12 @@ jobs:
run: pnpm run build:landing
working-directory: ./configurator
- name: Upload configurator artifact for GitHub Pages
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: configurator-pages-artifact
path: configurator/dist
- name: Upload landing page artifact for GitHub Pages
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: landing-pages-artifact
path: configurator/dist-landing
From 4cf7987af4f90285dfcaab54e185b93fd2c896ae Mon Sep 17 00:00:00 2001
From: Richard Smith
Date: Fri, 13 Feb 2026 16:58:36 +0000
Subject: [PATCH 17/22] Manual rebase onto main
---
faderpunk/src/app.rs | 91 ++++-
faderpunk/src/apps/cvcombine.rs | 542 ++++++++++++++++++++++++++++++
faderpunk/src/apps/gatecombine.rs | 408 ++++++++++++++++++++++
faderpunk/src/apps/mod.rs | 2 +
faderpunk/src/state.rs | 27 +-
faderpunk/src/tasks/max.rs | 4 +-
libfp/src/lib.rs | 19 ++
7 files changed, 1081 insertions(+), 12 deletions(-)
create mode 100644 faderpunk/src/apps/cvcombine.rs
create mode 100644 faderpunk/src/apps/gatecombine.rs
diff --git a/faderpunk/src/app.rs b/faderpunk/src/app.rs
index 14cb0cf4..de902673 100644
--- a/faderpunk/src/app.rs
+++ b/faderpunk/src/app.rs
@@ -9,17 +9,19 @@ use max11300::config::{
};
use midly::{live::LiveEvent, num::u4, MidiMessage, PitchBend};
use portable_atomic::Ordering;
+use serde::{Deserialize, Serialize};
use libfp::{
latch::AnalogLatch,
quantizer::{Pitch, QuantizerState},
utils::scale_bits_12_7,
Brightness, ClockDivision, Color, Key, MidiCc, MidiChannel, MidiIn, MidiNote, MidiOut, Note,
- Range, TakeoverMode,
+ Range, TakeoverMode, GLOBAL_CHANNELS,
};
use crate::{
events::{EventPubSubChannel, InputEvent},
+ state::{get_gate_jacks, get_in_jacks, get_out_jacks, update_state},
tasks::{
buttons::{is_channel_button_pressed, is_shift_button_pressed},
clock::{ClockSubscriber, CLOCK_PUBSUB, TICK_COUNTER},
@@ -84,9 +86,10 @@ impl Leds {
}
}
+#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug)]
pub struct InJack {
- channel: usize,
- range: Range,
+ pub channel: usize,
+ pub range: Range,
}
impl InJack {
@@ -103,8 +106,9 @@ impl InJack {
}
}
+#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug)]
pub struct GateJack {
- channel: usize,
+ pub channel: usize,
}
impl GateJack {
@@ -121,9 +125,10 @@ impl GateJack {
}
}
+#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug)]
pub struct OutJack {
- channel: usize,
- range: Range,
+ pub channel: usize,
+ pub range: Range,
}
impl OutJack {
@@ -679,7 +684,15 @@ impl App {
)
.await;
- InJack::new(self.start_channel + chan, range)
+ let global_chan = self.start_channel + chan;
+ let jack = InJack::new(global_chan, range);
+ // Register new jack in global runtime state
+ update_state(|s| {
+ s.in_jacks[global_chan] = Some(jack);
+ true
+ })
+ .await;
+ jack
}
pub async fn make_out_jack(&self, chan: usize, range: Range) -> OutJack {
@@ -691,7 +704,15 @@ impl App {
self.reconfigure_jack(chan, Mode::Mode5(ConfigMode5(dac_range)), None)
.await;
- OutJack::new(self.start_channel + chan, range)
+ let global_chan = self.start_channel + chan;
+ let jack = OutJack::new(global_chan, range);
+ // Register new jack in global runtime state
+ update_state(|s| {
+ s.out_jacks[global_chan] = Some(jack);
+ true
+ })
+ .await;
+ jack
}
pub async fn make_gate_jack(&self, chan: usize, level: u16) -> GateJack {
@@ -699,7 +720,59 @@ impl App {
self.reconfigure_jack(chan, Mode::Mode3(ConfigMode3), Some(level))
.await;
- GateJack::new(self.start_channel + chan)
+ let global_chan = self.start_channel + chan;
+ let jack = GateJack::new(global_chan);
+ // Register new jack in global runtime state
+ update_state(|s| {
+ s.gate_jacks[chan] = Some(jack);
+ true
+ })
+ .await;
+ jack
+ }
+
+ /// Obtain current output value from any CV jack global channel, 0-based (not an app-specific channel)
+ /// If output jack voltage range is 0-10V or -5 to +5V, return value is in range 0-4095
+ /// If output jack voltage range in 0-5V, return value is in range 0-2047
+ ///
+ /// If you point this at a gate out jack by mistake, it will return 0.
+ ///
+ pub fn get_out_global_jack_value(global_chan: usize) -> u16 {
+ let chan = global_chan.clamp(0, GLOBAL_CHANNELS - 1);
+ MAX_VALUES_DAC[chan].load(Ordering::Relaxed)
+ }
+
+ /// Obtain current gate value from any Gate Jack global channel, 0-based (not an app-specific channel).
+ /// If gate is hi, will return true
+ /// If gate is lo, will return false
+ pub fn get_out_global_gate_jack_is_high(global_chan: usize) -> bool {
+ let chan = global_chan.clamp(0, GLOBAL_CHANNELS - 1);
+ let gate = MAX_TRIGGERS_GPO[chan].load(Ordering::Relaxed);
+ gate == 4
+ }
+
+ /// Gets a possible copy of the stored config of a given global CV output jack channel, if any
+ #[allow(unused)]
+ pub async fn get_out_jack_config(global_chan: usize) -> Option {
+ let chan = global_chan.clamp(0, GLOBAL_CHANNELS - 1);
+ let jacks = get_out_jacks().await;
+ jacks[chan]
+ }
+
+ /// Gets a possible copy of the stored config of a given global Gate output jack channel, if any
+ #[allow(unused)]
+ pub async fn get_gate_jack_config(global_chan: usize) -> Option {
+ let chan = global_chan.clamp(0, GLOBAL_CHANNELS - 1);
+ let jacks = get_gate_jacks().await;
+ jacks[chan]
+ }
+
+ /// Gets a possible copy of the stored config of a given global Gate input jack channel, if any
+ #[allow(unused)]
+ pub async fn get_in_jack_config(global_chan: usize) -> Option {
+ let chan = global_chan.clamp(0, GLOBAL_CHANNELS - 1);
+ let jacks = get_in_jacks().await;
+ jacks[chan]
}
pub async fn delay_millis(&self, millis: u64) {
diff --git a/faderpunk/src/apps/cvcombine.rs b/faderpunk/src/apps/cvcombine.rs
new file mode 100644
index 00000000..dae8ad4a
--- /dev/null
+++ b/faderpunk/src/apps/cvcombine.rs
@@ -0,0 +1,542 @@
+//! # CV Combine app
+//!
+//! Precision CV adder / combiner / quantizer of one or two other output variable CV jacks belonging to other apps
+//!
+//! Created by Richard Smith (@rjsmith on GitHub) in February 2026.
+//!
+//! This app is able to sum the output voltages of up to two other specified output jacks belonging to other apps in the same layout.
+//! It simulates the behaviour of Eurorack precision adder / CV math modules.
+//!
+//! The app combines the one or two sampled CV output jacks together, optionally quantises the sum, optionally applies a voltage offset, then re-scales the output signal to the required output voltage range.
+//!
+//! If the final voltage value exceed the min and max of the output Range, the summed CV will be hardclipped to the min or max of the Range.
+//!
+//! This app does not output any MIDI events.
+//!
+//! ## Combine Modes
+//!
+//! The app has 5 "Combine Modes" for combining the CV from the two sampled jacks:
+//! 1. A + B + offset: Sums the CV from the two jacks together,
+//! 2. A - B + offset: Subtracts the CV of the second jack from the first,
+//! 3. Max + offset: Outputs the higher of the two CVs,
+//! 4. Min + offset: Outputs the lower of the two CVs,
+//! 5. Average + offset: Outputs the average of the two CVs (if both channels active)
+//!
+//! ## Hardware Mapping
+//!
+//! | Control | Function | + Shift | + Fn
+//! |---------|----------|---------|------|
+//! | Jack 1 | CV out | N/A | N/A |
+//! | Fader 1 | Offset | Divisor | N/A. |
+//! | LED 1 Top | CV output level | Divisor | N/A
+//! | LED 1 Bottom | Offset (incl. divisor) | N/A | N/A
+//! | Fn 1 | Short press: Toggle Channel A, Long press: Toggle Offset | Short press: Toggle Channel B| N/A |
+//!
+//! The fader sets an CV offset which is applied to the combined CV output after the quantiser.
+//! By default, the offset is an effective range of -5V to + 5V, in steps of 1V. So if you are combining two v/o pitch signals, this
+//! shifts the post-quantized signal from -5 to +5 octaves. The bottom LED shows the level of the offset, with the midpoint (off) representing zero offset, fully lit (blue) representing +5V, and fully lit (red) representing -5V.
+//!
+//! Shift + Fader sets a divisor for the offset (in range 1 at the bottom to 12 at the top) so you can use fractions of a volt as an offset. When the divisor is 12, and the CV is v/o pitch, the offset is in terms of semitones.
+//!
+//! The offset can be toggled on and off by Shift + long-pressing the button.
+//!
+//! ## Scene Storage
+//! The app saves the mute state of the two channels and the offset voltage and divisor in scenes.
+//!
+//! ## Usage Tips
+//!
+//! ### Steevio Sequencing
+//! Replicate the magic sequencing techniques of the Welsh modular musician, Steevio!:
+//! 1. Set up a "Sequencer" 8-channel app with two note patterns with different lengths and tempo (e.g. 5 and 7 steps).
+//! 2. Place an "CV Combine" app somewhere else on the layout, configuring its "A" and "B" Jack channels to the first two "CV Output" jacks of the Sequencer (channels 1 & 3 on the Sequencer).
+//! 3. Set the Combine's output range to 0-10V, turn on Quantize output (to match the fixed 0-10V output range of the Sequencer app).
+//!
+//! ### Muting
+//! Mute and unmute the CV Combine's "A" and "B" channels to bring in either pattern
+//!
+//! ### Combine Modes
+//! Try out the different Combine Modes for different variations in how the two patterns interact.
+//!
+//! ### Octave and semitone shifting
+//! Add some octave shifts (+5 to -5 octave offsets) by moving the bipolar offset fader (no offset in the middle of its range)
+//! Hold Shift and experiment with different offset divisors.
+//! With a divisor of 12 (fader at top of its range), the offset is in semitones
+//!
+//! ### Mix LFOs and Control CVs
+//! Configure the CV Combine to mix an LFO and a Control app together, or two LFOs set to different frequencies, for more complex modulation patterns.
+//! (Disable the quantizer in this case to preserve the smoothness of the LFO signal)
+//!
+//! ### Scene Switching
+//! Save different scene settings for a CV Combine app with different offsets or channel mutes, then switch betwen them during a performance.
+//! If you also change the scene settings of the upstream CV generating apps, you can get some really interesting switched melodies and modulation.
+//!
+use embassy_futures::{
+ join::{join5}, select::{select, select3}
+};
+use embassy_sync::{blocking_mutex::raw::NoopRawMutex, signal::Signal};
+use heapless::Vec;
+use libfp::{
+ APP_MAX_PARAMS, AppIcon, Brightness, Color, Config, GLOBAL_CHANNELS, latch::LatchLayer,
+Param, Range, Value, ext::FromValue};
+
+use libm::roundf;
+use serde::{Deserialize, Serialize};
+
+use crate::app::{App, AppParams, AppStorage, Led, ManagedStorage, ParamStore, SceneEvent };
+
+pub const CHANNELS: usize = 1;
+pub const PARAMS: usize = 8;
+
+const _5V:i16 = 2047;
+const _10V:i16 = 4095;
+const V_PER_OCTAVE:f32 = 409.5;
+
+// App configuration visible to the configurator
+pub static CONFIG: Config = Config::new(
+ "CV Combine",
+ "Adds two CV outputs from other channels, with octave offset and optional global quantization",
+ Color::Yellow,
+ AppIcon::KnobRound,
+)
+.add_param(Param::bool { name: "Enable Channel A" })
+.add_param(Param::i32 { name: "Channel A Jack", min: 1, max: GLOBAL_CHANNELS as i32 })
+.add_param(Param::bool { name: "Enable Channel B" })
+.add_param(Param::i32 { name: "Channel B Jack", min: 1, max: GLOBAL_CHANNELS as i32 })
+.add_param(Param::Color {
+ name: "Color",
+ variants: &[
+ Color::Blue,
+ Color::Green,
+ Color::Rose,
+ Color::Orange,
+ Color::Cyan,
+ Color::Pink,
+ Color::Violet,
+ Color::Yellow,
+ ],
+})
+.add_param(Param::Range {
+ name: "Range",
+ variants: &[Range::_0_10V, Range::_0_5V, Range::_Neg5_5V],
+}).add_param(Param::bool { name: "Quantize output" })
+.add_param(Param::Enum {
+ name: "Combine Mode",
+ variants: &["A+B", "A-B", "Max", "Min", "Average"]});
+
+pub struct Params {
+ // Will be added if = true
+ channel_a_enabled: bool,
+ // Output jack number 1 - GLOBAL_CHANNELS to be sampled
+ channel_a_jack_num: i32,
+ // Will be added if = true
+ channel_b_enabled: bool,
+ // Output jack number 1 - GLOBAL_CHANNELS to be sampled
+ channel_b_jack_num: i32,
+ // LED colour
+ color: Color,
+ // Output CV range
+ range: Range,
+ // Quantize output = true
+ quantize: bool,
+ // Output combination mode
+ combine_mode: usize,
+}
+
+impl Default for Params {
+ fn default() -> Self {
+ Self {
+ channel_a_enabled: false,
+ channel_a_jack_num: 1,
+ channel_b_enabled: false,
+ channel_b_jack_num: 1,
+ color: Color::Yellow,
+ range: Range::_0_10V,
+ quantize: false,
+ combine_mode: 0, // A + B
+ }
+ }
+}
+
+impl AppParams for Params {
+ fn from_values(values: &[Value]) -> Option {
+ if values.len() < PARAMS {
+ return None;
+ }
+ Some(Self {
+ channel_a_enabled: bool::from_value(values[0]),
+ channel_a_jack_num: i32::from_value(values[1]),
+ channel_b_enabled: bool::from_value(values[2]),
+ channel_b_jack_num: i32::from_value(values[3]),
+ color: Color::from_value(values[4]),
+ range: Range::from_value(values[5]),
+ quantize: bool::from_value(values[6]),
+ combine_mode: usize::from_value(values[7]),
+ })
+ }
+
+ fn to_values(&self) -> Vec {
+ let mut vec = Vec::new();
+ vec.push(self.channel_a_enabled.into()).unwrap();
+ vec.push(self.channel_a_jack_num.into()).unwrap();
+ vec.push(self.channel_b_enabled.into()).unwrap();
+ vec.push(self.channel_b_jack_num.into()).unwrap();
+ vec.push(self.color.into()).unwrap();
+ vec.push(self.range.into()).unwrap();
+ vec.push(self.quantize.into()).unwrap();
+ vec.push(self.combine_mode.into()).unwrap();
+ vec
+ }
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Storage {
+ channel_a_mute_saved: bool,
+ channel_b_mute_saved: bool,
+ offset_enabled_saved:bool,
+ offset_voltage_saved: u16,
+ offset_divisor_saved: u16,
+}
+
+impl Default for Storage {
+ fn default() -> Self {
+ Self {
+ channel_a_mute_saved: false,
+ channel_b_mute_saved: false,
+ offset_enabled_saved: true,
+ offset_voltage_saved: 0,
+ offset_divisor_saved: 0,
+ }
+ }
+}
+
+impl AppStorage for Storage {}
+
+// Wrapper task - required for all apps
+#[embassy_executor::task(pool_size = 16/CHANNELS)]
+pub async fn wrapper(app: App, exit_signal: &'static Signal) {
+ let param_store = ParamStore::::new(app.app_id, app.layout_id);
+ let storage = ManagedStorage::::new(app.app_id, app.layout_id);
+
+ param_store.load().await;
+ storage.load().await;
+
+ let app_loop = async {
+ loop {
+ select3(
+ run(&app, ¶m_store, &storage),
+ param_store.param_handler(),
+ storage.saver_task(),
+ )
+ .await;
+ }
+ };
+
+ select(app_loop, app.exit_handler(exit_signal)).await;
+}
+
+// Main app logic
+pub async fn run(app: &App,
+ params: &ParamStore,
+ storage: &ManagedStorage,
+) {
+
+
+ // first_channel and second_channel params are converted to usize in range 0 - (GLOBAL_CHANNELS-1)
+ let (channel_a_enabled, channel_a_jack_num, channel_b_enabled, channel_b_jack_num, led_color, range, quantize, combine_mode) = params
+ .query(|p| {
+ (
+ p.channel_a_enabled,
+ p.channel_a_jack_num,
+ p.channel_b_enabled,
+ p.channel_b_jack_num,
+ p.color,
+ p.range,
+ p.quantize,
+ p.combine_mode,
+ )
+ });
+
+ let channel_a_safe = (channel_a_jack_num.clamp(1, GLOBAL_CHANNELS as i32) - 1) as usize;
+ let channel_b_safe = (channel_b_jack_num.clamp(1, GLOBAL_CHANNELS as i32) - 1) as usize;
+
+ let output = app.make_out_jack(0, range).await;
+ let fader = app.use_faders();
+ let buttons = app.use_buttons();
+ let leds = app.use_leds();
+
+ let quantizer = app.use_quantizer(range);
+ let channel_a_mute_glob = app.make_global(storage.query(|s| s.channel_a_mute_saved));
+ let channel_b_mute_glob = app.make_global(storage.query(|s| s.channel_b_mute_saved));
+ let glob_latch_layer = app.make_global(LatchLayer::Main);
+
+ // Set up initial state of LED button
+ if channel_a_mute_glob.get() {
+ leds.unset(0, Led::Button);
+ } else {
+ leds.set(0, Led::Button, led_color, Brightness::Mid);
+ }
+
+ let main_fut = async {
+
+ loop {
+ app.delay_millis(1).await;
+
+ let channel_a_active = channel_a_enabled && !channel_a_mute_glob.get();
+ let channel_b_active = channel_b_enabled && !channel_b_mute_glob.get();
+
+ // Prevent feedback loops by disabling the possibility to sample from the same channel that the app is outputting on
+ let channel_a_use = channel_a_active && app.start_channel != channel_a_safe;
+ let channel_b_use = channel_b_active && app.start_channel != channel_b_safe;
+
+ // Get output jack config to find the configured output CV Range
+ // TODO: Take this out of this fast loop
+ let a_jack_config = if channel_a_use {
+ App::::get_out_jack_config(channel_a_safe).await
+ } else {
+ None
+ };
+ let b_jack_config = if channel_b_use {
+ App::::get_out_jack_config(channel_b_safe).await
+ } else {
+ None
+ };
+
+ // Get sampled jack values and transform to voltage values according to their individual configured CV Range.
+ // If channel not active, treat as zero. If jack config not found (e.g. app unplugged), also treat as zero.
+ let a_in_v: i16 = if channel_a_use {
+ match a_jack_config {
+ Some(config) => {
+ let raw = App::::get_out_global_jack_value(channel_a_safe);
+ config.range.jack_value_to_voltage_value(raw)
+ },
+ None => 0
+ }
+ } else {
+ 0
+ };
+ let b_in_v:i16 = if channel_b_use {
+ match b_jack_config {
+ Some(config) => {
+ let raw = App::::get_out_global_jack_value(channel_b_safe);
+ config.range.jack_value_to_voltage_value(raw)
+ },
+ None => 0
+ }
+ } else {
+ 0
+ };
+ let a_plus_5v = a_in_v + _5V;
+ let b_plus_5v = b_in_v + _5V;
+
+ let mut out_v:i16 = if combine_mode == 0 {
+ // Add
+ a_plus_5v + b_plus_5v - _10V
+ } else if combine_mode == 1 {
+ // Subtract
+ a_plus_5v - b_plus_5v - _10V
+ } else if combine_mode == 2 {
+ // Max
+ if a_plus_5v > b_plus_5v { a_in_v } else { b_in_v }
+ } else if combine_mode == 3 {
+ // Min
+ if channel_a_use && channel_b_use {
+ if a_plus_5v < b_plus_5v { a_in_v } else { b_in_v }
+ } else if channel_a_use && !channel_b_use {
+ a_in_v
+ } else if !channel_a_use && channel_b_use {
+ b_in_v
+ } else {
+ 0
+ }
+ } else if combine_mode == 4 {
+ // Average
+ // If both channels active, output their average, else either a or b only
+ if channel_a_use && channel_b_use {
+ ((a_plus_5v + b_plus_5v) / 2) - _5V
+ } else if channel_a_use && !channel_b_use {
+ a_in_v
+ } else {
+ b_in_v
+ }
+ } else {
+ 0
+ };
+
+
+ // Optionally add additional octave or divided semitone offset
+ let (offset_enabled, offset_voltage_saved, offset_divisor_saved) = storage.query(|s| (s.offset_enabled_saved, s.offset_voltage_saved, s.offset_divisor_saved));
+ let mut offset_v = 0;
+ let mut offset_divisor = 1;
+ if offset_enabled {
+ offset_divisor = if offset_divisor_saved > 0 { offset_divisor_saved } else { 1 };
+ offset_v = calculate_offset_voltage(offset_voltage_saved, offset_divisor);
+ out_v += offset_v;
+ }
+
+ // Update bottom LED to either show offset or divisor level
+ match glob_latch_layer.get() {
+ LatchLayer::Main => {
+ if offset_enabled && offset_v > 0 {
+ let pos: u8 = (offset_v / 8).clamp(0, 255) as u8;
+ leds.set(0, Led::Bottom, Color::Blue, Brightness::Custom(pos));
+ } else if offset_enabled && offset_v < 0 {
+ let neg = ((offset_v.abs()) / 8).clamp(0, 255) as u8;
+ leds.set(0, Led::Bottom, Color::Rose, Brightness::Custom(neg));
+ } else {
+ leds.unset(0, Led::Bottom);
+ }
+ }
+ LatchLayer::Alt => {
+ let divisor: u8 = (((11.0 / 4095.0) * offset_divisor as f32) as i16 + 1).clamp(1, 12) as u8;
+ leds.set(0, Led::Bottom, Color::Green, Brightness::Custom(21 * divisor)); // 255/12 is approx 21
+ }
+ _ => {}
+ };
+
+ // Clamp to output voltage range, negative voltages clamped to 0V if output range does not support negative voltages
+ out_v = out_v.clamp(-_5V, _10V);
+ let out_safe = match range {
+ Range::_0_10V => out_v.clamp(0, _10V) as u16,
+ Range::_0_5V => out_v.clamp(0, _5V) as u16 * 2,
+ Range::_Neg5_5V => (out_v.clamp(-_5V, _5V) + _5V) as u16,
+ };
+
+ // Optionally quantize output CV to global quantizer scale
+ let out: u16 = if quantize {
+ let out_pitch = quantizer.get_quantized_note(out_safe).await;
+ out_pitch.as_counts(range)
+ } else {
+ out_safe
+ };
+
+ output.set_value(out);
+ leds.set(0, Led::Top, led_color, Brightness::Custom((out / 16) as u8));
+
+
+ // Change state of button when shift is pressed or released to show correct active state of channels A & B
+ glob_latch_layer.set(LatchLayer::from(buttons.is_shift_pressed()));
+ if !buttons.is_shift_pressed() {
+ let muted = storage.query(|s| s.channel_a_mute_saved);
+ if muted {
+ leds.unset(0, Led::Button);
+ } else {
+ leds.set(0, Led::Button, led_color, Brightness::Mid);
+ }
+ } else {
+ let muted = storage.query(|s| s.channel_b_mute_saved);
+ if muted {
+ leds.unset(0, Led::Button);
+ } else {
+ leds.set(0, Led::Button, led_color, Brightness::Mid);
+ }
+ }
+
+ }
+ };
+
+ // Returns offset voltage in range -2047 to + 2047 (ie. -5V to +5V)
+ fn calculate_offset_voltage(offset_voltage_saved: u16, offset_divisor: u16) -> i16 {
+ // Map divisor saved value to 1 - 12
+ let divisor: i16 = (((11.0 / 4095.0) * offset_divisor as f32) as i16 + 1).clamp(1, 12);
+ let offset = (offset_voltage_saved as f32 / 409.5) - 5.0; // in range -5.0 to +5.0V (-2047 to + 2047)
+ if divisor == 1 {
+ (roundf(offset) * V_PER_OCTAVE) as i16 // whole volt (ie. octave) steps
+ } else {
+ // Continuous offset, quantized by divisor
+ roundf(offset * V_PER_OCTAVE) as i16 / divisor
+ }
+ }
+
+ let faders_fut = async {
+ let mut latch = app.make_latch(fader.get_value());
+ loop {
+ fader.wait_for_change().await;
+
+ let latch_layer = glob_latch_layer.get();
+
+ let target_value = match latch_layer {
+ LatchLayer::Main => storage.query(|s| s.offset_voltage_saved),
+ LatchLayer::Alt => storage.query(|s| s.offset_divisor_saved),
+ LatchLayer::Third => 0,
+ };
+
+ if let Some(new_value) = latch.update(fader.get_value(), latch_layer, target_value) {
+ match latch_layer {
+ LatchLayer::Main => {
+ storage.modify_and_save(|s| s.offset_voltage_saved = new_value);
+ }
+ LatchLayer::Alt => {
+ storage.modify_and_save(|s| s.offset_divisor_saved = new_value);
+ }
+ LatchLayer::Third => ()
+ }
+ }
+ };
+ };
+
+ let btn_fut = async {
+ loop {
+ buttons.wait_for_down(0).await;
+ if !buttons.is_shift_pressed() {
+ // First channel mute
+ let muted = storage.modify_and_save(|s| {
+ s.channel_a_mute_saved = !s.channel_a_mute_saved;
+ s.channel_a_mute_saved
+ });
+ channel_a_mute_glob.set(muted);
+ if muted {
+ leds.unset(0, Led::Button);
+ } else {
+ leds.set(0, Led::Button, led_color, Brightness::Mid);
+ }
+ } else {
+ // Second channel mute
+ let muted = storage.modify_and_save(|s| {
+ s.channel_b_mute_saved = !s.channel_b_mute_saved;
+ s.channel_b_mute_saved
+ });
+ channel_b_mute_glob.set(muted);
+ if muted {
+ leds.unset(0, Led::Button);
+ } else {
+ leds.set(0, Led::Button, led_color, Brightness::Mid);
+ }
+ }
+ };
+ };
+
+ let long_press_fut = async {
+ //long press
+
+ loop {
+ let (_, is_shift_pressed) = buttons.wait_for_any_long_press().await;
+
+ if !is_shift_pressed {
+ // Toggle offset on/off
+ storage.modify_and_save(|s| {
+ s.offset_enabled_saved = !s.offset_enabled_saved;
+ s.offset_enabled_saved
+ });
+ }
+ }
+ };
+
+ let scene_handler = async {
+ loop {
+ match app.wait_for_scene_event().await {
+ SceneEvent::LoadScene(scene) => {
+ storage.load_from_scene(scene).await;
+ channel_a_mute_glob.set(storage.query(|s| s.channel_a_mute_saved));
+ channel_b_mute_glob.set(storage.query(|s| s.channel_b_mute_saved));
+ }
+
+ SceneEvent::SaveScene(scene) => {
+ storage.save_to_scene(scene).await;
+ }
+ }
+ }
+ };
+
+ join5(main_fut, faders_fut, btn_fut, long_press_fut, scene_handler).await;
+
+}
\ No newline at end of file
diff --git a/faderpunk/src/apps/gatecombine.rs b/faderpunk/src/apps/gatecombine.rs
new file mode 100644
index 00000000..23a9d075
--- /dev/null
+++ b/faderpunk/src/apps/gatecombine.rs
@@ -0,0 +1,408 @@
+//! # Gate Combine app
+//!
+//! Combines two other output gates or triggers from other apps into a single combined gate signal, using binary logic.
+//! The combined gate signal is then passed through a probabilistic gate that is controlled by the app's fader.
+//! If the combined signal goes high, and the probablistic gate allows it through, the output gate will go high,
+//! and stay high until the combined signal goes low, else the output will be low.
+//!
+//! ## Combine Modes
+//!
+//! Gate Combine has different binary logic modes to generate a separate gate signal from two gate signals that generated
+//! by other apps in the same Faderpunk layout:
+//!
+//! 1. OR: Gate output is high if either A or B are high
+//! 2. AND: Gate output is high only if A and B are both high
+//! 3. XOR: Gate output is high only if one of A or B are high (but not both at the same time)
+//! 4. NOR: Gate output is high only if both A and B are low
+//! 5. NAND: Gate output is low if both A and B are high
+//! 6. XNOR: Gate output is high if either A & B are both low or both high
+//!
+//! ## Hardware Mapping
+//!
+//! | Control | Function | + Shift | + Fn
+//! |---------|----------|---------|------|
+//! | Jack 1 | Gate out | N/A | N/A |
+//! | Fader 1 | Gate Probability | N/A | N/A. |
+//! | LED 1 Top | Gate output | N/A | N/A
+//! | LED 1 Bottom | Probability | N/A | N/A
+//! | Fn 1 | Toggle Channel A | Toggle Channel B| N/A |
+//!
+//! ## Usage Tips
+//!
+//! ### Combine drum triggers
+//! Combine the output gate or trigger signals from two other app channels (e.g. "Euclid" and "Random Trigger") using one of the
+//! combine modes and patch the output gate to a drum voice or envelope trigger. It's a great way of creating more rhythms from
+//! a smaller number of trigger sources.
+//!
+//! ### Inverted Gate
+//! Configure input channel's A & B to the same gate signal (e.g. from a Euclid app), then use the NAND or NOR combine mode to
+//! get an inverted version of the original gate signal at the output, which can be used to trigger different voices or functions
+//! at the opposite time of the original gate (e.g. trigger an envelope that controls a VCA with a Bass voice)
+//!
+
+
+use embassy_futures::{
+ join::{join5}, select::{select, select3}
+};
+use embassy_sync::{blocking_mutex::raw::NoopRawMutex, signal::Signal};
+use heapless::Vec;
+use libfp::{
+ APP_MAX_PARAMS, AppIcon, Brightness, Color, Config, GLOBAL_CHANNELS,
+ latch::LatchLayer,
+ Param, Value, ext::FromValue};
+
+use serde::{Deserialize, Serialize};
+
+use crate::app::{App, AppParams, AppStorage, Led, ManagedStorage, ParamStore, SceneEvent };
+
+pub const CHANNELS: usize = 1;
+pub const PARAMS: usize = 6;
+
+const LED_BRIGHTNESS: Brightness = Brightness::High;
+
+// App configuration visible to the configurator
+pub static CONFIG: Config = Config::new(
+ "Gate Combine",
+ "Adds two gate or trigger outputs from other channels together using binary logic",
+ Color::SkyBlue,
+ AppIcon::KnobRound,
+)
+.add_param(Param::bool { name: "Enable Channel A" })
+.add_param(Param::i32 { name: "Channel A Jack", min: 1, max: GLOBAL_CHANNELS as i32 })
+.add_param(Param::bool { name: "Enable Channel B" })
+.add_param(Param::i32 { name: "Channel B Jack", min: 1, max: GLOBAL_CHANNELS as i32 })
+.add_param(Param::Color {
+ name: "Color",
+ variants: &[
+ Color::Blue,
+ Color::Green,
+ Color::Rose,
+ Color::Orange,
+ Color::Cyan,
+ Color::Pink,
+ Color::Violet,
+ Color::Yellow,
+ ],
+})
+.add_param(Param::Enum {
+ name: "Combine Mode",
+ variants: &["OR", "AND", "XOR", "NOR", "NAND", "XNOR"]});
+
+pub struct Params {
+ // Will be added if = true
+ channel_a_enabled: bool,
+ // Output jack number 1 - GLOBAL_CHANNELS to be sampled
+ channel_a_gate_jack_num: i32,
+ // Will be added if = true
+ channel_b_enabled: bool,
+ // Output jack number 1 - GLOBAL_CHANNELS to be sampled
+ channel_b_gate_jack_num: i32,
+ // LED colour
+ color: Color,
+ // Output combination mode
+ combine_mode: usize,
+}
+
+impl Default for Params {
+ fn default() -> Self {
+ Self {
+ channel_a_enabled: false,
+ channel_a_gate_jack_num: 1,
+ channel_b_enabled: false,
+ channel_b_gate_jack_num: 1,
+ color: Color::Yellow,
+ combine_mode: 0, // OR
+ }
+ }
+}
+
+impl AppParams for Params {
+ fn from_values(values: &[Value]) -> Option {
+ if values.len() < PARAMS {
+ return None;
+ }
+ Some(Self {
+ channel_a_enabled: bool::from_value(values[0]),
+ channel_a_gate_jack_num: i32::from_value(values[1]),
+ channel_b_enabled: bool::from_value(values[2]),
+ channel_b_gate_jack_num: i32::from_value(values[3]),
+ color: Color::from_value(values[4]),
+ combine_mode: usize::from_value(values[5]),
+ })
+ }
+
+ fn to_values(&self) -> Vec {
+ let mut vec = Vec::new();
+ vec.push(self.channel_a_enabled.into()).unwrap();
+ vec.push(self.channel_a_gate_jack_num.into()).unwrap();
+ vec.push(self.channel_b_enabled.into()).unwrap();
+ vec.push(self.channel_b_gate_jack_num.into()).unwrap();
+ vec.push(self.color.into()).unwrap();
+ vec.push(self.combine_mode.into()).unwrap();
+ vec
+ }
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Storage {
+ channel_a_mute_saved: bool,
+ channel_b_mute_saved: bool,
+ prob_saved: u16,
+}
+impl Default for Storage {
+ fn default() -> Self {
+ Self {
+ channel_a_mute_saved: false,
+ channel_b_mute_saved: false,
+ prob_saved: 4096,
+ }
+ }
+}
+
+impl AppStorage for Storage {}
+
+// Wrapper task - required for all apps
+#[embassy_executor::task(pool_size = 16/CHANNELS)]
+pub async fn wrapper(app: App, exit_signal: &'static Signal) {
+ let param_store = ParamStore::::new(app.app_id, app.layout_id);
+ let storage = ManagedStorage::::new(app.app_id, app.layout_id);
+
+ param_store.load().await;
+ storage.load().await;
+
+ let app_loop = async {
+ loop {
+ select3(
+ run(&app, ¶m_store, &storage),
+ param_store.param_handler(),
+ storage.saver_task(),
+ )
+ .await;
+ }
+ };
+
+ select(app_loop, app.exit_handler(exit_signal)).await;
+}
+
+// Main app logic
+pub async fn run(app: &App,
+ params: &ParamStore,
+ storage: &ManagedStorage,
+) {
+
+
+ // first_channel and second_channel params are converted to usize in range 0 - (GLOBAL_CHANNELS-1)
+ let (channel_a_enabled, channel_a_gate_jack_num, channel_b_enabled, channel_b_gate_jack_num, led_color, combine_mode) = params
+ .query(|p| {
+ (
+ p.channel_a_enabled,
+ p.channel_a_gate_jack_num,
+ p.channel_b_enabled,
+ p.channel_b_gate_jack_num,
+ p.color,
+ p.combine_mode,
+ )
+ });
+
+ let channel_a_safe = (channel_a_gate_jack_num.clamp(1, GLOBAL_CHANNELS as i32) - 1) as usize;
+ let channel_b_safe = (channel_b_gate_jack_num.clamp(1, GLOBAL_CHANNELS as i32) - 1) as usize;
+
+ let output = app.make_gate_jack(0, 4095).await;
+ let fader = app.use_faders();
+ let buttons = app.use_buttons();
+ let leds = app.use_leds();
+ let die = app.use_die();
+
+ let channel_a_mute_glob = app.make_global(storage.query(|s| s.channel_a_mute_saved));
+ let channel_b_mute_glob = app.make_global(storage.query(|s| s.channel_b_mute_saved));
+ let glob_latch_layer = app.make_global(LatchLayer::Main);
+
+ // Set up initial state of LED button
+ if channel_a_mute_glob.get() {
+ leds.unset(0, Led::Button);
+ } else {
+ leds.set(0, Led::Button, led_color, Brightness::Mid);
+ }
+
+ let mut old_gate_was_high = false;
+
+ let main_fut = async {
+
+ loop {
+ app.delay_millis(1).await;
+
+ let channel_a_active = channel_a_enabled && !channel_a_mute_glob.get();
+ let channel_b_active = channel_b_enabled && !channel_b_mute_glob.get();
+ let channel_a_use = channel_a_active && app.start_channel != channel_a_safe;
+ let channel_b_use = channel_b_active && app.start_channel != channel_b_safe;
+ let a_is_high = if channel_a_use {
+ App::::get_out_global_gate_jack_is_high(channel_a_safe)
+ } else {
+ false
+ };
+ let b_is_high = if channel_b_use {
+ App::::get_out_global_gate_jack_is_high(channel_b_safe)
+ } else {
+ false
+ };
+ let mut out_is_high:bool = if combine_mode == 0 {
+ // OR
+ a_is_high | b_is_high
+ } else if combine_mode == 1 {
+ // AND
+ a_is_high & b_is_high
+ } else if combine_mode == 2 {
+ // XOR
+ a_is_high ^ b_is_high
+ } else if combine_mode == 3 {
+ // NOR
+ !(a_is_high | b_is_high)
+ } else if combine_mode == 4 {
+ // NAND
+ !(a_is_high & b_is_high)
+ } else if combine_mode == 5 {
+ // XNOR
+ !(a_is_high ^ b_is_high)
+ } else {
+ false
+ };
+
+ // Apply probabilistic gate
+
+ let prob = storage.query(|s| s.prob_saved); // Get gate probability from fader 0 - 4095
+ if out_is_high && !old_gate_was_high {
+ // Combined output has just gone high.
+
+ let rand_val = die.roll();
+ // The higher the probability fader, more chance there is of gate pasing through
+ if rand_val >= prob {
+ // bad luck, gate must stay low this time
+ out_is_high = false;
+ }
+ }
+
+ old_gate_was_high = out_is_high;
+
+ if out_is_high {
+ output.set_high().await;
+ leds.set(0, Led::Top, led_color, LED_BRIGHTNESS);
+ } else {
+ output.set_low().await;
+ leds.unset(0, Led::Top);
+ }
+
+ // Update bottom LED to show trigger probability when on main latch layer
+ match glob_latch_layer.get() {
+ LatchLayer::Main => {
+ let pos: u8 = (prob / 8).clamp(0, 255) as u8;
+ leds.set(0, Led::Bottom, Color::Blue, Brightness::Custom(pos));
+ }
+ _ => {
+ leds.unset(0, Led::Bottom);
+ }
+ };
+
+ }
+ };
+
+ let fader_fut = async {
+ let mut latch = app.make_latch(fader.get_value());
+ loop {
+ fader.wait_for_change_at(0).await;
+
+ let latch_layer = glob_latch_layer.get();
+
+ let target_value = match latch_layer {
+ LatchLayer::Main => storage.query(|s| s.prob_saved),
+ LatchLayer::Alt => 0,
+ LatchLayer::Third => 0,
+ };
+
+ if let Some(new_value) = latch.update(fader.get_value(), latch_layer, target_value) {
+ match latch_layer {
+ LatchLayer::Main => {
+ storage.modify_and_save(|s| s.prob_saved = new_value);
+ }
+ LatchLayer::Alt => {}
+ LatchLayer::Third => {}
+ }
+ }
+ }
+ };
+
+ let btn_fut = async {
+ loop {
+ buttons.wait_for_down(0).await;
+ if !buttons.is_shift_pressed() {
+ // First channel mute
+ let muted = storage.modify_and_save(|s| {
+ s.channel_a_mute_saved = !s.channel_a_mute_saved;
+ s.channel_a_mute_saved
+ });
+ channel_a_mute_glob.set(muted);
+ if muted {
+ leds.unset(0, Led::Button);
+ } else {
+ leds.set(0, Led::Button, led_color, Brightness::Mid);
+ }
+ } else {
+ // Second channel mute
+ let muted = storage.modify_and_save(|s| {
+ s.channel_b_mute_saved = !s.channel_b_mute_saved;
+ s.channel_b_mute_saved
+ });
+ channel_b_mute_glob.set(muted);
+ if muted {
+ leds.unset(0, Led::Button);
+ } else {
+ leds.set(0, Led::Button, led_color, Brightness::Mid);
+ }
+ }
+ };
+ };
+
+ let shift_fut = async {
+ loop {
+ app.delay_millis(1).await;
+
+ glob_latch_layer.set(LatchLayer::from(buttons.is_shift_pressed()));
+
+ // Change state of button when shift is pressed or released to show correct active state of first or second added channels
+ if !buttons.is_shift_pressed() {
+ let muted = storage.query(|s| s.channel_a_mute_saved);
+ if muted {
+ leds.unset(0, Led::Button);
+ } else {
+ leds.set(0, Led::Button, led_color, Brightness::Mid);
+ }
+ } else {
+ let muted = storage.query(|s| s.channel_b_mute_saved);
+ if muted {
+ leds.unset(0, Led::Button);
+ } else {
+ leds.set(0, Led::Button, led_color, Brightness::Mid);
+ }
+ }
+
+ };
+ };
+
+ let scene_handler = async {
+ loop {
+ match app.wait_for_scene_event().await {
+ SceneEvent::LoadScene(scene) => {
+ storage.load_from_scene(scene).await;
+ channel_a_mute_glob.set(storage.query(|s| s.channel_a_mute_saved));
+ channel_b_mute_glob.set(storage.query(|s| s.channel_b_mute_saved));
+ }
+
+ SceneEvent::SaveScene(scene) => {
+ storage.save_to_scene(scene).await;
+ }
+ }
+ }
+ };
+
+ join5(main_fut, fader_fut, btn_fut, shift_fut, scene_handler).await;
+
+}
\ No newline at end of file
diff --git a/faderpunk/src/apps/mod.rs b/faderpunk/src/apps/mod.rs
index 29f9478a..2718416f 100644
--- a/faderpunk/src/apps/mod.rs
+++ b/faderpunk/src/apps/mod.rs
@@ -19,4 +19,6 @@ register_apps!(
18 => clk_div,
19 => panner,
22 => lfo_plus,
+ 44 => cvcombine,
+ 45 => gatecombine,
);
diff --git a/faderpunk/src/state.rs b/faderpunk/src/state.rs
index b44ee078..bfad06ec 100644
--- a/faderpunk/src/state.rs
+++ b/faderpunk/src/state.rs
@@ -1,15 +1,25 @@
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
+use libfp::GLOBAL_CHANNELS;
use serde::{Deserialize, Serialize};
-use crate::storage;
+use crate::{
+ app::{GateJack, InJack, OutJack},
+ storage,
+};
#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug)]
pub struct RuntimeState {
pub clock_is_running: bool,
+ pub out_jacks: [Option; GLOBAL_CHANNELS],
+ pub in_jacks: [Option; GLOBAL_CHANNELS],
+ pub gate_jacks: [Option; GLOBAL_CHANNELS],
}
static STATE: Mutex = Mutex::new(RuntimeState {
clock_is_running: true,
+ out_jacks: [None; GLOBAL_CHANNELS],
+ in_jacks: [None; GLOBAL_CHANNELS],
+ gate_jacks: [None; GLOBAL_CHANNELS],
});
pub async fn init_state() {
@@ -45,3 +55,18 @@ where
pub async fn is_clock_running() -> bool {
STATE.lock().await.clock_is_running
}
+
+// Gets configured set of each CV out app jack
+pub async fn get_out_jacks() -> [Option; GLOBAL_CHANNELS] {
+ STATE.lock().await.out_jacks
+}
+
+/// Gets configured set of each input app jack
+pub async fn get_in_jacks() -> [Option; GLOBAL_CHANNELS] {
+ STATE.lock().await.in_jacks
+}
+
+/// Gets configured set of each gate out app jack
+pub async fn get_gate_jacks() -> [Option; GLOBAL_CHANNELS] {
+ STATE.lock().await.gate_jacks
+}
diff --git a/faderpunk/src/tasks/max.rs b/faderpunk/src/tasks/max.rs
index 44e6dc72..5b797b9e 100644
--- a/faderpunk/src/tasks/max.rs
+++ b/faderpunk/src/tasks/max.rs
@@ -242,11 +242,11 @@ async fn process_channel_values(
Mode::Mode3(_) => match MAX_TRIGGERS_GPO[i].load(Ordering::Relaxed) {
1 => {
max.gpo_set_low(port).await.unwrap();
- MAX_TRIGGERS_GPO[i].store(0, Ordering::Relaxed);
+ MAX_TRIGGERS_GPO[i].store(3, Ordering::Relaxed);
}
2 => {
max.gpo_set_high(port).await.unwrap();
- MAX_TRIGGERS_GPO[i].store(0, Ordering::Relaxed);
+ MAX_TRIGGERS_GPO[i].store(4, Ordering::Relaxed);
}
_ => {}
},
diff --git a/libfp/src/lib.rs b/libfp/src/lib.rs
index e0924ca7..02185f29 100644
--- a/libfp/src/lib.rs
+++ b/libfp/src/lib.rs
@@ -924,6 +924,24 @@ impl Range {
pub fn is_bipolar(&self) -> bool {
*self == Range::_Neg5_5V
}
+
+ /// Converts a normalised jack signal value (in range 0 - 4095) to signed voltage value
+ ///
+ /// e.g. convert from an InJack get_value() method call, or from a DAC output value
+ /// If Range 0-10V, output in range 0 - 4095
+ /// If Range 0-5V, output in range 0 - 2047
+ /// If Range -5 - 5V, output in range -2048 - 2047
+ ///
+ /// Useful for simulating real-world voltage math (e.g. Precision Adders)
+ ///
+ pub fn jack_value_to_voltage_value(&self, value: u16) -> i16 {
+ let value = value.clamp(0, 4095) as i16;
+ match self {
+ Range::_0_10V => value,
+ Range::_0_5V => value / 2,
+ Range::_Neg5_5V => value - 2048,
+ }
+ }
}
impl From for DACRANGE {
@@ -954,6 +972,7 @@ impl FromValue for Range {
}
}
+
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, PostcardBindings)]
pub struct MidiCc(u8);
From 59553f57c82304fc66eda8c5ac955ba7534e73e1 Mon Sep 17 00:00:00 2001
From: Richard Smith
Date: Sat, 14 Feb 2026 09:55:52 +0000
Subject: [PATCH 18/22] Tidy up fmt
---
libfp/src/lib.rs | 4 ----
1 file changed, 4 deletions(-)
diff --git a/libfp/src/lib.rs b/libfp/src/lib.rs
index 02185f29..950707c2 100644
--- a/libfp/src/lib.rs
+++ b/libfp/src/lib.rs
@@ -926,14 +926,11 @@ impl Range {
}
/// Converts a normalised jack signal value (in range 0 - 4095) to signed voltage value
- ///
/// e.g. convert from an InJack get_value() method call, or from a DAC output value
/// If Range 0-10V, output in range 0 - 4095
/// If Range 0-5V, output in range 0 - 2047
/// If Range -5 - 5V, output in range -2048 - 2047
- ///
/// Useful for simulating real-world voltage math (e.g. Precision Adders)
- ///
pub fn jack_value_to_voltage_value(&self, value: u16) -> i16 {
let value = value.clamp(0, 4095) as i16;
match self {
@@ -972,7 +969,6 @@ impl FromValue for Range {
}
}
-
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, PostcardBindings)]
pub struct MidiCc(u8);
From f6dafdd43560078391c7a1b4d7271eaf982771d3 Mon Sep 17 00:00:00 2001
From: Richard Smith
Date: Sat, 7 Mar 2026 10:30:24 +0000
Subject: [PATCH 19/22] CV combine manual
---
configurator/src/components/ManualTab.tsx | 84 +++++++++++++++++++++++
configurator/src/components/manual/Md.tsx | 3 +
faderpunk/src/apps/cvcombine.rs | 71 ++-----------------
3 files changed, 91 insertions(+), 67 deletions(-)
diff --git a/configurator/src/components/ManualTab.tsx b/configurator/src/components/ManualTab.tsx
index a8643a18..16db0fc5 100644
--- a/configurator/src/components/ManualTab.tsx
+++ b/configurator/src/components/ManualTab.tsx
@@ -871,6 +871,90 @@ const apps: ManualAppData[] = [
},
],
},
+ {
+ appId: 44,
+ title: "CV Combine",
+ description:
+ "Precision CV adder / combiner / quantizer of two outputs from other apps",
+ color: "Yellow",
+ icon: "knob-round",
+ params: [
+ "Enable Jack A",
+ "Jack A #",
+ "Enable Jack B",
+ "Jack B #",
+ "Color",
+ "Quantise enable",
+ "Range",
+ "Combine Mode",
+ ],
+ storage: [
+ "Jack A Mute",
+ "Jack B Mute",
+ "Quantize",
+ "CV offset enabled",
+ "CV offset",
+ "CV Offset divisor",
+ ],
+ text: `
+This app is able to sum the output voltages of up to two other specified output jacks belonging to other apps in the same layout. It simulates the behaviour of Eurorack precision adder / CV math modules.
+
+* Two selected CV output jacks are sampled every millisecond, combined, optionally quantised, optionally applied a voltage offset, then finally re-scales the output signal to the required output voltage range.
+* If the final voltage value exceed the min and max of the output Range, the summed CV will be hardclipped to the min or max of the Range.
+* The fader sets an CV offset which is applied to the combined CV output after the quantiser.
+* By default, the offset is an effective range of -5V to + 5V, in steps of 1V. So if you are combining two v/o pitch signals, this shifts the post-quantized signal from -5 to +5 octaves. The bottom LED shows the level of the offset, with the midpoint (off) representing zero offset, fully lit (blue) representing +5V, and fully lit (red) representing -5V.
+* Shift + Fader sets a divisor for the offset (in range 1 at the bottom to 12 at the top) so you can use fractions of a volt as an offset. When the divisor is 12, and the CV is used for V/o pitch, the offset is in units of semitones.
+* The offset can be toggled on and off by Shift + long-pressing the button.
+* This app does not emit any MIDI events
+
+#### Tip: Steevio Sequencing
+Replicate the magic sequencing techniques of the modular musician, Steevio!:
+1. Set up a "Sequencer" 8-channel app with two note patterns with different lengths and tempo (e.g. 5 and 7 steps).
+2. Place an "CV Combine" app somewhere else on the layout, configuring its "A" and "B" Jack channels to the first two "CV Output" jacks of the Sequencer (channels 1 & 3 on the Sequencer).
+3. Set the Combine's output range to 0-10V, turn on Quantize output (to match the fixed 0-10V output range of the Sequencer app).
+4. Play with the CV offset to change the played octave
+5. Patch the CV Combine output CV to your favourite V/O oscillator
+
+#### Tip: Muting
+Mute and unmute the CV Combine's "A" and "B" channels to bring in either pattern
+
+#### Tip: Combine Modes
+Try out the different Combine Modes for different variations in how the two patterns interact.
+
+#### Tip: Octave and semitone shifting
+* Add some octave shifts (+5 to -5 octave offsets) by moving the bipolar offset fader (no offset in the middle of its range)
+* Hold Shift and experiment with different offset divisors.
+* With a divisor of 12 (fader at top of its range), the offset is in semitones
+
+#### Tip: Mix LFOs and Control CVs
+Configure the CV Combine to mix an LFO and a Control app together, or two LFOs set to different frequencies, for more complex modulation patterns. Disable the quantizer in this case to preserve the smoothness of the LFO signal.
+
+#### Tip: Scene Switching
+Save different scene settings for a CV Combine app with different offsets or channel mutes, then switch betwen them during a performance. If you also change the scene settings of the upstream CV generating apps, you can get some really interesting switched melodies and modulation.
+
+#### Acknowledgements.
+
+* Created by Richard Smith (Discord: Phommed)
+ `,
+ channels: [
+ {
+ jackTitle: "CV Output",
+ jackDescription: "Combined, quantized, offset CV signal",
+ faderTitle: "CV offset",
+ faderDescription: "Applies bipolar CV offset after the quantizer",
+ faderPlusShiftTitle: "Offset Divisor",
+ faderPlusShiftDescription:
+ "Divides offset per volt, from 1 (octaves) to 12 (semitones)",
+ fnTitle: "Mute & offset",
+ fnDescription: "Short Mute A input- Long Toggle offset",
+ fnPlusShiftTitle: "Mute",
+ fnPlusShiftDescription: "Mute B input",
+ ledTop: "CV output level",
+ ledTopPlusShift: "Offset divisor (1-12)",
+ ledBottom: "Offset",
+ },
+ ],
+ },
];
export const ManualTab = () => {
diff --git a/configurator/src/components/manual/Md.tsx b/configurator/src/components/manual/Md.tsx
index 58626294..5fb9c141 100644
--- a/configurator/src/components/manual/Md.tsx
+++ b/configurator/src/components/manual/Md.tsx
@@ -12,6 +12,9 @@ export const Md = ({ children }: MdProps) => {
<>{children}>,
+ h4: ({ children }) => (
+ {children}
+ ),
strong: ({ children }) => (
{children}
),
diff --git a/faderpunk/src/apps/cvcombine.rs b/faderpunk/src/apps/cvcombine.rs
index dae8ad4a..5f2e0c7c 100644
--- a/faderpunk/src/apps/cvcombine.rs
+++ b/faderpunk/src/apps/cvcombine.rs
@@ -6,70 +6,7 @@
//!
//! This app is able to sum the output voltages of up to two other specified output jacks belonging to other apps in the same layout.
//! It simulates the behaviour of Eurorack precision adder / CV math modules.
-//!
-//! The app combines the one or two sampled CV output jacks together, optionally quantises the sum, optionally applies a voltage offset, then re-scales the output signal to the required output voltage range.
-//!
-//! If the final voltage value exceed the min and max of the output Range, the summed CV will be hardclipped to the min or max of the Range.
-//!
-//! This app does not output any MIDI events.
-//!
-//! ## Combine Modes
-//!
-//! The app has 5 "Combine Modes" for combining the CV from the two sampled jacks:
-//! 1. A + B + offset: Sums the CV from the two jacks together,
-//! 2. A - B + offset: Subtracts the CV of the second jack from the first,
-//! 3. Max + offset: Outputs the higher of the two CVs,
-//! 4. Min + offset: Outputs the lower of the two CVs,
-//! 5. Average + offset: Outputs the average of the two CVs (if both channels active)
-//!
-//! ## Hardware Mapping
-//!
-//! | Control | Function | + Shift | + Fn
-//! |---------|----------|---------|------|
-//! | Jack 1 | CV out | N/A | N/A |
-//! | Fader 1 | Offset | Divisor | N/A. |
-//! | LED 1 Top | CV output level | Divisor | N/A
-//! | LED 1 Bottom | Offset (incl. divisor) | N/A | N/A
-//! | Fn 1 | Short press: Toggle Channel A, Long press: Toggle Offset | Short press: Toggle Channel B| N/A |
-//!
-//! The fader sets an CV offset which is applied to the combined CV output after the quantiser.
-//! By default, the offset is an effective range of -5V to + 5V, in steps of 1V. So if you are combining two v/o pitch signals, this
-//! shifts the post-quantized signal from -5 to +5 octaves. The bottom LED shows the level of the offset, with the midpoint (off) representing zero offset, fully lit (blue) representing +5V, and fully lit (red) representing -5V.
//!
-//! Shift + Fader sets a divisor for the offset (in range 1 at the bottom to 12 at the top) so you can use fractions of a volt as an offset. When the divisor is 12, and the CV is v/o pitch, the offset is in terms of semitones.
-//!
-//! The offset can be toggled on and off by Shift + long-pressing the button.
-//!
-//! ## Scene Storage
-//! The app saves the mute state of the two channels and the offset voltage and divisor in scenes.
-//!
-//! ## Usage Tips
-//!
-//! ### Steevio Sequencing
-//! Replicate the magic sequencing techniques of the Welsh modular musician, Steevio!:
-//! 1. Set up a "Sequencer" 8-channel app with two note patterns with different lengths and tempo (e.g. 5 and 7 steps).
-//! 2. Place an "CV Combine" app somewhere else on the layout, configuring its "A" and "B" Jack channels to the first two "CV Output" jacks of the Sequencer (channels 1 & 3 on the Sequencer).
-//! 3. Set the Combine's output range to 0-10V, turn on Quantize output (to match the fixed 0-10V output range of the Sequencer app).
-//!
-//! ### Muting
-//! Mute and unmute the CV Combine's "A" and "B" channels to bring in either pattern
-//!
-//! ### Combine Modes
-//! Try out the different Combine Modes for different variations in how the two patterns interact.
-//!
-//! ### Octave and semitone shifting
-//! Add some octave shifts (+5 to -5 octave offsets) by moving the bipolar offset fader (no offset in the middle of its range)
-//! Hold Shift and experiment with different offset divisors.
-//! With a divisor of 12 (fader at top of its range), the offset is in semitones
-//!
-//! ### Mix LFOs and Control CVs
-//! Configure the CV Combine to mix an LFO and a Control app together, or two LFOs set to different frequencies, for more complex modulation patterns.
-//! (Disable the quantizer in this case to preserve the smoothness of the LFO signal)
-//!
-//! ### Scene Switching
-//! Save different scene settings for a CV Combine app with different offsets or channel mutes, then switch betwen them during a performance.
-//! If you also change the scene settings of the upstream CV generating apps, you can get some really interesting switched melodies and modulation.
-//!
use embassy_futures::{
join::{join5}, select::{select, select3}
};
@@ -98,10 +35,10 @@ pub static CONFIG: Config = Config::new(
Color::Yellow,
AppIcon::KnobRound,
)
-.add_param(Param::bool { name: "Enable Channel A" })
-.add_param(Param::i32 { name: "Channel A Jack", min: 1, max: GLOBAL_CHANNELS as i32 })
-.add_param(Param::bool { name: "Enable Channel B" })
-.add_param(Param::i32 { name: "Channel B Jack", min: 1, max: GLOBAL_CHANNELS as i32 })
+.add_param(Param::bool { name: "Enable Jack A" })
+.add_param(Param::i32 { name: "Jack A #", min: 1, max: GLOBAL_CHANNELS as i32 })
+.add_param(Param::bool { name: "Enable Jack B" })
+.add_param(Param::i32 { name: "Jack B #", min: 1, max: GLOBAL_CHANNELS as i32 })
.add_param(Param::Color {
name: "Color",
variants: &[
From b6af70f2b38844612c4025b6b4763ff31e3a197b Mon Sep 17 00:00:00 2001
From: Richard Smith
Date: Sat, 7 Mar 2026 11:09:28 +0000
Subject: [PATCH 20/22] Manual entry for Gate Combine app
---
configurator/src/components/ManualTab.tsx | 76 ++++++++++++++++++++++-
faderpunk/src/apps/cvcombine.rs | 4 +-
faderpunk/src/apps/gatecombine.rs | 36 +----------
3 files changed, 78 insertions(+), 38 deletions(-)
diff --git a/configurator/src/components/ManualTab.tsx b/configurator/src/components/ManualTab.tsx
index 16db0fc5..20a3f584 100644
--- a/configurator/src/components/ManualTab.tsx
+++ b/configurator/src/components/ManualTab.tsx
@@ -897,7 +897,7 @@ const apps: ManualAppData[] = [
"CV Offset divisor",
],
text: `
-This app is able to sum the output voltages of up to two other specified output jacks belonging to other apps in the same layout. It simulates the behaviour of Eurorack precision adder / CV math modules.
+Combines the output voltages of up to two other specified output jacks belonging to other apps in the same layout. It simulates the behaviour of Eurorack precision adder / CV math modules.
* Two selected CV output jacks are sampled every millisecond, combined, optionally quantised, optionally applied a voltage offset, then finally re-scales the output signal to the required output voltage range.
* If the final voltage value exceed the min and max of the output Range, the summed CV will be hardclipped to the min or max of the Range.
@@ -907,6 +907,15 @@ This app is able to sum the output voltages of up to two other specified output
* The offset can be toggled on and off by Shift + long-pressing the button.
* This app does not emit any MIDI events
+#### CV Combine Modes
+
+The app has 5 modes for combining the CV from the two sampled jacks:
+1. **A + B**: Sums the CV from the two jacks together,
+2. **A - B**: Subtracts the CV of the second jack from the first,
+3. **Max**: Outputs the higher of the two CVs,
+4. **Min**: Outputs the lower of the two CVs,
+5. **Average**: Outputs the average of the two CVs (if both channels active)
+
#### Tip: Steevio Sequencing
Replicate the magic sequencing techniques of the modular musician, Steevio!:
1. Set up a "Sequencer" 8-channel app with two note patterns with different lengths and tempo (e.g. 5 and 7 steps).
@@ -955,6 +964,71 @@ Save different scene settings for a CV Combine app with different offsets or cha
},
],
},
+ {
+ appId: 45,
+ title: "Gate Combine",
+ description:
+ "Binary logic combination of two gate signals from other apps",
+ color: "Yellow",
+ icon: "knob-round",
+ params: [
+ "Enable Jack A",
+ "Jack A #",
+ "Enable Jack B",
+ "Jack B #",
+ "Color",
+ "Combine Mode",
+ ],
+ storage: [
+ "Jack A Mute",
+ "Jack B Mute",
+ "Gate probability",
+ ],
+ text: `
+Combines two output gates or triggers from other apps into a single combined gate signal, using binary logic. The combined gate signal is then passed through a probabilistic gate that is controlled by the app's fader:
+* If the combined signal goes high, and the probablistic gate allows it through, the output gate will go high, and stay high until the combined signal goes low,
+* else the output will be low.
+
+
+#### Combine Modes
+
+Gate Combine has different binary logic modes to generate a separate gate signal from two gate signals that generated by other apps in the same Faderpunk layout:
+1. **OR**: Gate output is high if either A or B are high
+2. **AND**: Gate output is high only if A and B are both high
+3. **XOR**: Gate output is high only if one of A or B are high (but not both at the same time)
+4. **NOR**: Gate output is high only if both A and B are low
+5. **NAND**: Gate output is low if both A and B are high
+6. **XNOR**: Gate output is high if either A & B are both low or both high
+
+#### Tip: Combine clock-quantized drum triggers
+Combine the output gate or trigger signals from two other app channels (e.g. "Euclid" and "Random Trigger") using one of the
+combine modes and patch the output gate to a drum voice or envelope trigger. It's a great way of creating more rhythms from
+a smaller number of trigger sources.
+
+#### Tip: Inverted Gate
+Configure input channel's A & B to the same gate signal (e.g. from a Euclid app), then use the NAND or NOR combine mode to
+get an inverted version of the original gate signal at the output, which can be used to trigger different voices or functions
+at the opposite time of the original gate (e.g. trigger an envelope that controls a VCA with a Bass voice)
+
+#### Acknowledgements.
+
+* Created by Richard Smith (Discord: Phommed)
+ `,
+ channels: [
+ {
+ jackTitle: "Gate out",
+ jackDescription: "Combined gate output signal",
+ faderTitle: "Gate chance",
+ faderDescription: "Chance of firing new gate after the combine logic (0 - 100%)",
+ fnTitle: "Mute A",
+ fnDescription: "Mute A input",
+ fnPlusShiftTitle: "Mute B",
+ fnPlusShiftDescription: "Mute B input",
+ ledTop: "Gate output",
+ ledBottom: "Chance",
+ },
+ ],
+ },
];
export const ManualTab = () => {
diff --git a/faderpunk/src/apps/cvcombine.rs b/faderpunk/src/apps/cvcombine.rs
index 5f2e0c7c..7bf98dee 100644
--- a/faderpunk/src/apps/cvcombine.rs
+++ b/faderpunk/src/apps/cvcombine.rs
@@ -2,11 +2,11 @@
//!
//! Precision CV adder / combiner / quantizer of one or two other output variable CV jacks belonging to other apps
//!
-//! Created by Richard Smith (@rjsmith on GitHub) in February 2026.
-//!
//! This app is able to sum the output voltages of up to two other specified output jacks belonging to other apps in the same layout.
//! It simulates the behaviour of Eurorack precision adder / CV math modules.
//!
+//! Created by Richard Smith (@phommed on Faderpunk Discord) in February 2026.
+//!
use embassy_futures::{
join::{join5}, select::{select, select3}
};
diff --git a/faderpunk/src/apps/gatecombine.rs b/faderpunk/src/apps/gatecombine.rs
index 23a9d075..fd2967bf 100644
--- a/faderpunk/src/apps/gatecombine.rs
+++ b/faderpunk/src/apps/gatecombine.rs
@@ -4,43 +4,9 @@
//! The combined gate signal is then passed through a probabilistic gate that is controlled by the app's fader.
//! If the combined signal goes high, and the probablistic gate allows it through, the output gate will go high,
//! and stay high until the combined signal goes low, else the output will be low.
-//!
-//! ## Combine Modes
-//!
-//! Gate Combine has different binary logic modes to generate a separate gate signal from two gate signals that generated
-//! by other apps in the same Faderpunk layout:
-//!
-//! 1. OR: Gate output is high if either A or B are high
-//! 2. AND: Gate output is high only if A and B are both high
-//! 3. XOR: Gate output is high only if one of A or B are high (but not both at the same time)
-//! 4. NOR: Gate output is high only if both A and B are low
-//! 5. NAND: Gate output is low if both A and B are high
-//! 6. XNOR: Gate output is high if either A & B are both low or both high
-//!
-//! ## Hardware Mapping
-//!
-//! | Control | Function | + Shift | + Fn
-//! |---------|----------|---------|------|
-//! | Jack 1 | Gate out | N/A | N/A |
-//! | Fader 1 | Gate Probability | N/A | N/A. |
-//! | LED 1 Top | Gate output | N/A | N/A
-//! | LED 1 Bottom | Probability | N/A | N/A
-//! | Fn 1 | Toggle Channel A | Toggle Channel B| N/A |
-//!
-//! ## Usage Tips
//!
-//! ### Combine drum triggers
-//! Combine the output gate or trigger signals from two other app channels (e.g. "Euclid" and "Random Trigger") using one of the
-//! combine modes and patch the output gate to a drum voice or envelope trigger. It's a great way of creating more rhythms from
-//! a smaller number of trigger sources.
+//! Created by Richard Smith (@phommed on Faderpunk Discord) in February 2026.
//!
-//! ### Inverted Gate
-//! Configure input channel's A & B to the same gate signal (e.g. from a Euclid app), then use the NAND or NOR combine mode to
-//! get an inverted version of the original gate signal at the output, which can be used to trigger different voices or functions
-//! at the opposite time of the original gate (e.g. trigger an envelope that controls a VCA with a Bass voice)
-//!
-
-
use embassy_futures::{
join::{join5}, select::{select, select3}
};
From d6a4a57c51e07cae50d1072ef73ec1045fbaa568 Mon Sep 17 00:00:00 2001
From: Richard Smith
Date: Sat, 7 Mar 2026 11:17:14 +0000
Subject: [PATCH 21/22] Tideid up lint warnings
---
configurator/src/components/ManualTab.tsx | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/configurator/src/components/ManualTab.tsx b/configurator/src/components/ManualTab.tsx
index 20a3f584..db988120 100644
--- a/configurator/src/components/ManualTab.tsx
+++ b/configurator/src/components/ManualTab.tsx
@@ -964,11 +964,10 @@ Save different scene settings for a CV Combine app with different offsets or cha
},
],
},
- {
+ {
appId: 45,
title: "Gate Combine",
- description:
- "Binary logic combination of two gate signals from other apps",
+ description: "Binary logic combination of two gate signals from other apps",
color: "Yellow",
icon: "knob-round",
params: [
@@ -979,11 +978,7 @@ Save different scene settings for a CV Combine app with different offsets or cha
"Color",
"Combine Mode",
],
- storage: [
- "Jack A Mute",
- "Jack B Mute",
- "Gate probability",
- ],
+ storage: ["Jack A Mute", "Jack B Mute", "Gate probability"],
text: `
Combines two output gates or triggers from other apps into a single combined gate signal, using binary logic. The combined gate signal is then passed through a probabilistic gate that is controlled by the app's fader:
* If the combined signal goes high, and the probablistic gate allows it through, the output gate will go high, and stay high until the combined signal goes low,
@@ -1019,7 +1014,8 @@ at the opposite time of the original gate (e.g. trigger an envelope that control
jackTitle: "Gate out",
jackDescription: "Combined gate output signal",
faderTitle: "Gate chance",
- faderDescription: "Chance of firing new gate after the combine logic (0 - 100%)",
+ faderDescription:
+ "Chance of firing new gate after the combine logic (0 - 100%)",
fnTitle: "Mute A",
fnDescription: "Mute A input",
fnPlusShiftTitle: "Mute B",
@@ -1028,7 +1024,7 @@ at the opposite time of the original gate (e.g. trigger an envelope that control
ledBottom: "Chance",
},
],
- },
+ },
];
export const ManualTab = () => {
From 87b8e3659b3c4b5822669750470a06388ca9be09 Mon Sep 17 00:00:00 2001
From: Richard Smith
Date: Sat, 7 Mar 2026 14:47:01 +0000
Subject: [PATCH 22/22] Latched sample gate combine inputs
---
faderpunk/src/apps/cvcombine.rs | 26 ++++++++++----------
faderpunk/src/apps/gatecombine.rs | 41 +++++++++++++++++++++++++------
2 files changed, 47 insertions(+), 20 deletions(-)
diff --git a/faderpunk/src/apps/cvcombine.rs b/faderpunk/src/apps/cvcombine.rs
index 7bf98dee..a0fb3b7a 100644
--- a/faderpunk/src/apps/cvcombine.rs
+++ b/faderpunk/src/apps/cvcombine.rs
@@ -215,6 +215,19 @@ pub async fn run(app: &App,
let main_fut = async {
+ // Get output jack config to find the configured output CV Range
+ // Assume app config changes will re-spawn this app and re-execute this code.
+ let a_jack_config = if channel_a_enabled {
+ App::::get_out_jack_config(channel_a_safe).await
+ } else {
+ None
+ };
+ let b_jack_config = if channel_b_enabled {
+ App::::get_out_jack_config(channel_b_safe).await
+ } else {
+ None
+ };
+
loop {
app.delay_millis(1).await;
@@ -225,19 +238,6 @@ pub async fn run(app: &App,
let channel_a_use = channel_a_active && app.start_channel != channel_a_safe;
let channel_b_use = channel_b_active && app.start_channel != channel_b_safe;
- // Get output jack config to find the configured output CV Range
- // TODO: Take this out of this fast loop
- let a_jack_config = if channel_a_use {
- App::::get_out_jack_config(channel_a_safe).await
- } else {
- None
- };
- let b_jack_config = if channel_b_use {
- App::::get_out_jack_config(channel_b_safe).await
- } else {
- None
- };
-
// Get sampled jack values and transform to voltage values according to their individual configured CV Range.
// If channel not active, treat as zero. If jack config not found (e.g. app unplugged), also treat as zero.
let a_in_v: i16 = if channel_a_use {
diff --git a/faderpunk/src/apps/gatecombine.rs b/faderpunk/src/apps/gatecombine.rs
index fd2967bf..02e0d372 100644
--- a/faderpunk/src/apps/gatecombine.rs
+++ b/faderpunk/src/apps/gatecombine.rs
@@ -26,6 +26,10 @@ pub const PARAMS: usize = 6;
const LED_BRIGHTNESS: Brightness = Brightness::High;
+// Sampled input gate jack changed state must remain the same state for given number of milliseconds to change Gate Combine output
+// Intention is to smooth out micro-timing differences between sampled output gates, or when chaining successive Gate Combine apps
+const LATCHED_GATE_CHANGE_MILLIS: u32 = 3;
+
// App configuration visible to the configurator
pub static CONFIG: Config = Config::new(
"Gate Combine",
@@ -190,24 +194,34 @@ pub async fn run(app: &App,
leds.set(0, Led::Button, led_color, Brightness::Mid);
}
- let mut old_gate_was_high = false;
+ let mut old_out_gate_was_high = false;
let main_fut = async {
-
- loop {
+
+ let mut gate_a_unchanged_counter = 0u32;
+ let mut gate_b_unchanged_counter = 0u32;
+ let mut last_gate_a_is_high = false;
+ let mut last_gate_b_is_high = false;
+ let mut latched_gate_a_is_high = false;
+ let mut latched_gate_b_is_high = false;
+
+ loop {
app.delay_millis(1).await;
+ latched_gate_change(channel_a_safe, &mut gate_a_unchanged_counter, &mut last_gate_a_is_high, &mut latched_gate_a_is_high);
+ latched_gate_change(channel_b_safe, &mut gate_b_unchanged_counter, &mut last_gate_b_is_high, &mut latched_gate_b_is_high);
+
let channel_a_active = channel_a_enabled && !channel_a_mute_glob.get();
let channel_b_active = channel_b_enabled && !channel_b_mute_glob.get();
let channel_a_use = channel_a_active && app.start_channel != channel_a_safe;
let channel_b_use = channel_b_active && app.start_channel != channel_b_safe;
let a_is_high = if channel_a_use {
- App::::get_out_global_gate_jack_is_high(channel_a_safe)
+ latched_gate_a_is_high
} else {
false
};
let b_is_high = if channel_b_use {
- App::::get_out_global_gate_jack_is_high(channel_b_safe)
+ latched_gate_b_is_high
} else {
false
};
@@ -236,7 +250,7 @@ pub async fn run(app: &App,
// Apply probabilistic gate
let prob = storage.query(|s| s.prob_saved); // Get gate probability from fader 0 - 4095
- if out_is_high && !old_gate_was_high {
+ if out_is_high && !old_out_gate_was_high {
// Combined output has just gone high.
let rand_val = die.roll();
@@ -247,7 +261,7 @@ pub async fn run(app: &App,
}
}
- old_gate_was_high = out_is_high;
+ old_out_gate_was_high = out_is_high;
if out_is_high {
output.set_high().await;
@@ -371,4 +385,17 @@ pub async fn run(app: &App,
join5(main_fut, fader_fut, btn_fut, shift_fut, scene_handler).await;
+}
+
+fn latched_gate_change(channel_safe: usize, gate_unchanged_counter: &mut u32, last_gate_is_high: &mut bool, latched_gate_is_high: &mut bool) {
+ let jack_is_now_high = App::::get_out_global_gate_jack_is_high(channel_safe);
+ if *last_gate_is_high == jack_is_now_high {
+ *gate_unchanged_counter += 1;
+ } else {
+ *last_gate_is_high = !*last_gate_is_high;
+ *gate_unchanged_counter = 0;
+ }
+ if *latched_gate_is_high != jack_is_now_high && *gate_unchanged_counter > LATCHED_GATE_CHANGE_MILLIS {
+ *latched_gate_is_high = jack_is_now_high;
+ }
}
\ No newline at end of file