ativar/desativar tracks de áudio na playlist, knobs funcionais

This commit is contained in:
JotaChina committed 2025-12-27 23:21:58 -03:00
1 parent 73a5257aec
commit ce9a9dfda8
2 files changed
+323 -33

No files matched your search

+136 -2
View File
@@ -20,6 +20,73 @@ import {
getSecondsPerStep,
} from "../utils.js";
import { sendAction, sendActionSafe } from "../socket.js";
import { setPatternTrackMute, setPatternTrackVolume, setPatternTrackPan } from "../pattern/pattern_state.js";
import { initializeAudioContext } from "../audio.js";
// --- Knob helpers (mesmo comportamento do pattern_ui.js) ---
const _KNOB_MIN_DEG = -135;
const _KNOB_MAX_DEG = 135;
const _MAX_TRACK_VOLUME = 1.5;
function _clampKnob(n, min, max) {
const x = Number(n);
if (!Number.isFinite(x)) return min;
return Math.min(max, Math.max(min, x));
}
function _valueToDeg(control, value) {
if (control === "pan") {
const v = _clampKnob(value, -1, 1);
const norm = (v + 1) / 2;
return _KNOB_MIN_DEG + norm * (_KNOB_MAX_DEG - _KNOB_MIN_DEG);
}
const v = _clampKnob(value, 0, _MAX_TRACK_VOLUME);
const norm = v / _MAX_TRACK_VOLUME;
return _KNOB_MIN_DEG + norm * (_KNOB_MAX_DEG - _KNOB_MIN_DEG);
}
function _setKnobIndicator(knobEl, control, value) {
const ind = knobEl.querySelector(".knob-indicator");
if (!ind) return;
ind.style.transform = `rotate(${_valueToDeg(control, value)}deg)`;
}
function _attachKnobDrag(knobEl, { control, getCurrent, setLocal, commit }) {
if (!knobEl) return;
knobEl.addEventListener("mousedown", (e) => {
e.preventDefault();
e.stopPropagation();
initializeAudioContext();
const startY = e.clientY;
const startVal = Number(getCurrent?.() ?? 0);
const sensitivity = control === "pan" ? 0.01 : 0.005;
let lastVal = startVal;
const onMove = (ev) => {
const delta = (startY - ev.clientY) * sensitivity;
let v = startVal + delta;
if (control === "pan") v = _clampKnob(v, -1, 1);
else v = _clampKnob(v, 0, _MAX_TRACK_VOLUME);
lastVal = v;
setLocal?.(v);
_setKnobIndicator(knobEl, control, v);
};
const onUp = () => {
window.removeEventListener("mousemove", onMove);
window.removeEventListener("mouseup", onUp);
commit?.(lastVal);
};
window.addEventListener("mousemove", onMove);
window.addEventListener("mouseup", onUp);
});
}
// =====================================================
// Playlist Patterns (Bassline clips) - Drag/Resize/Delete
@@ -512,8 +579,13 @@ export function renderAudioEditor() {
<div class="track-mute"></div>
</div>
<div class="track-controls">
<div class="knob-container"> <div class="knob" data-control="volume"><div class="knob-indicator"></div></div> <span>VOL</span> </div>
<div class="knob-container"> <div class="knob" data-control="pan"><div class="knob-indicator"></div></div> <span>PAN</span> </div>
<div class="knob-container"> <div class="knob" data-control="volume" data-track-id="${trackData.id}"><div class="knob-indicator"></div></div> <span>VOL</span> </div>
<div class="knob-container">
<div class="knob" data-control="pan" data-track-id="${trackData.id}">
<div class="knob-indicator"></div>
</div>
<span>PAN</span>
</div>
</div>
</div>
<div class="timeline-container">
@@ -523,6 +595,68 @@ export function renderAudioEditor() {
`;
newTrackContainer.appendChild(audioTrackLane);
// MUTE/VOL/PAN na playlist (principalmente p/ bassline/pattern lanes)
const muteBtn = audioTrackLane.querySelector(".track-mute");
if (muteBtn) {
const isMutedNow = !!(trackData.isMuted || trackData.muted);
muteBtn.classList.toggle("muted", isMutedNow);
muteBtn.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
initializeAudioContext();
const cur = !!(trackData.isMuted || trackData.muted);
const next = !cur;
setPatternTrackMute(trackData.id, next);
sendAction({ type: "SET_PATTERN_TRACK_MUTE", trackId: trackData.id, isMuted: next });
renderAudioEditor();
restartAudioEditorIfPlaying?.();
});
}
// Só aplica mixer se existir no Pattern State
const patternTrack = (appState.pattern?.tracks || []).find(
(t) => String(t.id) === String(trackData.id)
);
if (patternTrack) {
// Knobs (VOL/PAN)
audioTrackLane.querySelectorAll(".knob").forEach((knobEl) => {
const control = knobEl.dataset.control;
const trackId = knobEl.dataset.trackId;
const getT = () => (appState.pattern?.tracks || []).find((x) => String(x.id) === String(trackId));
const initial = control === "pan" ? (getT()?.pan ?? 0) : (getT()?.volume ?? 1);
_setKnobIndicator(knobEl, control, initial);
_attachKnobDrag(knobEl, {
control,
getCurrent: () => (control === "pan" ? (getT()?.pan ?? 0) : (getT()?.volume ?? 1)),
setLocal: (v) => {
if (control === "pan") setPatternTrackPan(trackId, v);
else setPatternTrackVolume(trackId, v);
},
commit: (v) => {
if (control === "pan") sendAction({ type: "SET_PATTERN_TRACK_PAN", trackId, pan: v });
else sendAction({ type: "SET_PATTERN_TRACK_VOLUME", trackId, volume: v });
},
});
});
} else {
// opcional: desabilitar visualmente knobs/mute em lanes de áudio
audioTrackLane.querySelectorAll(".track-mute, .knob").forEach((el) => {
el.style.opacity = "0.35";
el.style.pointerEvents = "none";
});
}
// Knobs: igual pattern_ui (drag vertical)
// -> ao arrastar: setPatternTrackVolume/Pan()
// -> ao soltar: sendAction SET_PATTERN_TRACK_VOLUME/PAN
const timelineContainer = audioTrackLane.querySelector(".timeline-container");
const grid = timelineContainer.querySelector(".spectrogram-view-grid");