ativar/desativar tracks de áudio na playlist, knobs funcionais
This commit is contained in:
1 parent
73a5257aec
commit
ce9a9dfda8
2 files changed
+323
-33
No files matched your search
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user