Padronização da interface e atualização do sync entre clientes
This commit is contained in:
1 parent
3f714c88de
commit
7d9d2b0f63
10 files changed
+796
-377
No files matched your search
@@ -1,4 +1,4 @@
|
||||
// js/pattern_state.js
|
||||
// js/pattern/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";
|
||||
@@ -11,6 +11,11 @@ import { DEFAULT_VOLUME, DEFAULT_PAN } from "../config.js";
|
||||
import { getMainGainNode } from "../audio.js";
|
||||
import { getTotalSteps } from "../utils.js";
|
||||
|
||||
// XML padrão para o instrumento Kicker (bateria simples)
|
||||
const DEFAULT_KICKER_XML = `<kicker>
|
||||
<env amt="0" attack="0.01" hold="0.1" decay="0.1" release="0.1" sustain="0.5" sync_mode="0"/>
|
||||
</kicker>`;
|
||||
|
||||
export function initializePatternState() {
|
||||
appState.pattern.tracks.forEach(track => {
|
||||
try { track.player?.dispose(); } catch {}
|
||||
@@ -55,20 +60,17 @@ export async function loadAudioForTrack(track) {
|
||||
|
||||
// --- 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
|
||||
// Se não for áudio padrão, assumimos Plugin (ou Kicker padrão)
|
||||
if (!track.samplePath || !isStandardAudio) {
|
||||
try {
|
||||
if (track.instrument) { try { track.instrument.dispose(); } catch {} }
|
||||
|
||||
let synth;
|
||||
const name = (track.instrumentName || "").toLowerCase();
|
||||
// Normaliza o nome do instrumento. Se vazio, assume kicker.
|
||||
const name = (track.instrumentName || "kicker").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
|
||||
@@ -87,64 +89,53 @@ export async function loadAudioForTrack(track) {
|
||||
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
|
||||
case "freeboy":
|
||||
case "papu":
|
||||
case "sid":
|
||||
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":
|
||||
case "zynaddsubfx":
|
||||
case "watsyn":
|
||||
case "monstro":
|
||||
case "vibedstrings":
|
||||
case "supersaw":
|
||||
synth = new SuperSaw(Tone.getContext(), pluginData);
|
||||
break;
|
||||
|
||||
case "organic": // Fallback simples para Organic (Additive)
|
||||
case "organic":
|
||||
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" } });
|
||||
console.warn(`Plugin ${name} desconhecido, usando fallback (Kicker).`);
|
||||
// Fallback seguro: Kicker
|
||||
synth = new Kicker(Tone.getContext(), pluginData);
|
||||
}
|
||||
|
||||
// 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';
|
||||
// Atualiza o nome se ele estava vazio
|
||||
if (!track.instrumentName) track.instrumentName = name;
|
||||
|
||||
console.log(`[Audio] Plugin carregado: ${name}`);
|
||||
|
||||
} catch (e) {
|
||||
console.error("Erro ao carregar plugin:", name, e);
|
||||
console.error("Erro ao carregar plugin:", track.instrumentName, e);
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
// 3. Lógica para SAMPLERS (Arquivos de Áudio Reais)
|
||||
// 3. Lógica para SAMPLERS
|
||||
try {
|
||||
try { track.player?.dispose(); } catch {}
|
||||
track.player = null;
|
||||
@@ -166,7 +157,7 @@ export async function loadAudioForTrack(track) {
|
||||
|
||||
track.player = player;
|
||||
track.buffer = buffer;
|
||||
track.type = 'sampler'; // Garante o tipo correto
|
||||
track.type = 'sampler';
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erro ao carregar sample:', track.samplePath);
|
||||
@@ -181,21 +172,29 @@ export async function loadAudioForTrack(track) {
|
||||
export function addTrackToState() {
|
||||
const totalSteps = getTotalSteps();
|
||||
const referenceTrack = appState.pattern.tracks[0];
|
||||
const newId = Date.now() + Math.random();
|
||||
|
||||
const newTrack = {
|
||||
id: Date.now() + Math.random(),
|
||||
name: "novo instrumento",
|
||||
id: newId,
|
||||
name: `Novo Instrumento ${appState.pattern.tracks.length + 1}`,
|
||||
samplePath: null,
|
||||
type: 'plugin', // Padrão
|
||||
type: 'plugin',
|
||||
|
||||
// 🔥 CORREÇÃO: Definir instrumento padrão (Kicker) e XML
|
||||
instrumentName: "kicker",
|
||||
instrumentXml: DEFAULT_KICKER_XML,
|
||||
|
||||
player: null,
|
||||
buffer: null,
|
||||
patterns: referenceTrack
|
||||
? referenceTrack.patterns.map(p => ({
|
||||
name: p.name,
|
||||
steps: new Array(p.steps.length).fill(false),
|
||||
notes: [],
|
||||
pos: p.pos
|
||||
}))
|
||||
: [{ name: "Pattern 1", steps: new Array(totalSteps).fill(false), pos: 0 }],
|
||||
: [{ name: "Pattern 1", steps: new Array(totalSteps).fill(false), notes: [], pos: 0 }],
|
||||
|
||||
activePatternIndex: 0,
|
||||
volume: DEFAULT_VOLUME,
|
||||
pan: DEFAULT_PAN,
|
||||
@@ -206,10 +205,39 @@ export function addTrackToState() {
|
||||
newTrack.volumeNode.connect(newTrack.pannerNode);
|
||||
newTrack.pannerNode.connect(getMainGainNode());
|
||||
|
||||
// Carrega o áudio (vai cair no case "kicker" do loadAudioForTrack)
|
||||
loadAudioForTrack(newTrack);
|
||||
|
||||
appState.pattern.tracks.push(newTrack);
|
||||
appState.pattern.activeTrackId = newTrack.id;
|
||||
|
||||
console.log("Faixa adicionada ao estado com Kicker padrão.");
|
||||
}
|
||||
|
||||
export function removeTrackById(trackId) {
|
||||
const index = appState.pattern.tracks.findIndex(t => t.id === trackId);
|
||||
|
||||
if (index !== -1) {
|
||||
const trackToRemove = appState.pattern.tracks[index];
|
||||
|
||||
// Limpeza de memória
|
||||
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 {}
|
||||
|
||||
// Remove do array
|
||||
appState.pattern.tracks.splice(index, 1);
|
||||
|
||||
// Ajusta seleção se necessário
|
||||
if (appState.pattern.activeTrackId === trackId) {
|
||||
appState.pattern.activeTrackId = appState.pattern.tracks[0]?.id ?? null;
|
||||
}
|
||||
|
||||
return true; // Retorna sucesso
|
||||
}
|
||||
return false; // Não achou (o fantasma não existe aqui)
|
||||
}
|
||||
|
||||
export function removeLastTrackFromState() {
|
||||
|
||||
Reference in new issue
Block a user