Skip to content

Commit dd87705

Browse files
bwhitmanclaude
andcommitted
Level sliders: pseudo-log range 0..9.9 (dpwe's curve)
Both Level sliders (channel strip and FX header) now map slider position p in [0,1] to level = 10^(2(p-0.5)) - 0.1: 0.0 at the left edge, ~1 at mid, 9.9 at the right edge, per dpwe's review on #1132. This is the same curve as AMY's log MIDI-CC mapping with min 0, max 9.9, offset 0.1, so the two dedicated level knobs' CC ranges are updated to match — a hardware CC sweep and a slider sweep now track identically. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent c753ea0 commit dd87705

2 files changed

Lines changed: 44 additions & 20 deletions

File tree

tulip/amyboardweb/static/amy_parameters.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,17 @@ window.addEventListener("DOMContentLoaded", function() {
3838
// registered as a channel knob so its MIDI CC rides the same
3939
// save/restore/bulk-send path as every other knob. Same AMY command as the
4040
// Level slider: i<ch>v0a<val> (amp on the control osc). Default CC 7 = the
41-
// MIDI standard "Channel Volume".
41+
// MIDI standard "Channel Volume". The log range 0..9.9 with offset 0.1 is
42+
// the same pseudo-log curve as the UI slider: 10^(2(p-0.5)) - 0.1.
4243
section: "Synth",
4344
cc: 7,
4445
display_name: "level",
4546
change_code: "i%iv" + CTL_OSC + "a%v",
4647
knob_type: "log",
4748
default_value: 1.0,
4849
amy_default: 1.0,
49-
min_value: 0.001,
50-
max_value: 7,
50+
min_value: 0,
51+
max_value: 9.9,
5152
offset: 0.1,
5253
dedicated_slider: true,
5354
},
@@ -140,19 +141,21 @@ window.addEventListener("DOMContentLoaded", function() {
140141

141142
{
142143
// Per-bus master volume (final mixdown gain for this bus). Shown by the
143-
// dedicated "Level" slider in the Effects panel header (dedicated_slider
144-
// -> kept out of the knob grid), but registered as a bus knob so it can
144+
// dedicated "Level" slider in the FX column header (dedicated_slider ->
145+
// kept out of the knob grid), but registered as a bus knob so it can
145146
// carry a MIDI CC mapping like every other knob. The i%i prefix makes
146-
// AMY resolve the bus from the sending channel at execution time.
147+
// AMY resolve the bus from the sending channel at execution time. The
148+
// log range 0..9.9 with offset 0.1 is the same pseudo-log curve as the
149+
// UI slider: 10^(2(p-0.5)) - 0.1.
147150
section: "Bus",
148151
cc: "",
149152
display_name: "level",
150153
change_code: "i%iV%v",
151154
knob_type: "log",
152155
default_value: 1.0,
153156
amy_default: 1.0,
154-
min_value: 0.001,
155-
max_value: 7,
157+
min_value: 0,
158+
max_value: 9.9,
156159
offset: 0.1,
157160
dedicated_slider: true,
158161
},

tulip/amyboardweb/static/editor/index.html

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,7 +1458,7 @@ <h4 class="modal-title w-100 text-center" id="simulateWelcomeModalLabel">Welcome
14581458
</div>
14591459
<div class="col-12 col-md-auto mt-2 mt-md-0 d-flex align-items-center gap-2 ms-md-2">
14601460
<label class="text-nowrap mb-0 text-white" id="synth-level-label" for="synth-level-slider" style="font-size:0.875rem; cursor:pointer;" title="Click to set a MIDI CC for this channel's level">Level</label>
1461-
<input type="range" class="form-range" id="synth-level-slider" min="0.001" max="7" step="0.001" value="1" style="width:100px;">
1461+
<input type="range" class="form-range" id="synth-level-slider" min="0" max="1" step="0.001" value="0.521" style="width:100px;">
14621462
</div>
14631463
<div class="col-12 col-md-auto mt-2 mt-md-0 d-flex align-items-center gap-2 ms-md-2">
14641464
<label class="text-nowrap mb-0 text-white" for="bus-select" style="font-size:0.875rem;" title="Which mix bus this channel's synth is routed to. Each bus has its own Effects.">Bus</label>
@@ -1473,7 +1473,7 @@ <h4 class="modal-title w-100 text-center" id="simulateWelcomeModalLabel">Welcome
14731473
<div class="patch-global-title">
14741474
<span>FX</span>
14751475
<label class="text-nowrap mb-0" id="fx-level-label" for="fx-level-slider" style="font-size:0.75rem; font-weight:400; letter-spacing:normal; text-transform:none; cursor:pointer;" title="Master level of this channel's mix bus. Click to set a MIDI CC.">Level</label>
1476-
<input type="range" class="form-range" id="fx-level-slider" min="0.001" max="7" step="0.001" value="1" style="width:100px;">
1476+
<input type="range" class="form-range" id="fx-level-slider" min="0" max="1" step="0.001" value="0.521" style="width:100px;">
14771477
</div>
14781478
</div>
14791479
<div class="patch-layout-body">
@@ -1988,14 +1988,32 @@ <h5 class="modal-title" id="adminTokenModalLabel">Admin Token Required</h5>
19881988
const patchLoadModalButton = document.getElementById("patch-open-load-modal-btn");
19891989
const loadPatchModalEl = document.getElementById("loadPatchModal");
19901990
const patchClearChannelButton = document.getElementById("patch-clear-channel-btn");
1991+
// Pseudo-log mapping for both Level sliders (channel and FX/bus):
1992+
// slider position p in [0,1] -> level = 10^(2(p-0.5)) - 0.1, so the
1993+
// level is 0.0 at the left edge, ~1 at mid, 9.9 at the right edge.
1994+
// Same curve as AMY's log MIDI-CC mapping with min 0, max 9.9,
1995+
// offset 0.1 (which the dedicated level knobs use), so a hardware CC
1996+
// sweep and a slider sweep track identically.
1997+
function levelSliderToValue(p) {
1998+
p = Math.min(1, Math.max(0, Number(p) || 0));
1999+
return Math.pow(10, 2 * (p - 0.5)) - 0.1;
2000+
}
2001+
function levelValueToSlider(v) {
2002+
v = Number(v);
2003+
if (!Number.isFinite(v) || v < 0) v = 0;
2004+
return Math.min(1, Math.max(0, 0.5 + 0.5 * Math.log10(v + 0.1)));
2005+
}
2006+
19912007
const synthLevelSlider = document.getElementById("synth-level-slider");
19922008
const _synthLevels = {}; // per-channel level storage
19932009
function getSynthLevel(ch) { return _synthLevels[ch] != null ? _synthLevels[ch] : 1; }
19942010
function setSynthLevel(val, send) {
19952011
const ch = Number(window.current_synth || 1);
1996-
val = Math.min(7, Math.max(0.001, Number(val) || 1));
2012+
val = Number(val);
2013+
if (!Number.isFinite(val)) val = 1;
2014+
val = Math.min(9.9, Math.max(0, val));
19972015
_synthLevels[ch] = val;
1998-
if (synthLevelSlider) synthLevelSlider.value = val;
2016+
if (synthLevelSlider) synthLevelSlider.value = levelValueToSlider(val);
19992017
if (send && typeof window.amy_add_log_message === "function") {
20002018
var code = "i" + ch + "v0a" + val.toFixed(3);
20012019
window.amy_add_log_message(code);
@@ -2011,17 +2029,18 @@ <h5 class="modal-title" id="adminTokenModalLabel">Admin Token Required</h5>
20112029
function syncSynthLevelForChannel() {
20122030
var ch = Number(window.current_synth || 1);
20132031
var val = getSynthLevel(ch);
2014-
if (synthLevelSlider) synthLevelSlider.value = val;
2032+
if (synthLevelSlider) synthLevelSlider.value = levelValueToSlider(val);
20152033
}
20162034
if (synthLevelSlider) {
2017-
synthLevelSlider.addEventListener("input", function() { setSynthLevel(synthLevelSlider.value, true); });
2035+
synthLevelSlider.addEventListener("input", function() { setSynthLevel(levelSliderToValue(synthLevelSlider.value), true); });
20182036
}
20192037
window.syncSynthLevelForChannel = syncSynthLevelForChannel;
20202038
window.setSynthLevelFromAmy = function(ch, val) {
2021-
val = Math.min(7, Math.max(0.001, Number(val) || 1));
2039+
val = Number(val);
2040+
if (!Number.isFinite(val) || val < 0) val = 1;
20222041
_synthLevels[ch] = val;
20232042
if (Number(window.current_synth || 1) === ch) {
2024-
if (synthLevelSlider) synthLevelSlider.value = val;
2043+
if (synthLevelSlider) synthLevelSlider.value = levelValueToSlider(val);
20252044
}
20262045
};
20272046
// Clicking the "Level" label opens the same MIDI-CC popup the knobs use,
@@ -2076,14 +2095,16 @@ <h5 class="modal-title" id="adminTokenModalLabel">Admin Token Required</h5>
20762095
var bus = currentBus();
20772096
var vols = Array.isArray(window.amy_bus_volumes) ? window.amy_bus_volumes : [];
20782097
var val = (vols[bus] != null) ? vols[bus] : 1;
2079-
if (fxLevelSlider) fxLevelSlider.value = val;
2098+
if (fxLevelSlider) fxLevelSlider.value = levelValueToSlider(val);
20802099
}
20812100
window.syncFxLevelForBus = syncFxLevelForBus;
20822101
function setFxLevel(val, send) {
20832102
var bus = currentBus();
2084-
val = Math.min(7, Math.max(0.001, Number(val) || 1));
2103+
val = Number(val);
2104+
if (!Number.isFinite(val)) val = 1;
2105+
val = Math.min(9.9, Math.max(0, val));
20852106
if (Array.isArray(window.amy_bus_volumes)) window.amy_bus_volumes[bus] = val;
2086-
if (fxLevelSlider) fxLevelSlider.value = val;
2107+
if (fxLevelSlider) fxLevelSlider.value = levelValueToSlider(val);
20872108
if (send && typeof window.amy_add_log_message === "function") {
20882109
// Bus-addressed volume: y<bus>V<val> sets that bus's mixdown gain.
20892110
var code = "y" + bus + "V" + val.toFixed(3);
@@ -2095,7 +2116,7 @@ <h5 class="modal-title" id="adminTokenModalLabel">Admin Token Required</h5>
20952116
}
20962117
}
20972118
if (fxLevelSlider) {
2098-
fxLevelSlider.addEventListener("input", function() { setFxLevel(fxLevelSlider.value, true); });
2119+
fxLevelSlider.addEventListener("input", function() { setFxLevel(levelSliderToValue(fxLevelSlider.value), true); });
20992120
}
21002121
// Clicking the Effects "Level" label opens the MIDI-CC popup for the
21012122
// bus's dedicated level knob (template i%iV%v — resolves the bus from

0 commit comments

Comments
 (0)