sync mode on
This commit is contained in:
1 parent
1818932244
commit
1174b328e5
415 files changed
+44294
-1182
No files matched your search
@@ -1,171 +1,218 @@
|
||||
// js/pattern_ui.js
|
||||
import { appState } from "../state.js";
|
||||
import {
|
||||
toggleStepState,
|
||||
updateTrackSample
|
||||
updateTrackSample
|
||||
} from "./pattern_state.js";
|
||||
import { playSample, stopPlayback } from "./pattern_audio.js"; // Será criado no próximo passo
|
||||
import { playSample, stopPlayback } from "./pattern_audio.js";
|
||||
import { getTotalSteps } from "../utils.js";
|
||||
import { sendAction } from '../socket.js';
|
||||
import { initializeAudioContext } from '../audio.js';
|
||||
|
||||
// Função principal de renderização para o editor de patterns
|
||||
export function renderPatternEditor() {
|
||||
const trackContainer = document.getElementById("track-container");
|
||||
trackContainer.innerHTML = "";
|
||||
const trackContainer = document.getElementById("track-container");
|
||||
trackContainer.innerHTML = "";
|
||||
|
||||
appState.pattern.tracks.forEach((trackData) => {
|
||||
const trackLane = document.createElement("div");
|
||||
trackLane.className = "track-lane";
|
||||
trackLane.dataset.trackId = trackData.id;
|
||||
// (V7) Adicionado 'trackIndex'
|
||||
appState.pattern.tracks.forEach((trackData, trackIndex) => {
|
||||
const trackLane = document.createElement("div");
|
||||
trackLane.className = "track-lane";
|
||||
trackLane.dataset.trackIndex = trackIndex; // (V7) Usando índice
|
||||
|
||||
if (trackData.id === appState.pattern.activeTrackId) {
|
||||
trackLane.classList.add('active-track');
|
||||
}
|
||||
if (trackData.id === appState.pattern.activeTrackId) {
|
||||
trackLane.classList.add('active-track');
|
||||
}
|
||||
|
||||
trackLane.innerHTML = `
|
||||
<div class="track-info">
|
||||
<i class="fa-solid fa-gear"></i>
|
||||
<div class="track-mute"></div>
|
||||
<span class="track-name">${trackData.name}</span>
|
||||
</div>
|
||||
<div class="track-controls">
|
||||
<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 class="step-sequencer-wrapper"></div>
|
||||
`;
|
||||
trackLane.innerHTML = `
|
||||
<div class="track-info">
|
||||
<i class="fa-solid fa-gear"></i>
|
||||
<div class="track-mute"></div>
|
||||
<span class="track-name">${trackData.name}</span>
|
||||
</div>
|
||||
<div class="track-controls">
|
||||
<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 class="step-sequencer-wrapper"></div>
|
||||
`;
|
||||
|
||||
trackLane.addEventListener('click', () => {
|
||||
if (appState.pattern.activeTrackId === trackData.id) return;
|
||||
stopPlayback();
|
||||
appState.pattern.activeTrackId = trackData.id;
|
||||
document.querySelectorAll('.track-lane').forEach(lane => lane.classList.remove('active-track'));
|
||||
trackLane.classList.add('active-track');
|
||||
updateGlobalPatternSelector();
|
||||
redrawSequencer();
|
||||
});
|
||||
// (Listener de clique da track é local, sem mudanças)
|
||||
trackLane.addEventListener('click', () => {
|
||||
if (appState.pattern.activeTrackId === trackData.id) return;
|
||||
stopPlayback();
|
||||
appState.pattern.activeTrackId = trackData.id;
|
||||
document.querySelectorAll('.track-lane').forEach(lane => lane.classList.remove('active-track'));
|
||||
trackLane.classList.add('active-track');
|
||||
updateGlobalPatternSelector();
|
||||
redrawSequencer();
|
||||
});
|
||||
|
||||
trackLane.addEventListener("dragover", (e) => { e.preventDefault(); trackLane.classList.add("drag-over"); });
|
||||
trackLane.addEventListener("dragleave", () => trackLane.classList.remove("drag-over"));
|
||||
trackLane.addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
trackLane.classList.remove("drag-over");
|
||||
const filePath = e.dataTransfer.getData("text/plain");
|
||||
if (filePath) {
|
||||
updateTrackSample(trackData.id, filePath);
|
||||
}
|
||||
});
|
||||
trackLane.addEventListener("dragover", (e) => { e.preventDefault(); trackLane.classList.add("drag-over"); });
|
||||
trackLane.addEventListener("dragleave", () => trackLane.classList.remove("drag-over"));
|
||||
|
||||
trackContainer.appendChild(trackLane);
|
||||
// A lógica dos knobs precisará ser reimplementada ou movida para um arquivo de componentes
|
||||
});
|
||||
|
||||
updateGlobalPatternSelector();
|
||||
redrawSequencer();
|
||||
// (V9) Listener de "drop" (arrastar) agora usa 'sendAction'
|
||||
trackLane.addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
trackLane.classList.remove("drag-over");
|
||||
const filePath = e.dataTransfer.getData("text/plain");
|
||||
|
||||
if (filePath) {
|
||||
sendAction({
|
||||
type: 'SET_TRACK_SAMPLE',
|
||||
trackIndex: trackIndex,
|
||||
filePath: filePath
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
trackContainer.appendChild(trackLane);
|
||||
});
|
||||
|
||||
updateGlobalPatternSelector();
|
||||
redrawSequencer();
|
||||
}
|
||||
|
||||
export function redrawSequencer() {
|
||||
const totalGridSteps = getTotalSteps();
|
||||
document.querySelectorAll(".step-sequencer-wrapper").forEach((wrapper) => {
|
||||
let sequencerContainer = wrapper.querySelector(".step-sequencer");
|
||||
if (!sequencerContainer) {
|
||||
sequencerContainer = document.createElement("div");
|
||||
sequencerContainer.className = "step-sequencer";
|
||||
wrapper.appendChild(sequencerContainer);
|
||||
}
|
||||
|
||||
const parentTrackElement = wrapper.closest(".track-lane");
|
||||
const trackId = parentTrackElement.dataset.trackId;
|
||||
const trackData = appState.pattern.tracks.find((t) => t.id == trackId);
|
||||
const totalGridSteps = getTotalSteps();
|
||||
document.querySelectorAll(".step-sequencer-wrapper").forEach((wrapper) => {
|
||||
let sequencerContainer = wrapper.querySelector(".step-sequencer");
|
||||
if (!sequencerContainer) {
|
||||
sequencerContainer = document.createElement("div");
|
||||
sequencerContainer.className = "step-sequencer";
|
||||
wrapper.appendChild(sequencerContainer);
|
||||
}
|
||||
|
||||
const parentTrackElement = wrapper.closest(".track-lane");
|
||||
const trackIndex = parseInt(parentTrackElement.dataset.trackIndex, 10); // (V7)
|
||||
// ... dentro da função redrawSequencer() ...
|
||||
|
||||
if (!trackData || !trackData.patterns || trackData.patterns.length === 0) {
|
||||
sequencerContainer.innerHTML = ""; return;
|
||||
}
|
||||
const trackData = appState.pattern.tracks[trackIndex];
|
||||
|
||||
const activePattern = trackData.patterns[appState.pattern.activePatternIndex];
|
||||
if (!activePattern) {
|
||||
sequencerContainer.innerHTML = ""; return;
|
||||
}
|
||||
const patternSteps = activePattern.steps;
|
||||
if (!trackData || !trackData.patterns || trackData.patterns.length === 0) {
|
||||
sequencerContainer.innerHTML = ""; return;
|
||||
}
|
||||
|
||||
sequencerContainer.innerHTML = "";
|
||||
for (let i = 0; i < totalGridSteps; i++) {
|
||||
const stepWrapper = document.createElement("div");
|
||||
stepWrapper.className = "step-wrapper";
|
||||
const stepElement = document.createElement("div");
|
||||
stepElement.className = "step";
|
||||
|
||||
if (patternSteps[i] === true) {
|
||||
stepElement.classList.add("active");
|
||||
}
|
||||
// --- CORRIJA ESTAS DUAS LINHAS ---
|
||||
// ANTES:
|
||||
// const activePatternIndex = appState.pattern.activePatternIndex;
|
||||
// const activePattern = trackData.patterns[activePatternIndex];
|
||||
//
|
||||
// DEPOIS:
|
||||
const activePatternIndex = trackData.activePatternIndex;
|
||||
const activePattern = trackData.patterns[activePatternIndex];
|
||||
|
||||
stepElement.addEventListener("click", () => {
|
||||
toggleStepState(trackData.id, i);
|
||||
stepElement.classList.toggle("active");
|
||||
if (trackData && trackData.samplePath) {
|
||||
playSample(trackData.samplePath, trackData.id);
|
||||
}
|
||||
});
|
||||
if (!activePattern) {
|
||||
sequencerContainer.innerHTML = ""; return;
|
||||
}
|
||||
// ... resto da função ...
|
||||
const patternSteps = activePattern.steps;
|
||||
|
||||
const beatsPerBar = parseInt(document.getElementById("compasso-a-input").value, 10) || 4;
|
||||
const groupIndex = Math.floor(i / beatsPerBar);
|
||||
if (groupIndex % 2 === 0) {
|
||||
stepElement.classList.add("step-dark");
|
||||
}
|
||||
sequencerContainer.innerHTML = "";
|
||||
for (let i = 0; i < totalGridSteps; i++) {
|
||||
const stepWrapper = document.createElement("div");
|
||||
stepWrapper.className = "step-wrapper";
|
||||
const stepElement = document.createElement("div");
|
||||
stepElement.className = "step";
|
||||
|
||||
if (patternSteps[i] === true) {
|
||||
stepElement.classList.add("active");
|
||||
}
|
||||
|
||||
const stepsPerBar = 16;
|
||||
if (i > 0 && i % stepsPerBar === 0) {
|
||||
const marker = document.createElement("div");
|
||||
marker.className = "step-marker";
|
||||
marker.textContent = Math.floor(i / stepsPerBar) + 1;
|
||||
stepWrapper.appendChild(marker);
|
||||
}
|
||||
|
||||
stepWrapper.appendChild(stepElement);
|
||||
sequencerContainer.appendChild(stepWrapper);
|
||||
}
|
||||
});
|
||||
stepElement.addEventListener("click", () => {
|
||||
initializeAudioContext(); // (V8)
|
||||
|
||||
const currentState = activePattern.steps[i] || false;
|
||||
const isActive = !currentState;
|
||||
|
||||
sendAction({ // (V7)
|
||||
type: 'TOGGLE_NOTE',
|
||||
trackIndex: trackIndex,
|
||||
patternIndex: activePatternIndex,
|
||||
stepIndex: i,
|
||||
isActive: isActive
|
||||
});
|
||||
|
||||
if (isActive && trackData && trackData.samplePath) {
|
||||
playSample(trackData.samplePath, trackData.id);
|
||||
}
|
||||
});
|
||||
|
||||
const beatsPerBar = parseInt(document.getElementById("compasso-a-input").value, 10) || 4;
|
||||
const groupIndex = Math.floor(i / beatsPerBar);
|
||||
if (groupIndex % 2 === 0) {
|
||||
stepElement.classList.add("step-dark");
|
||||
}
|
||||
|
||||
const stepsPerBar = 16;
|
||||
if (i > 0 && i % stepsPerBar === 0) {
|
||||
const marker = document.createElement("div");
|
||||
marker.className = "step-marker";
|
||||
marker.textContent = Math.floor(i / stepsPerBar) + 1;
|
||||
stepWrapper.appendChild(marker);
|
||||
}
|
||||
|
||||
stepWrapper.appendChild(stepElement);
|
||||
sequencerContainer.appendChild(stepWrapper);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function updateGlobalPatternSelector() {
|
||||
const globalPatternSelector = document.getElementById('global-pattern-selector');
|
||||
if (!globalPatternSelector) return;
|
||||
const globalPatternSelector = document.getElementById('global-pattern-selector');
|
||||
if (!globalPatternSelector) return;
|
||||
|
||||
const referenceTrack = appState.pattern.tracks[0];
|
||||
globalPatternSelector.innerHTML = '';
|
||||
if (referenceTrack && referenceTrack.patterns.length > 0) {
|
||||
referenceTrack.patterns.forEach((pattern, index) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = index;
|
||||
option.textContent = pattern.name;
|
||||
globalPatternSelector.appendChild(option);
|
||||
});
|
||||
globalPatternSelector.selectedIndex = appState.pattern.activePatternIndex;
|
||||
globalPatternSelector.disabled = false;
|
||||
} else {
|
||||
const option = document.createElement('option');
|
||||
option.textContent = 'Sem patterns';
|
||||
globalPatternSelector.appendChild(option);
|
||||
globalPatternSelector.disabled = true;
|
||||
}
|
||||
const referenceTrack = appState.pattern.tracks[0];
|
||||
globalPatternSelector.innerHTML = '';
|
||||
if (referenceTrack && referenceTrack.patterns.length > 0) {
|
||||
referenceTrack.patterns.forEach((pattern, index) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = index;
|
||||
option.textContent = pattern.name;
|
||||
globalPatternSelector.appendChild(option);
|
||||
});
|
||||
globalPatternSelector.selectedIndex = appState.pattern.activePatternIndex;
|
||||
globalPatternSelector.disabled = false;
|
||||
} else {
|
||||
const option = document.createElement('option');
|
||||
option.textContent = 'Sem patterns';
|
||||
globalPatternSelector.appendChild(option);
|
||||
globalPatternSelector.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function highlightStep(stepIndex, isActive) {
|
||||
if (stepIndex < 0) return;
|
||||
document.querySelectorAll(".track-lane").forEach((track) => {
|
||||
const stepWrapper = track.querySelector(
|
||||
`.step-sequencer .step-wrapper:nth-child(${stepIndex + 1})`
|
||||
);
|
||||
if (stepWrapper) {
|
||||
const stepElement = stepWrapper.querySelector(".step");
|
||||
if (stepElement) {
|
||||
stepElement.classList.toggle("playing", isActive);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (stepIndex < 0) return;
|
||||
document.querySelectorAll(".track-lane").forEach((track) => {
|
||||
const stepWrapper = track.querySelector(
|
||||
`.step-sequencer .step-wrapper:nth-child(${stepIndex + 1})`
|
||||
);
|
||||
if (stepWrapper) {
|
||||
const stepElement = stepWrapper.querySelector(".step");
|
||||
if (stepElement) {
|
||||
stepElement.classList.toggle("playing", isActive);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// (V7) Função de UI "cirúrgica"
|
||||
export function updateStepUI(trackIndex, patternIndex, stepIndex, isActive) {
|
||||
if (patternIndex !== appState.pattern.activePatternIndex) {
|
||||
return;
|
||||
}
|
||||
const trackElement = document.querySelector(`.track-lane[data-track-index="${trackIndex}"]`);
|
||||
if (!trackElement) return;
|
||||
const stepWrapper = trackElement.querySelector(
|
||||
`.step-sequencer .step-wrapper:nth-child(${stepIndex + 1})`
|
||||
);
|
||||
if (!stepWrapper) return;
|
||||
const stepElement = stepWrapper.querySelector(".step");
|
||||
if (!stepElement) return;
|
||||
stepElement.classList.toggle("active", isActive);
|
||||
}
|
||||
Reference in new issue
Block a user