Initial: OPLabsBandChannel.lv2 + OPLabsBandBus.lv2
This commit is contained in:
@@ -0,0 +1,679 @@
|
||||
/* ===== OPLabs Band Bus - ModGUI Application ===== */
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// ---- Constants ----
|
||||
var NUM_CHANNELS = 8;
|
||||
var DB_MIN = -70; // meter min
|
||||
var DB_MIN_VOL = -60; // volume min
|
||||
var DB_MAX = 6; // max value (both vol and meter)
|
||||
var CHANNEL_NAMES = [
|
||||
'Ch 1', 'Ch 2', 'Ch 3', 'Ch 4',
|
||||
'Ch 5', 'Ch 6', 'Ch 7', 'Ch 8'
|
||||
];
|
||||
|
||||
// ---- State ----
|
||||
var state = {
|
||||
channels: [],
|
||||
masterVol: 0,
|
||||
masterMute: 0,
|
||||
masterLevelL: DB_MIN,
|
||||
masterLevelR: DB_MIN
|
||||
};
|
||||
|
||||
// Initialize channel state
|
||||
for (var i = 0; i < NUM_CHANNELS; i++) {
|
||||
state.channels.push({
|
||||
vol: 0,
|
||||
mute: 0,
|
||||
levelL: DB_MIN,
|
||||
levelR: DB_MIN
|
||||
});
|
||||
}
|
||||
|
||||
// Peak hold state
|
||||
var peakHold = {};
|
||||
var peakTimers = {};
|
||||
var PEAK_HOLD_MS = 1500;
|
||||
|
||||
// Drag state for faders
|
||||
var dragState = null;
|
||||
|
||||
// ---- DOM References ----
|
||||
var els = {};
|
||||
|
||||
// ---- Build Channel Strips ----
|
||||
function buildChannelStrips() {
|
||||
var container = document.getElementById('busChannels');
|
||||
for (var i = 0; i < NUM_CHANNELS; i++) {
|
||||
var ch = document.createElement('div');
|
||||
ch.className = 'ch-strip';
|
||||
ch.dataset.channel = i;
|
||||
ch.id = 'chStrip' + i;
|
||||
|
||||
// Label
|
||||
var label = document.createElement('div');
|
||||
label.className = 'ch-strip-label';
|
||||
label.textContent = CHANNEL_NAMES[i];
|
||||
|
||||
// Mute button
|
||||
var muteBtn = document.createElement('button');
|
||||
muteBtn.className = 'ch-strip-mute';
|
||||
muteBtn.id = 'chMute' + i;
|
||||
muteBtn.title = 'Mute ' + CHANNEL_NAMES[i];
|
||||
muteBtn.textContent = 'M';
|
||||
muteBtn.addEventListener('click', makeMuteHandler(i));
|
||||
|
||||
// Mini stereo meter
|
||||
var meterDiv = document.createElement('div');
|
||||
meterDiv.className = 'ch-strip-meter';
|
||||
|
||||
var meterL = makeMiniMeter('ch' + i + 'L');
|
||||
var meterR = makeMiniMeter('ch' + i + 'R');
|
||||
meterDiv.appendChild(meterL);
|
||||
meterDiv.appendChild(meterR);
|
||||
|
||||
// Mini fader
|
||||
var faderDiv = document.createElement('div');
|
||||
faderDiv.className = 'ch-strip-fader';
|
||||
|
||||
var faderLabel = document.createElement('div');
|
||||
faderLabel.className = 'fader-label';
|
||||
faderLabel.textContent = 'Vol';
|
||||
|
||||
var faderTrack = document.createElement('div');
|
||||
faderTrack.className = 'fader-track';
|
||||
faderTrack.id = 'chFaderTrack' + i;
|
||||
|
||||
var faderFill = document.createElement('div');
|
||||
faderFill.className = 'fader-fill';
|
||||
faderFill.id = 'chFaderFill' + i;
|
||||
|
||||
var faderThumb = document.createElement('div');
|
||||
faderThumb.className = 'fader-thumb';
|
||||
faderThumb.id = 'chFaderThumb' + i;
|
||||
faderThumb.addEventListener('pointerdown', makeFaderPointerDown(i));
|
||||
faderThumb.addEventListener('touchstart', makeFaderTouchStart(i), { passive: false });
|
||||
|
||||
faderTrack.appendChild(faderFill);
|
||||
faderTrack.appendChild(faderThumb);
|
||||
|
||||
var faderValue = document.createElement('div');
|
||||
faderValue.className = 'fader-value';
|
||||
faderValue.id = 'chFaderValue' + i;
|
||||
faderValue.textContent = '0.0';
|
||||
|
||||
faderDiv.appendChild(faderLabel);
|
||||
faderDiv.appendChild(faderTrack);
|
||||
faderDiv.appendChild(faderValue);
|
||||
|
||||
// Assemble
|
||||
ch.appendChild(label);
|
||||
ch.appendChild(muteBtn);
|
||||
ch.appendChild(meterDiv);
|
||||
ch.appendChild(faderDiv);
|
||||
container.appendChild(ch);
|
||||
|
||||
// Cache DOM refs
|
||||
els['chMute' + i] = muteBtn;
|
||||
els['chMeterFill' + i + 'L'] = meterL.querySelector('.meter-fill');
|
||||
els['chMeterFill' + i + 'R'] = meterR.querySelector('.meter-fill');
|
||||
els['chMeterPeak' + i + 'L'] = meterL.querySelector('.meter-peak');
|
||||
els['chMeterPeak' + i + 'R'] = meterR.querySelector('.meter-peak');
|
||||
els['chFaderFill' + i] = faderFill;
|
||||
els['chFaderThumb' + i] = faderThumb;
|
||||
els['chFaderValue' + i] = faderValue;
|
||||
els['chStrip' + i] = ch;
|
||||
|
||||
// Init peak hold
|
||||
peakHold['ch' + i + 'L'] = DB_MIN;
|
||||
peakHold['ch' + i + 'R'] = DB_MIN;
|
||||
peakTimers['ch' + i + 'L'] = null;
|
||||
peakTimers['ch' + i + 'R'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
function makeMiniMeter(idPrefix) {
|
||||
var div = document.createElement('div');
|
||||
div.className = 'meter-channel';
|
||||
|
||||
var label = document.createElement('span');
|
||||
label.className = 'meter-label';
|
||||
label.textContent = idPrefix.charAt(idPrefix.length - 1); // L or R
|
||||
|
||||
var track = document.createElement('div');
|
||||
track.className = 'meter-track';
|
||||
|
||||
var fill = document.createElement('div');
|
||||
fill.className = 'meter-fill';
|
||||
|
||||
var peak = document.createElement('div');
|
||||
peak.className = 'meter-peak';
|
||||
|
||||
var tick20 = document.createElement('div');
|
||||
tick20.className = 'meter-tick';
|
||||
tick20.style.bottom = '80%';
|
||||
var tick50 = document.createElement('div');
|
||||
tick50.className = 'meter-tick';
|
||||
tick50.style.bottom = '50%';
|
||||
var tick80 = document.createElement('div');
|
||||
tick80.className = 'meter-tick';
|
||||
tick80.style.bottom = '20%';
|
||||
|
||||
track.appendChild(fill);
|
||||
track.appendChild(peak);
|
||||
track.appendChild(tick20);
|
||||
track.appendChild(tick50);
|
||||
track.appendChild(tick80);
|
||||
div.appendChild(label);
|
||||
div.appendChild(track);
|
||||
return div;
|
||||
}
|
||||
|
||||
// ---- Mute button handler factory ----
|
||||
function makeMuteHandler(chIdx) {
|
||||
return function () {
|
||||
var symbol = 'ch' + (chIdx + 1) + 'Mute';
|
||||
var newVal = state.channels[chIdx].mute >= 0.5 ? 0 : 1;
|
||||
state.channels[chIdx].mute = newVal;
|
||||
sendValue(symbol, newVal);
|
||||
updateChannelMute(chIdx, newVal);
|
||||
};
|
||||
}
|
||||
|
||||
// ---- Fader drag handler factories ----
|
||||
function makeFaderPointerDown(chIdx) {
|
||||
return function (e) {
|
||||
e.preventDefault();
|
||||
startFaderDrag(chIdx, e.clientY);
|
||||
};
|
||||
}
|
||||
|
||||
function makeFaderTouchStart(chIdx) {
|
||||
return function (e) {
|
||||
e.preventDefault();
|
||||
startFaderDrag(chIdx, e.touches[0].clientY);
|
||||
};
|
||||
}
|
||||
|
||||
function startFaderDrag(chIdx, clientY) {
|
||||
if (dragState) return;
|
||||
var symbol = 'ch' + (chIdx + 1) + 'Vol';
|
||||
dragState = {
|
||||
chIdx: chIdx,
|
||||
symbol: symbol,
|
||||
startY: clientY,
|
||||
startVal: state.channels[chIdx].vol
|
||||
};
|
||||
document.addEventListener('pointermove', onFaderPointerMove);
|
||||
document.addEventListener('pointerup', onFaderPointerUp);
|
||||
document.addEventListener('touchmove', onFaderTouchMove, { passive: false });
|
||||
document.addEventListener('touchend', onFaderPointerUp);
|
||||
}
|
||||
|
||||
function onFaderPointerMove(e) {
|
||||
if (!dragState) return;
|
||||
e.preventDefault();
|
||||
dragFader(e.clientY);
|
||||
}
|
||||
|
||||
function onFaderTouchMove(e) {
|
||||
if (!dragState) return;
|
||||
e.preventDefault();
|
||||
dragFader(e.touches[0].clientY);
|
||||
}
|
||||
|
||||
function dragFader(clientY) {
|
||||
var d = dragState;
|
||||
var deltaY = d.startY - clientY;
|
||||
var range = DB_MAX - DB_MIN_VOL; // 66
|
||||
var deltaVal = (deltaY / 200) * range; // 200px for full range
|
||||
var newVal = clamp(d.startVal + deltaVal, DB_MIN_VOL, DB_MAX);
|
||||
// Snap to 0.5 dB steps
|
||||
newVal = Math.round(newVal * 2) / 2;
|
||||
newVal = clamp(newVal, DB_MIN_VOL, DB_MAX);
|
||||
|
||||
state.channels[d.chIdx].vol = newVal;
|
||||
sendValue(d.symbol, newVal);
|
||||
updateChannelFader(d.chIdx, newVal);
|
||||
}
|
||||
|
||||
function onFaderPointerUp() {
|
||||
if (!dragState) return;
|
||||
dragState = null;
|
||||
document.removeEventListener('pointermove', onFaderPointerMove);
|
||||
document.removeEventListener('pointerup', onFaderPointerUp);
|
||||
document.removeEventListener('touchmove', onFaderTouchMove);
|
||||
document.removeEventListener('touchend', onFaderPointerUp);
|
||||
}
|
||||
|
||||
// ---- Master fader drag ----
|
||||
function masterFaderPointerDown(e) {
|
||||
e.preventDefault();
|
||||
if (dragState) return;
|
||||
dragState = {
|
||||
master: true,
|
||||
startY: e.clientY,
|
||||
startVal: state.masterVol
|
||||
};
|
||||
document.addEventListener('pointermove', onMasterFaderMove);
|
||||
document.addEventListener('pointerup', onMasterFaderUp);
|
||||
document.addEventListener('touchmove', onMasterFaderTouchMove, { passive: false });
|
||||
document.addEventListener('touchend', onMasterFaderUp);
|
||||
}
|
||||
|
||||
function masterFaderTouchStart(e) {
|
||||
e.preventDefault();
|
||||
if (dragState) return;
|
||||
dragState = {
|
||||
master: true,
|
||||
startY: e.touches[0].clientY,
|
||||
startVal: state.masterVol
|
||||
};
|
||||
document.addEventListener('pointermove', onMasterFaderMove);
|
||||
document.addEventListener('pointerup', onMasterFaderUp);
|
||||
document.addEventListener('touchmove', onMasterFaderTouchMove, { passive: false });
|
||||
document.addEventListener('touchend', onMasterFaderUp);
|
||||
}
|
||||
|
||||
function onMasterFaderMove(e) { if (dragState) { e.preventDefault(); dragMasterFader(e.clientY); } }
|
||||
function onMasterFaderTouchMove(e) { if (dragState) { e.preventDefault(); dragMasterFader(e.touches[0].clientY); } }
|
||||
|
||||
function dragMasterFader(clientY) {
|
||||
var d = dragState;
|
||||
var deltaY = d.startY - clientY;
|
||||
var range = DB_MAX - DB_MIN_VOL;
|
||||
var deltaVal = (deltaY / 200) * range;
|
||||
var newVal = clamp(d.startVal + deltaVal, DB_MIN_VOL, DB_MAX);
|
||||
newVal = Math.round(newVal * 2) / 2;
|
||||
newVal = clamp(newVal, DB_MIN_VOL, DB_MAX);
|
||||
|
||||
state.masterVol = newVal;
|
||||
sendValue('masterVol', newVal);
|
||||
updateMasterFader(newVal);
|
||||
}
|
||||
|
||||
function onMasterFaderUp() {
|
||||
if (!dragState) return;
|
||||
dragState = null;
|
||||
document.removeEventListener('pointermove', onMasterFaderMove);
|
||||
document.removeEventListener('pointerup', onMasterFaderUp);
|
||||
document.removeEventListener('touchmove', onMasterFaderTouchMove);
|
||||
document.removeEventListener('touchend', onMasterFaderUp);
|
||||
}
|
||||
|
||||
// ---- Cache static DOM elements ----
|
||||
function cacheElements() {
|
||||
els.masterBtnMute = document.getElementById('masterBtnMute');
|
||||
els.masterMeterFillL = document.getElementById('masterMeterFillL');
|
||||
els.masterMeterFillR = document.getElementById('masterMeterFillR');
|
||||
els.masterMeterPeakL = document.getElementById('masterMeterPeakL');
|
||||
els.masterMeterPeakR = document.getElementById('masterMeterPeakR');
|
||||
els.masterFaderFill = document.getElementById('masterFaderFill');
|
||||
els.masterFaderThumb = document.getElementById('masterFaderThumb');
|
||||
els.masterFaderReadout = document.getElementById('masterFaderReadout');
|
||||
els.masterDbReadout = document.getElementById('masterDbReadout');
|
||||
els.masterSection = document.querySelector('.master-section');
|
||||
els.masterFaderTrack = document.getElementById('masterFaderTrack');
|
||||
|
||||
// Init peak holds for master
|
||||
peakHold['masterL'] = DB_MIN;
|
||||
peakHold['masterR'] = DB_MIN;
|
||||
peakTimers['masterL'] = null;
|
||||
peakTimers['masterR'] = null;
|
||||
}
|
||||
|
||||
// ---- Utility ----
|
||||
function clamp(val, min, max) {
|
||||
return Math.min(max, Math.max(min, val));
|
||||
}
|
||||
|
||||
function dbToPercent(db) {
|
||||
var clamped = clamp(db, DB_MIN, DB_MAX);
|
||||
var normalized = (clamped - DB_MIN) / (DB_MAX - DB_MIN);
|
||||
return Math.sqrt(normalized) * 100;
|
||||
}
|
||||
|
||||
function getMeterColor(pct) {
|
||||
if (pct < 60) return 'var(--meter-green)';
|
||||
if (pct < 85) return 'var(--meter-yellow)';
|
||||
return 'var(--meter-red)';
|
||||
}
|
||||
|
||||
// ---- Send value to host ----
|
||||
function sendValue(symbol, value) {
|
||||
if (window.modguijs && typeof window.modguijs.setValue === 'function') {
|
||||
window.modguijs.setValue(symbol, value);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Update functions ----
|
||||
|
||||
// Channel mute
|
||||
function updateChannelMute(chIdx, val) {
|
||||
var active = val >= 0.5;
|
||||
var btn = els['chMute' + chIdx];
|
||||
var strip = els['chStrip' + chIdx];
|
||||
btn.classList.toggle('active', active);
|
||||
strip.classList.toggle('muted', active);
|
||||
}
|
||||
|
||||
// Channel fader
|
||||
function updateChannelFader(chIdx, val) {
|
||||
var pct = dbToPercent(val);
|
||||
var fill = els['chFaderFill' + chIdx];
|
||||
var thumb = els['chFaderThumb' + chIdx];
|
||||
var valueEl = els['chFaderValue' + chIdx];
|
||||
|
||||
// Fader fill: percentage from bottom
|
||||
fill.style.height = Math.min(100, pct) + '%';
|
||||
|
||||
// Thumb position: also from bottom
|
||||
var thumbPct = Math.min(100, pct);
|
||||
thumb.style.bottom = 'calc(' + thumbPct + '% - 3px)';
|
||||
|
||||
// Display
|
||||
var display = val <= DB_MIN_VOL ? '-∞' : val.toFixed(1);
|
||||
valueEl.textContent = display;
|
||||
}
|
||||
|
||||
// Channel meter
|
||||
function updateChannelMeter(chIdx) {
|
||||
var ch = state.channels[chIdx];
|
||||
updateMeterBar('ch' + chIdx, 'L', ch.levelL, els['chMeterFill' + chIdx + 'L'], els['chMeterPeak' + chIdx + 'L']);
|
||||
updateMeterBar('ch' + chIdx, 'R', ch.levelR, els['chMeterFill' + chIdx + 'R'], els['chMeterPeak' + chIdx + 'R']);
|
||||
}
|
||||
|
||||
function updateMeterBar(chKey, side, level, fillEl, peakEl) {
|
||||
var pct = dbToPercent(level);
|
||||
fillEl.style.height = Math.min(100, pct) + '%';
|
||||
fillEl.style.background = getMeterColor(pct);
|
||||
fillEl.className = 'meter-fill' + (pct >= 85 ? ' hot' : pct >= 60 ? ' warm' : '');
|
||||
|
||||
// Peak hold
|
||||
var pkKey = chKey + side;
|
||||
if (level > peakHold[pkKey]) {
|
||||
peakHold[pkKey] = level;
|
||||
if (peakTimers[pkKey]) { clearTimeout(peakTimers[pkKey]); }
|
||||
peakTimers[pkKey] = setTimeout(function () {
|
||||
peakHold[pkKey] = DB_MIN;
|
||||
var pkPct = dbToPercent(DB_MIN);
|
||||
peakEl.style.bottom = Math.min(100, pkPct) + '%';
|
||||
}, PEAK_HOLD_MS);
|
||||
}
|
||||
peakEl.style.bottom = Math.min(100, pct) + '%';
|
||||
}
|
||||
|
||||
// Master mute
|
||||
function updateMasterMute(val) {
|
||||
var active = val >= 0.5;
|
||||
els.masterBtnMute.classList.toggle('active', active);
|
||||
els.masterSection.classList.toggle('muted', active);
|
||||
}
|
||||
|
||||
// Master fader
|
||||
function updateMasterFader(val) {
|
||||
var pct = dbToPercent(val);
|
||||
els.masterFaderFill.style.height = Math.min(100, pct) + '%';
|
||||
var thumbPct = Math.min(100, pct);
|
||||
els.masterFaderThumb.style.bottom = 'calc(' + thumbPct + '% - 4px)';
|
||||
var display = val <= DB_MIN_VOL ? '-∞' : val.toFixed(1);
|
||||
els.masterFaderReadout.textContent = display;
|
||||
}
|
||||
|
||||
// Master meter
|
||||
function updateMasterMeter() {
|
||||
updateMeterBar('master', 'L', state.masterLevelL, els.masterMeterFillL, els.masterMeterPeakL);
|
||||
updateMeterBar('master', 'R', state.masterLevelR, els.masterMeterFillR, els.masterMeterPeakR);
|
||||
|
||||
// dB readout
|
||||
var dbL = state.masterLevelL <= DB_MIN ? '-∞' : state.masterLevelL.toFixed(1);
|
||||
var dbR = state.masterLevelR <= DB_MIN ? '-∞' : state.masterLevelR.toFixed(1);
|
||||
els.masterDbReadout.innerHTML =
|
||||
'L: <span class="' + (state.masterLevelL > -10 ? 'hot' : '') + '">' + dbL + '</span>' +
|
||||
' R: <span class="' + (state.masterLevelR > -10 ? 'hot' : '') + '">' + dbR + '</span>';
|
||||
}
|
||||
|
||||
// ---- Full UI update ----
|
||||
function updateAll() {
|
||||
// Channels
|
||||
for (var i = 0; i < NUM_CHANNELS; i++) {
|
||||
updateChannelMute(i, state.channels[i].mute);
|
||||
updateChannelFader(i, state.channels[i].vol);
|
||||
updateChannelMeter(i);
|
||||
}
|
||||
// Master
|
||||
updateMasterMute(state.masterMute);
|
||||
updateMasterFader(state.masterVol);
|
||||
updateMasterMeter();
|
||||
}
|
||||
|
||||
// ---- UI Event Handlers ----
|
||||
function setupUIHandlers() {
|
||||
// Master mute button
|
||||
els.masterBtnMute.addEventListener('click', function () {
|
||||
var newVal = state.masterMute >= 0.5 ? 0 : 1;
|
||||
state.masterMute = newVal;
|
||||
sendValue('masterMute', newVal);
|
||||
updateMasterMute(newVal);
|
||||
});
|
||||
|
||||
// Master fader
|
||||
els.masterFaderThumb.addEventListener('pointerdown', masterFaderPointerDown);
|
||||
els.masterFaderThumb.addEventListener('touchstart', masterFaderTouchStart, { passive: false });
|
||||
}
|
||||
|
||||
// ---- ModGUI API Integration ----
|
||||
function setupModGUI() {
|
||||
var paramSymbols = [];
|
||||
// Per-channel params
|
||||
for (var i = 1; i <= NUM_CHANNELS; i++) {
|
||||
paramSymbols.push('ch' + i + 'Vol');
|
||||
paramSymbols.push('ch' + i + 'Mute');
|
||||
}
|
||||
// Master params
|
||||
paramSymbols.push('masterVol');
|
||||
paramSymbols.push('masterMute');
|
||||
|
||||
// Meter symbols (output only, for reading)
|
||||
var meterSymbols = [];
|
||||
for (var i = 1; i <= NUM_CHANNELS; i++) {
|
||||
meterSymbols.push('ch' + i + 'LevelL');
|
||||
meterSymbols.push('ch' + i + 'LevelR');
|
||||
}
|
||||
meterSymbols.push('masterLevelL');
|
||||
meterSymbols.push('masterLevelR');
|
||||
|
||||
// Helper to get channel index from port symbol
|
||||
function chIdxFromSymbol(symbol) {
|
||||
var m = symbol.match(/^ch(\d+)/);
|
||||
return m ? (parseInt(m[1], 10) - 1) : -1;
|
||||
}
|
||||
|
||||
if (window.modguijs) {
|
||||
// Read initial values
|
||||
var allSymbols = paramSymbols.concat(meterSymbols);
|
||||
allSymbols.forEach(function (symbol) {
|
||||
if (typeof window.modguijs.getValue === 'function') {
|
||||
try {
|
||||
var val = window.modguijs.getValue(symbol);
|
||||
if (val !== undefined && val !== null) {
|
||||
applyValue(symbol, val);
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
});
|
||||
|
||||
// Subscribe to changes
|
||||
if (typeof window.modguijs.onChange === 'function') {
|
||||
allSymbols.forEach(function (symbol) {
|
||||
window.modguijs.onChange(symbol, function (value) {
|
||||
applyValue(symbol, value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Fallback polling if no onChange
|
||||
if (typeof window.modguijs.onChange !== 'function') {
|
||||
startPolling(paramSymbols, meterSymbols);
|
||||
}
|
||||
} else {
|
||||
// Retry after delay
|
||||
setTimeout(function () {
|
||||
if (window.modguijs) {
|
||||
setupModGUI();
|
||||
} else {
|
||||
startDemoMode();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Apply a value to state and update UI ----
|
||||
function applyValue(symbol, value) {
|
||||
var m = symbol.match(/^ch(\d+)(Vol|Mute|Level[LR])$/);
|
||||
if (m) {
|
||||
var chIdx = parseInt(m[1], 10) - 1;
|
||||
var param = m[2];
|
||||
if (chIdx >= 0 && chIdx < NUM_CHANNELS) {
|
||||
if (param === 'Vol') {
|
||||
state.channels[chIdx].vol = value;
|
||||
updateChannelFader(chIdx, value);
|
||||
} else if (param === 'Mute') {
|
||||
state.channels[chIdx].mute = value;
|
||||
updateChannelMute(chIdx, value);
|
||||
} else if (param === 'LevelL') {
|
||||
state.channels[chIdx].levelL = value;
|
||||
updateChannelMeter(chIdx);
|
||||
} else if (param === 'LevelR') {
|
||||
state.channels[chIdx].levelR = value;
|
||||
updateChannelMeter(chIdx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Master params
|
||||
if (symbol === 'masterVol') {
|
||||
state.masterVol = value;
|
||||
updateMasterFader(value);
|
||||
} else if (symbol === 'masterMute') {
|
||||
state.masterMute = value;
|
||||
updateMasterMute(value);
|
||||
} else if (symbol === 'masterLevelL') {
|
||||
state.masterLevelL = value;
|
||||
updateMasterMeter();
|
||||
} else if (symbol === 'masterLevelR') {
|
||||
state.masterLevelR = value;
|
||||
updateMasterMeter();
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Polling fallback ----
|
||||
var pollInterval = null;
|
||||
var pollSymbols = [];
|
||||
|
||||
function startPolling(paramSymbols, meterSymbols) {
|
||||
pollSymbols = paramSymbols.concat(meterSymbols);
|
||||
if (pollInterval) return;
|
||||
pollInterval = setInterval(function () {
|
||||
if (!window.modguijs || typeof window.modguijs.getValue !== 'function') return;
|
||||
pollSymbols.forEach(function (symbol) {
|
||||
try {
|
||||
var val = window.modguijs.getValue(symbol);
|
||||
if (val !== undefined && val !== null) {
|
||||
// Only update if changed
|
||||
var current = getCurrentValue(symbol);
|
||||
if (current === undefined || Math.abs(val - current) > 0.001) {
|
||||
applyValue(symbol, val);
|
||||
}
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
|
||||
function getCurrentValue(symbol) {
|
||||
var m = symbol.match(/^ch(\d+)(Vol|Mute|Level[LR])$/);
|
||||
if (m) {
|
||||
var chIdx = parseInt(m[1], 10) - 1;
|
||||
if (chIdx >= 0 && chIdx < NUM_CHANNELS) {
|
||||
var param = m[2];
|
||||
if (param === 'Vol') return state.channels[chIdx].vol;
|
||||
if (param === 'Mute') return state.channels[chIdx].mute;
|
||||
if (param === 'LevelL') return state.channels[chIdx].levelL;
|
||||
if (param === 'LevelR') return state.channels[chIdx].levelR;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (symbol === 'masterVol') return state.masterVol;
|
||||
if (symbol === 'masterMute') return state.masterMute;
|
||||
if (symbol === 'masterLevelL') return state.masterLevelL;
|
||||
if (symbol === 'masterLevelR') return state.masterLevelR;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// ---- Demo mode ----
|
||||
var demoInterval = null;
|
||||
|
||||
function startDemoMode() {
|
||||
console.log('OPLabs Band Bus: No modguijs API found, running demo mode');
|
||||
var t = 0;
|
||||
demoInterval = setInterval(function () {
|
||||
t += 0.05;
|
||||
// Simulate different levels per channel
|
||||
for (var i = 0; i < NUM_CHANNELS; i++) {
|
||||
var offset = i * 0.3 + Math.sin(t * 0.1 + i) * 0.2;
|
||||
var lvl = -50 + 45 * (Math.sin(t + offset) * 0.5 + 0.5);
|
||||
var lvr = -50 + 45 * (Math.cos(t * 0.7 + offset) * 0.5 + 0.5);
|
||||
state.channels[i].levelL = lvl;
|
||||
state.channels[i].levelR = lvr;
|
||||
updateChannelMeter(i);
|
||||
}
|
||||
state.masterLevelL = -30 + 25 * (Math.sin(t * 0.5) * 0.5 + 0.5);
|
||||
state.masterLevelR = -30 + 25 * (Math.cos(t * 0.5 + 0.3) * 0.5 + 0.5);
|
||||
updateMasterMeter();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// ---- Initialization ----
|
||||
function init() {
|
||||
buildChannelStrips();
|
||||
cacheElements();
|
||||
setupUIHandlers();
|
||||
|
||||
// Master fader initial position
|
||||
updateMasterFader(state.masterVol);
|
||||
|
||||
// Draw initial state
|
||||
updateAll();
|
||||
|
||||
// Connect to modguijs
|
||||
setupModGUI();
|
||||
}
|
||||
|
||||
// Wait for DOM
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
// Clean up on unload
|
||||
window.addEventListener('beforeunload', function () {
|
||||
if (pollInterval) clearInterval(pollInterval);
|
||||
if (demoInterval) clearInterval(demoInterval);
|
||||
// Clear all peak timers
|
||||
for (var key in peakTimers) {
|
||||
if (peakTimers[key]) clearTimeout(peakTimers[key]);
|
||||
}
|
||||
if (dragState) {
|
||||
document.removeEventListener('pointermove', onFaderPointerMove);
|
||||
document.removeEventListener('pointerup', onFaderPointerUp);
|
||||
document.removeEventListener('touchmove', onFaderTouchMove);
|
||||
document.removeEventListener('touchend', onFaderPointerUp);
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user