plugin v1.0.0

This commit is contained in:
JotaChina committed 2025-11-20 20:54:29 -03:00
1 parent 12c5b55bce
commit 44fce48bb5
12 files changed
+1316 -635

No files matched your search

+128 -22
View File
@@ -1,5 +1,10 @@
// js/pattern_state.js
import * as Tone from "https://esm.sh/tone";
import { TripleOscillator } from "../../audio/plugins/TripleOscillator.js";
import { Kicker } from "../../audio/plugins/Kicker.js";
import { Lb302 } from "../../audio/plugins/Lb302.js";
import { Nes } from "../../audio/plugins/Nes.js";
import { SuperSaw } from "../../audio/plugins/SuperSaw.js";
import { appState } from "../state.js";
import { DEFAULT_VOLUME, DEFAULT_PAN } from "../config.js";
@@ -7,29 +12,20 @@ import { getMainGainNode } from "../audio.js";
import { getTotalSteps } from "../utils.js";
export function initializePatternState() {
// Limpa players/buffers existentes
appState.pattern.tracks.forEach(track => {
try { track.player?.dispose(); } catch {}
try { track.buffer?.dispose?.(); } catch {}
try { track.instrument?.dispose(); } catch {}
});
// Reseta estado do editor de pattern
appState.pattern.tracks = [];
appState.pattern.activeTrackId = null;
appState.pattern.activePatternIndex = 0;
}
export async function loadAudioForTrack(track) {
if (!track.samplePath) return track;
// 1. Garante a criação dos nós de Volume e Pan
try {
// Descartar player/buffer anteriores com segurança
try { track.player?.dispose(); } catch {}
track.player = null;
try { track.buffer?.dispose?.(); } catch {}
track.buffer = null;
// Garante nós de volume/pan (Opção B: Volume em dB)
if (!track.volumeNode) {
track.volumeNode = new Tone.Volume(
track.volume === 0 ? -Infinity : Tone.gainToDb(track.volume)
@@ -45,30 +41,135 @@ export async function loadAudioForTrack(track) {
track.pannerNode.pan.value = track.pan ?? 0;
}
// Encadeia: Volume(dB) -> Panner -> Master
try { track.instrument?.disconnect(); } catch {}
try { track.player?.disconnect(); } catch {}
try { track.volumeNode.disconnect(); } catch {}
try { track.pannerNode.disconnect(); } catch {}
track.volumeNode.connect(track.pannerNode);
track.pannerNode.connect(getMainGainNode());
// Cria e carrega o Player
} catch (e) {
console.error("Erro ao criar nós de áudio base:", e);
}
// --- DETECÇÃO DE TIPO DE ARQUIVO ---
// Verifica se é um formato de áudio que o navegador suporta
// Verifica tipo de arquivo
const isStandardAudio = track.samplePath && /\.(wav|mp3|ogg|flac|m4a)$/i.test(track.samplePath);
const isDrumSynth = track.samplePath && /\.ds$/i.test(track.samplePath);
// Se não for áudio (ou for .ds/.xpf), é Plugin
if (!track.samplePath || !isStandardAudio) {
try {
if (track.instrument) { try { track.instrument.dispose(); } catch {} }
let synth;
const name = (track.instrumentName || "").toLowerCase();
// DADOS DO PLUGIN (Tenta parsear XML se for string, ou usa vazio)
// Dica: O ideal seria ter um parser real aqui, mas vamos passar {} por enquanto
const pluginData = {};
// SELETOR DE PLUGINS
switch (name) {
case "tripleoscillator":
case "3osc":
synth = new TripleOscillator(Tone.getContext(), pluginData);
break;
case "kicker":
synth = new Kicker(Tone.getContext(), pluginData);
break;
case "lb302":
synth = new Lb302(Tone.getContext(), pluginData);
break;
case "nes":
case "freeboy": // Freeboy é parecido com NES
case "papu": // Papu também é Gameboy
case "sid": // SID é 8-bit também, usaremos NES como fallback por enquanto
synth = new Nes(Tone.getContext(), pluginData);
break;
// --- PACOTE SUPER SAW ---
case "zynaddsubfx": // O clássico
case "watsyn": // Wavetable Synth
case "monstro": // 3 Osciladores monstruosos
case "vibedstrings": // Strings vibrantes (fatsaw funciona bem como base)
case "SuperSaw":
synth = new SuperSaw(Tone.getContext(), pluginData);
break;
case "organic": // Fallback simples para Organic (Additive)
synth = new Tone.PolySynth(Tone.Synth, {
oscillator: { type: "sine", count: 8, spread: 20 }
});
break;
case "zynaddsubfx": // O monstro!
// Zyn é impossível de clonar rápido. Vamos usar um "SuperSaw" gordo como placeholder
synth = new Tone.PolySynth(Tone.Synth, {
oscillator: { type: "fatsawtooth", count: 3, spread: 30 },
envelope: { attack: 0.1, decay: 0.3, sustain: 0.8, release: 1 }
});
break;
default:
console.warn(`Plugin ${name} desconhecido, usando fallback.`);
// Fallback genérico
synth = new Tone.PolySynth(Tone.Synth, { oscillator: { type: "triangle" } });
}
// Se o plugin criou uma classe wrapper (nossas classes .js),
// ele tem o método .connect. Se for Tone nativo (Organic/Zyn fallback), também tem.
if (synth.output) {
// Nossas classes customizadas
synth.connect(track.volumeNode);
} else {
// Objetos Tone.js puros
synth.connect(track.volumeNode);
}
track.instrument = synth;
track.player = null;
track.type = 'plugin';
console.log(`[Audio] Plugin carregado: ${name}`);
} catch (e) {
console.error("Erro ao carregar plugin:", name, e);
}
return track;
}
// 3. Lógica para SAMPLERS (Arquivos de Áudio Reais)
try {
try { track.player?.dispose(); } catch {}
track.player = null;
try { track.buffer?.dispose?.(); } catch {}
track.buffer = null;
if (track.instrument) {
try { track.instrument.dispose(); } catch {}
track.instrument = null;
}
const player = new Tone.Player({ url: track.samplePath, autostart: false });
await player.load(track.samplePath); // garante buffer carregado
await player.load(track.samplePath);
// Conecta o player ao volumeNode
player.connect(track.volumeNode);
// Buffer separado (se você usar waveform em outro lugar)
const buffer = new Tone.Buffer();
await buffer.load(track.samplePath);
// Atribuições finais
track.player = player;
track.buffer = buffer;
track.type = 'sampler'; // Garante o tipo correto
} catch (error) {
console.error('Erro ao carregar sample:', track.samplePath);
console.error(`Falha ao carregar áudio para a trilha "${track.name}":`, error);
try { track.player?.dispose(); } catch {}
try { track.buffer?.dispose?.(); } catch {}
track.player = null;
@@ -85,6 +186,7 @@ export function addTrackToState() {
id: Date.now() + Math.random(),
name: "novo instrumento",
samplePath: null,
type: 'plugin', // Padrão
player: null,
buffer: null,
patterns: referenceTrack
@@ -97,14 +199,14 @@ export function addTrackToState() {
activePatternIndex: 0,
volume: DEFAULT_VOLUME,
pan: DEFAULT_PAN,
// Opção B: controlar volume em dB
volumeNode: new Tone.Volume(Tone.gainToDb(DEFAULT_VOLUME)),
pannerNode: new Tone.Panner(DEFAULT_PAN),
};
// Cadeia de áudio nova
newTrack.volumeNode.connect(newTrack.pannerNode);
newTrack.pannerNode.connect(getMainGainNode());
loadAudioForTrack(newTrack);
appState.pattern.tracks.push(newTrack);
appState.pattern.activeTrackId = newTrack.id;
@@ -116,6 +218,7 @@ export function removeLastTrackFromState() {
try { trackToRemove.player?.dispose(); } catch {}
try { trackToRemove.buffer?.dispose?.(); } catch {}
try { trackToRemove.instrument?.dispose(); } catch {}
try { trackToRemove.pannerNode?.disconnect(); } catch {}
try { trackToRemove.volumeNode?.disconnect(); } catch {}
@@ -131,8 +234,11 @@ export async function updateTrackSample(trackIndex, samplePath) {
if (track) {
track.samplePath = samplePath;
track.name = samplePath.split("/").pop();
// Reseta o tipo baseado no novo arquivo
const isAudio = /\.(wav|mp3|ogg|flac|m4a)$/i.test(samplePath);
track.type = isAudio ? 'sampler' : 'plugin';
// (re)carrega e reconecta corretamente o player nesta trilha
await loadAudioForTrack(track);
}
}
@@ -145,4 +251,4 @@ export function toggleStepState(trackIndex, stepIndex) {
activePattern.steps[stepIndex] = !activePattern.steps[stepIndex];
}
}
}
}