mmpSearch/assets/js/creations/pattern/pattern_ui.js

218 lines
8.0 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// js/pattern_ui.js
import { appState } from "../state.js";
import {
    updateTrackSample
} 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';
// Função principal de renderização para o editor de patterns
export function renderPatternEditor() {
  const trackContainer = document.getElementById("track-container");
  trackContainer.innerHTML = "";
// (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');
    }
    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>
    `;
// (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"));
// (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 trackIndex = parseInt(parentTrackElement.dataset.trackIndex, 10); // (V7)
    // ... dentro da função redrawSequencer() ...
    const trackData = appState.pattern.tracks[trackIndex];
    if (!trackData || !trackData.patterns || trackData.patterns.length === 0) {
      sequencerContainer.innerHTML = ""; return;
    }
// --- 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];
    if (!activePattern) {
        sequencerContainer.innerHTML = ""; return;
    }
// ... resto da função ...
    const patternSteps = activePattern.steps;
    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");
      }
      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 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);
      }
    }
  });
}
// (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);
}