Skip to content

Commit c3a8be7

Browse files
committed
learncc
1 parent ed570f0 commit c3a8be7

2 files changed

Lines changed: 164 additions & 1 deletion

File tree

tulip/amyboardweb/static/editor_knobs.js

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,59 @@ function init_knobs(knobConfigs, gridId, onChange) {
117117
title.style.marginBottom = "4px";
118118
label.style.marginBottom = "6px";
119119

120+
const rangeWrap = document.createElement("div");
121+
rangeWrap.style.display = "none";
122+
rangeWrap.style.marginBottom = "6px";
123+
124+
const rangeRow = document.createElement("div");
125+
rangeRow.style.display = "flex";
126+
rangeRow.style.gap = "6px";
127+
rangeRow.style.marginBottom = "6px";
128+
129+
const minField = document.createElement("div");
130+
minField.style.flex = "1";
131+
const minLabel = document.createElement("div");
132+
minLabel.textContent = "min_value";
133+
minLabel.style.fontSize = "11px";
134+
minLabel.style.marginBottom = "2px";
135+
const minInput = document.createElement("input");
136+
minInput.type = "text";
137+
minInput.style.width = "100%";
138+
minInput.style.boxSizing = "border-box";
139+
minField.appendChild(minLabel);
140+
minField.appendChild(minInput);
141+
142+
const maxField = document.createElement("div");
143+
maxField.style.flex = "1";
144+
const maxLabel = document.createElement("div");
145+
maxLabel.textContent = "max_value";
146+
maxLabel.style.fontSize = "11px";
147+
maxLabel.style.marginBottom = "2px";
148+
const maxInput = document.createElement("input");
149+
maxInput.type = "text";
150+
maxInput.style.width = "100%";
151+
maxInput.style.boxSizing = "border-box";
152+
maxField.appendChild(maxLabel);
153+
maxField.appendChild(maxInput);
154+
155+
rangeRow.appendChild(minField);
156+
rangeRow.appendChild(maxField);
157+
158+
const logRow = document.createElement("label");
159+
logRow.style.display = "flex";
160+
logRow.style.alignItems = "center";
161+
logRow.style.gap = "6px";
162+
logRow.style.fontSize = "11px";
163+
const logCheckbox = document.createElement("input");
164+
logCheckbox.type = "checkbox";
165+
const logText = document.createElement("span");
166+
logText.textContent = "log";
167+
logRow.appendChild(logCheckbox);
168+
logRow.appendChild(logText);
169+
170+
rangeWrap.appendChild(rangeRow);
171+
rangeWrap.appendChild(logRow);
172+
120173
const input = document.createElement("input");
121174
input.type = "text";
122175
input.style.width = "100%";
@@ -129,6 +182,12 @@ function init_knobs(knobConfigs, gridId, onChange) {
129182
error.style.minHeight = "14px";
130183
error.style.marginBottom = "6px";
131184

185+
const learn = document.createElement("button");
186+
learn.type = "button";
187+
learn.textContent = "Learn";
188+
learn.style.fontSize = "11px";
189+
learn.style.marginBottom = "6px";
190+
132191
const actions = document.createElement("div");
133192
actions.style.display = "flex";
134193
actions.style.gap = "6px";
@@ -147,13 +206,66 @@ function init_knobs(knobConfigs, gridId, onChange) {
147206
actions.appendChild(cancel);
148207
actions.appendChild(save);
149208
container.appendChild(title);
209+
container.appendChild(rangeWrap);
150210
container.appendChild(label);
151211
container.appendChild(input);
212+
container.appendChild(learn);
152213
container.appendChild(error);
153214
container.appendChild(actions);
154215
document.body.appendChild(container);
155216

156-
editor = { container: container, input: input, error: error, save: save, cancel: cancel, current: null, title: title };
217+
editor = { container: container, input: input, error: error, save: save, cancel: cancel, learn: learn, current: null, title: title, learning: false, minInput: minInput, maxInput: maxInput, logCheckbox: logCheckbox, rangeWrap: rangeWrap };
218+
219+
function setLearnActive(active) {
220+
editor.learning = active === true;
221+
if (editor.learn) {
222+
editor.learn.style.backgroundColor = editor.learning ? "#1fd17a" : "";
223+
editor.learn.style.color = editor.learning ? "#111" : "";
224+
}
225+
if (!editor.learning && window.cc_learn_handler) {
226+
window.cc_learn_handler = null;
227+
}
228+
}
229+
230+
function parseNumberField(fieldValue) {
231+
if (fieldValue.trim() === "") {
232+
return null;
233+
}
234+
const parsed = Number(fieldValue);
235+
return Number.isFinite(parsed) ? parsed : null;
236+
}
237+
238+
function applyRangeUpdates() {
239+
if (!editor.rangeWrap || editor.rangeWrap.style.display === "none") {
240+
return true;
241+
}
242+
const minValue = parseNumberField(editor.minInput.value);
243+
const maxValue = parseNumberField(editor.maxInput.value);
244+
if (minValue === null || maxValue === null) {
245+
editor.error.textContent = "Enter numeric min/max values.";
246+
return false;
247+
}
248+
if (minValue >= maxValue) {
249+
editor.error.textContent = "min_value must be less than max_value.";
250+
return false;
251+
}
252+
const prevMin = editor.current.min_value;
253+
const prevMax = editor.current.max_value;
254+
const prevType = editor.current.knob_type;
255+
editor.current.min_value = minValue;
256+
editor.current.max_value = maxValue;
257+
if (editor.logCheckbox && editor.logCheckbox.checked) {
258+
editor.current.knob_type = "log";
259+
} else if (editor.current.knob_type === "log") {
260+
editor.current.knob_type = undefined;
261+
}
262+
const rangeChanged = prevMin !== editor.current.min_value || prevMax !== editor.current.max_value;
263+
const typeChanged = prevType !== editor.current.knob_type;
264+
if ((rangeChanged || typeChanged) && typeof window.refresh_knobs_for_channel === "function") {
265+
window.refresh_knobs_for_channel();
266+
}
267+
return true;
268+
}
157269

158270
function applyValue(value) {
159271
if (!editor.current) {
@@ -169,6 +281,9 @@ function init_knobs(knobConfigs, gridId, onChange) {
169281
save.addEventListener("click", function() {
170282
const trimmed = editor.input.value.trim();
171283
if (trimmed === "") {
284+
if (!applyRangeUpdates()) {
285+
return;
286+
}
172287
applyValue("");
173288
return;
174289
}
@@ -177,13 +292,38 @@ function init_knobs(knobConfigs, gridId, onChange) {
177292
editor.error.textContent = "Enter an integer 0-127.";
178293
return;
179294
}
295+
if (!applyRangeUpdates()) {
296+
return;
297+
}
180298
applyValue(parsed);
181299
});
182300

183301
cancel.addEventListener("click", function() {
184302
hideCcEditor(editor);
185303
});
186304

305+
learn.addEventListener("click", function() {
306+
if (!editor.current) {
307+
return;
308+
}
309+
if (editor.learning) {
310+
setLearnActive(false);
311+
return;
312+
}
313+
setLearnActive(true);
314+
window.cc_learn_handler = function(cc) {
315+
if (!editor.current) {
316+
setLearnActive(false);
317+
return;
318+
}
319+
editor.input.value = String(cc);
320+
editor.error.textContent = "";
321+
setLearnActive(false);
322+
editor.input.focus();
323+
editor.input.select();
324+
};
325+
});
326+
187327
input.addEventListener("keydown", function(event) {
188328
if (event.key === "Enter") {
189329
event.preventDefault();
@@ -206,6 +346,16 @@ function init_knobs(knobConfigs, gridId, onChange) {
206346
})();
207347

208348
function hideCcEditor(editor) {
349+
if (editor && editor.learning) {
350+
editor.learning = false;
351+
if (editor.learn) {
352+
editor.learn.style.backgroundColor = "";
353+
editor.learn.style.color = "";
354+
}
355+
if (window.cc_learn_handler) {
356+
window.cc_learn_handler = null;
357+
}
358+
}
209359
editor.container.style.display = "none";
210360
editor.current = null;
211361
}
@@ -234,6 +384,15 @@ function init_knobs(knobConfigs, gridId, onChange) {
234384
editor.input.value = knobConfig.cc === "" || knobConfig.cc === null || knobConfig.cc === undefined
235385
? ""
236386
: String(knobConfig.cc);
387+
const isRangeType = !knobConfig.knob_type || knobConfig.knob_type === "log";
388+
editor.rangeWrap.style.display = isRangeType ? "block" : "none";
389+
if (isRangeType) {
390+
const minValue = Number.isFinite(Number(knobConfig.min_value)) ? String(knobConfig.min_value) : "";
391+
const maxValue = Number.isFinite(Number(knobConfig.max_value)) ? String(knobConfig.max_value) : "";
392+
editor.minInput.value = minValue;
393+
editor.maxInput.value = maxValue;
394+
editor.logCheckbox.checked = knobConfig.knob_type === "log";
395+
}
237396
positionCcEditor(editor, labelEl);
238397
editor.container.style.display = "block";
239398
editor.input.focus();

tulip/amyboardweb/static/spss.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,10 @@ window.amy_cv_knob_change = function(index, value) {
320320
function move_knob(channel, cc, value) {
321321
// Hook for MIDI CC -> knob mapping.
322322
const knobList = window.get_current_knobs ? window.get_current_knobs() : [];
323+
if (window.cc_learn_handler && channel === window.current_synth) {
324+
window.cc_learn_handler(cc);
325+
return;
326+
}
323327
if (channel !== window.current_synth || !Array.isArray(knobList)) {
324328
return;
325329
}

0 commit comments

Comments
 (0)