ativar/desativar tracks de áudio, knobs funcionais
This commit is contained in:
1 parent
d3e209d8e8
commit
73a5257aec
5 files changed
+260
-28
No files matched your search
@@ -1,11 +1,83 @@
|
||||
// js/pattern/pattern_ui.js
|
||||
import { appState } from "../state.js";
|
||||
import { updateTrackSample } from "./pattern_state.js";
|
||||
import { playSample, stopPlayback } from "./pattern_audio.js";
|
||||
import {
|
||||
updateTrackSample,
|
||||
setPatternTrackMute,
|
||||
setPatternTrackVolume,
|
||||
setPatternTrackPan,
|
||||
} from "./pattern_state.js";
|
||||
import { playSample, stopPlayback } from "./pattern_audio.js";
|
||||
import { getTotalSteps } from "../utils.js";
|
||||
import { sendAction } from '../socket.js';
|
||||
import { initializeAudioContext } from '../audio.js';
|
||||
import * as Tone from "https://esm.sh/tone";
|
||||
import { sendAction } from "../socket.js";
|
||||
import { initializeAudioContext } from "../audio.js";
|
||||
import * as Tone from "https://esm.sh/tone";
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Knob helpers (drag vertical para ajustar)
|
||||
// ------------------------------------------------------------
|
||||
const _KNOB_MIN_DEG = -135;
|
||||
const _KNOB_MAX_DEG = 135;
|
||||
const _MAX_TRACK_VOLUME = 1.5;
|
||||
|
||||
function _clamp(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 = _clamp(value, -1, 1);
|
||||
const norm = (v + 1) / 2; // 0..1
|
||||
return _KNOB_MIN_DEG + norm * (_KNOB_MAX_DEG - _KNOB_MIN_DEG);
|
||||
}
|
||||
const v = _clamp(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 = _clamp(v, -1, 1);
|
||||
else v = _clamp(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);
|
||||
});
|
||||
}
|
||||
|
||||
// Função principal de renderização para o editor de patterns
|
||||
export function renderPatternEditor() {
|
||||
@@ -140,6 +212,71 @@ export function renderPatternEditor() {
|
||||
<div class="step-sequencer-wrapper"></div>
|
||||
`;
|
||||
|
||||
// --------------------------
|
||||
// Mute (ativar/desativar canal)
|
||||
// --------------------------
|
||||
const muteBtn = trackLane.querySelector(".track-mute");
|
||||
if (muteBtn) {
|
||||
const isMutedNow = !!(trackData.isMuted || trackData.muted);
|
||||
muteBtn.classList.toggle("muted", isMutedNow);
|
||||
muteBtn.title = isMutedNow ? "Unmute" : "Mute";
|
||||
|
||||
muteBtn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
initializeAudioContext();
|
||||
|
||||
const t = appState.pattern.tracks[originalIndex];
|
||||
const cur = !!(t?.isMuted || t?.muted);
|
||||
const next = !cur;
|
||||
|
||||
// aplica local (instantâneo)
|
||||
setPatternTrackMute(trackData.id, next);
|
||||
|
||||
// feedback visual
|
||||
muteBtn.classList.toggle("muted", next);
|
||||
muteBtn.title = next ? "Unmute" : "Mute";
|
||||
|
||||
// broadcast (colaboração)
|
||||
sendAction({
|
||||
type: "SET_PATTERN_TRACK_MUTE",
|
||||
trackId: trackData.id,
|
||||
isMuted: next,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Knobs (VOL / PAN)
|
||||
// --------------------------
|
||||
trackLane.querySelectorAll(".knob").forEach((knobEl) => {
|
||||
const control = knobEl.dataset.control;
|
||||
const trackId = knobEl.dataset.trackId;
|
||||
|
||||
const t = appState.pattern.tracks[originalIndex];
|
||||
const initial = control === "pan" ? (t?.pan ?? 0) : (t?.volume ?? 1);
|
||||
_setKnobIndicator(knobEl, control, initial);
|
||||
|
||||
_attachKnobDrag(knobEl, {
|
||||
control,
|
||||
getCurrent: () => {
|
||||
const tr = appState.pattern.tracks.find((x) => String(x.id) === String(trackId));
|
||||
return control === "pan" ? (tr?.pan ?? 0) : (tr?.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 });
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Eventos de Seleção
|
||||
trackLane.addEventListener('click', () => {
|
||||
if (appState.pattern.activeTrackId === trackData.id) return;
|
||||
|
||||
Reference in new issue
Block a user