141 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			141 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
// js/pattern_audio.js
 | 
						|
import { appState } from "../state.js";
 | 
						|
import { highlightStep } from "./pattern_ui.js";
 | 
						|
import { getTotalSteps } from "../utils.js";
 | 
						|
import { initializeAudioContext } from "../audio.js";
 | 
						|
 | 
						|
const timerDisplay = document.getElementById('timer-display');
 | 
						|
 | 
						|
function formatTime(milliseconds) {
 | 
						|
  const totalSeconds = Math.floor(milliseconds / 1000);
 | 
						|
  const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0');
 | 
						|
  const seconds = (totalSeconds % 60).toString().padStart(2, '0');
 | 
						|
  const centiseconds = Math.floor((milliseconds % 1000) / 10).toString().padStart(2, '0');
 | 
						|
  return `${minutes}:${seconds}:${centiseconds}`;
 | 
						|
}
 | 
						|
 | 
						|
export function playMetronomeSound(isDownbeat) {
 | 
						|
  initializeAudioContext();
 | 
						|
  const synth = new Tone.Synth().toDestination();
 | 
						|
  const freq = isDownbeat ? 1000 : 800;
 | 
						|
  synth.triggerAttackRelease(freq, "8n", Tone.now());
 | 
						|
}
 | 
						|
 | 
						|
// --- FUNÇÃO CORRIGIDA E EFICIENTE ---
 | 
						|
export function playSample(filePath, trackId) {
 | 
						|
  initializeAudioContext();
 | 
						|
  const track = trackId ? appState.pattern.tracks.find((t) => t.id == trackId) : null;
 | 
						|
 | 
						|
  // Se a faixa existe e tem um player pré-carregado, apenas o dispara.
 | 
						|
  if (track && track.player) {
 | 
						|
    // Atualiza o volume/pan caso tenham sido alterados
 | 
						|
    track.gainNode.gain.value = Tone.gainToDb(track.volume);
 | 
						|
    track.pannerNode.pan.value = track.pan;
 | 
						|
    
 | 
						|
    // Dispara o som imediatamente. Esta operação é instantânea.
 | 
						|
    track.player.start(Tone.now());
 | 
						|
  } 
 | 
						|
  // Fallback para preview de samples no navegador (sem trackId)
 | 
						|
  else if (!trackId && filePath) {
 | 
						|
      const previewPlayer = new Tone.Player(filePath).toDestination();
 | 
						|
      previewPlayer.autostart = true;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function tick() {
 | 
						|
  if (!appState.global.isPlaying) {
 | 
						|
    stopPlayback();
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  const totalSteps = getTotalSteps();
 | 
						|
  const lastStepIndex = appState.global.currentStep === 0 ? totalSteps - 1 : appState.global.currentStep - 1;
 | 
						|
  highlightStep(lastStepIndex, false);
 | 
						|
 | 
						|
  const bpm = parseInt(document.getElementById("bpm-input").value, 10) || 120;
 | 
						|
  const stepInterval = (60 * 1000) / (bpm * 4);
 | 
						|
  const currentTime = appState.global.currentStep * stepInterval;
 | 
						|
  if (timerDisplay) {
 | 
						|
    timerDisplay.textContent = formatTime(currentTime);
 | 
						|
  }
 | 
						|
 | 
						|
  if (appState.global.metronomeEnabled) {
 | 
						|
    const noteValue = parseInt(document.getElementById("compasso-b-input").value, 10) || 4;
 | 
						|
    const stepsPerBeat = 16 / noteValue;
 | 
						|
    if (appState.global.currentStep % stepsPerBeat === 0) {
 | 
						|
      playMetronomeSound(appState.global.currentStep % (stepsPerBeat * 4) === 0);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  appState.pattern.tracks.forEach((track) => {
 | 
						|
    if (!track.patterns || track.patterns.length === 0) return;
 | 
						|
    
 | 
						|
    const activePattern = track.patterns[appState.pattern.activePatternIndex];
 | 
						|
 | 
						|
    if (activePattern && activePattern.steps[appState.global.currentStep] && track.samplePath) {
 | 
						|
      playSample(track.samplePath, track.id);
 | 
						|
    }
 | 
						|
  });
 | 
						|
 | 
						|
  highlightStep(appState.global.currentStep, true);
 | 
						|
  appState.global.currentStep = (appState.global.currentStep + 1) % totalSteps;
 | 
						|
}
 | 
						|
 | 
						|
export function startPlayback() {
 | 
						|
  if (appState.global.isPlaying || appState.pattern.tracks.length === 0) return;
 | 
						|
  initializeAudioContext();
 | 
						|
  
 | 
						|
  if (appState.global.currentStep === 0) {
 | 
						|
      rewindPlayback();
 | 
						|
  }
 | 
						|
 | 
						|
  const bpm = parseInt(document.getElementById("bpm-input").value, 10) || 120;
 | 
						|
  Tone.Transport.bpm.value = bpm;
 | 
						|
  const stepInterval = (60 * 1000) / (bpm * 4);
 | 
						|
 | 
						|
  if (appState.global.playbackIntervalId) clearInterval(appState.global.playbackIntervalId);
 | 
						|
 | 
						|
  appState.global.isPlaying = true;
 | 
						|
  document.getElementById("play-btn").classList.remove("fa-play");
 | 
						|
  document.getElementById("play-btn").classList.add("fa-pause");
 | 
						|
 | 
						|
  tick();
 | 
						|
  appState.global.playbackIntervalId = setInterval(tick, stepInterval);
 | 
						|
}
 | 
						|
 | 
						|
export function stopPlayback() {
 | 
						|
  if(appState.global.playbackIntervalId) {
 | 
						|
    clearInterval(appState.global.playbackIntervalId);
 | 
						|
  }
 | 
						|
  appState.global.playbackIntervalId = null;
 | 
						|
  appState.global.isPlaying = false;
 | 
						|
 | 
						|
  document.querySelectorAll('.step.playing').forEach(s => s.classList.remove('playing'));
 | 
						|
  appState.global.currentStep = 0;
 | 
						|
  if (timerDisplay) timerDisplay.textContent = '00:00:00';
 | 
						|
 | 
						|
  const playBtn = document.getElementById("play-btn");
 | 
						|
  if (playBtn) {
 | 
						|
    playBtn.classList.remove("fa-pause");
 | 
						|
    playBtn.classList.add("fa-play");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
export function rewindPlayback() {
 | 
						|
  const lastStep = appState.global.currentStep > 0 ? appState.global.currentStep - 1 : getTotalSteps() - 1;
 | 
						|
  appState.global.currentStep = 0;
 | 
						|
  if (!appState.global.isPlaying) {
 | 
						|
    if (timerDisplay) timerDisplay.textContent = '00:00:00';
 | 
						|
    highlightStep(lastStep, false);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
export function togglePlayback() {
 | 
						|
  initializeAudioContext();
 | 
						|
  if (appState.global.isPlaying) {
 | 
						|
    stopPlayback();
 | 
						|
  } else {
 | 
						|
    appState.global.currentStep = 0;
 | 
						|
    startPlayback();
 | 
						|
  }
 | 
						|
} |