corrigindo play/stop do editor de patterns
Deploy / Deploy (push) Successful in 2m1s
Details
Deploy / Deploy (push) Successful in 2m1s
Details
This commit is contained in:
parent
e24b188d8d
commit
bf03931eb8
|
|
@ -65,14 +65,17 @@ export function playMetronomeSound(isDownbeat) {
|
||||||
// Dispara o sample de uma track, garantindo que o player esteja roteado corretamente
|
// Dispara o sample de uma track, garantindo que o player esteja roteado corretamente
|
||||||
export function playSample(filePath, trackId) {
|
export function playSample(filePath, trackId) {
|
||||||
initializeAudioContext();
|
initializeAudioContext();
|
||||||
|
|
||||||
const track = trackId
|
const track = trackId
|
||||||
? appState.pattern.tracks.find((t) => t.id == trackId)
|
? appState.pattern.tracks.find((t) => t.id == trackId)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
// Se a faixa existe e tem um player pré-carregado
|
// Se a track existe e tem player/preload
|
||||||
if (track && track.player) {
|
if (track && (track.previewPlayer || track.player)) {
|
||||||
if (track.player.loaded) {
|
const playerToUse = track.previewPlayer || track.player;
|
||||||
// Ajusta volume/pan sempre que tocar (robustez a alterações em tempo real)
|
|
||||||
|
if (playerToUse.loaded) {
|
||||||
|
// Atualiza volume/pan ao tocar
|
||||||
if (track.volumeNode) {
|
if (track.volumeNode) {
|
||||||
track.volumeNode.volume.value =
|
track.volumeNode.volume.value =
|
||||||
track.volume === 0 ? -Infinity : Tone.gainToDb(track.volume);
|
track.volume === 0 ? -Infinity : Tone.gainToDb(track.volume);
|
||||||
|
|
@ -81,29 +84,30 @@ export function playSample(filePath, trackId) {
|
||||||
track.pannerNode.pan.value = track.pan ?? 0;
|
track.pannerNode.pan.value = track.pan ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Garante conexão: player -> volumeNode
|
// roteia playerToUse -> volumeNode
|
||||||
try {
|
try { playerToUse.disconnect(); } catch {}
|
||||||
track.player.disconnect();
|
if (track.volumeNode) playerToUse.connect(track.volumeNode);
|
||||||
} catch {}
|
|
||||||
if (track.volumeNode) {
|
|
||||||
track.player.connect(track.volumeNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispara imediatamente
|
// Dispara (preview não interfere no player da playlist)
|
||||||
track.player.start(Tone.now());
|
try {
|
||||||
|
playerToUse.start(Tone.now());
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Falha ao tocar preview/sample:", track.name, e);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(`Player da trilha "${track.name}" ainda não carregado — pulando.`);
|
||||||
`Player da trilha "${track.name}" ainda não carregado — pulando este tick.`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// Fallback para preview de sample sem trackId
|
|
||||||
else if (!trackId && filePath) {
|
// Fallback: preview sem trackId
|
||||||
|
if (!trackId && filePath) {
|
||||||
const previewPlayer = new Tone.Player(filePath).toDestination();
|
const previewPlayer = new Tone.Player(filePath).toDestination();
|
||||||
previewPlayer.autostart = true;
|
previewPlayer.autostart = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function playSamplerNoteAtTime(track, midi, time, durationSec) {
|
function playSamplerNoteAtTime(track, midi, time, durationSec) {
|
||||||
if (!track?.buffer || !track.volumeNode) return;
|
if (!track?.buffer || !track.volumeNode) return;
|
||||||
|
|
||||||
|
|
@ -179,12 +183,18 @@ export function stopPlayback(rewind = true) {
|
||||||
Tone.Transport.cancel();
|
Tone.Transport.cancel();
|
||||||
stopScheduledPianoRoll();
|
stopScheduledPianoRoll();
|
||||||
|
|
||||||
|
// ✅ Pattern Editor: para apenas o preview (não mexe no track.player da playlist)
|
||||||
|
appState.pattern.tracks.forEach((track) => {
|
||||||
|
try { track.previewPlayer?.stop(); } catch {}
|
||||||
|
});
|
||||||
|
|
||||||
if (rewind) {
|
if (rewind) {
|
||||||
currentStep = 0;
|
currentStep = 0;
|
||||||
updateStepHighlight(currentStep);
|
updateStepHighlight(currentStep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function rewindPlayback() {
|
export function rewindPlayback() {
|
||||||
const lastStep =
|
const lastStep =
|
||||||
appState.global.currentStep > 0
|
appState.global.currentStep > 0
|
||||||
|
|
@ -539,20 +549,17 @@ export function startSongPatternPlaybackOnTransport() {
|
||||||
if (songPatternScheduleId !== null) return;
|
if (songPatternScheduleId !== null) return;
|
||||||
|
|
||||||
songPatternScheduleId = Tone.Transport.scheduleRepeat((time) => {
|
songPatternScheduleId = Tone.Transport.scheduleRepeat((time) => {
|
||||||
// bpm atual
|
|
||||||
const bpm = parseInt(document.getElementById("bpm-input")?.value, 10) || 120;
|
const bpm = parseInt(document.getElementById("bpm-input")?.value, 10) || 120;
|
||||||
const stepIntervalSec = 60 / (bpm * 4);
|
const stepIntervalSec = 60 / (bpm * 4);
|
||||||
|
|
||||||
// step absoluto do song (considera seek do Transport)
|
|
||||||
const transportSec = Tone.Transport.getSecondsAtTime
|
const transportSec = Tone.Transport.getSecondsAtTime
|
||||||
? Tone.Transport.getSecondsAtTime(time)
|
? Tone.Transport.getSecondsAtTime(time)
|
||||||
: Tone.Transport.seconds;
|
: Tone.Transport.seconds;
|
||||||
|
|
||||||
const songStep = Math.floor(transportSec / stepIntervalSec + 1e-6);
|
const songStep = Math.floor(transportSec / stepIntervalSec + 1e-6);
|
||||||
|
|
||||||
const songTick = songStep * LMMS_TICKS_PER_STEP;
|
const songTick = songStep * LMMS_TICKS_PER_STEP;
|
||||||
|
|
||||||
// quais patterns (colunas) estão ativas neste tick?
|
// Patterns ativas neste tick (pelas basslines/playlist clips)
|
||||||
const basslineTracks = appState.pattern.tracks.filter(
|
const basslineTracks = appState.pattern.tracks.filter(
|
||||||
(t) => t.type === "bassline" && !t.isMuted
|
(t) => t.type === "bassline" && !t.isMuted
|
||||||
);
|
);
|
||||||
|
|
@ -569,7 +576,7 @@ export function startSongPatternPlaybackOnTransport() {
|
||||||
|
|
||||||
if (activePatternHits.length === 0) return;
|
if (activePatternHits.length === 0) return;
|
||||||
|
|
||||||
// dispara instrumentos reais (samplers/plugins)
|
// Dispara tracks reais (samplers/plugins)
|
||||||
for (const track of appState.pattern.tracks) {
|
for (const track of appState.pattern.tracks) {
|
||||||
if (track.type === "bassline") continue;
|
if (track.type === "bassline") continue;
|
||||||
if (track.muted) continue;
|
if (track.muted) continue;
|
||||||
|
|
@ -584,24 +591,29 @@ export function startSongPatternPlaybackOnTransport() {
|
||||||
for (const n of patt.notes) {
|
for (const n of patt.notes) {
|
||||||
const pos = Number(n.pos) || 0;
|
const pos = Number(n.pos) || 0;
|
||||||
const rawLen = Number(n.len) || 0;
|
const rawLen = Number(n.len) || 0;
|
||||||
const len = Math.max(rawLen, LMMS_TICKS_PER_STEP); // mínimo 1 step
|
const len = rawLen < 0 ? LMMS_TICKS_PER_STEP : Math.max(rawLen, LMMS_TICKS_PER_STEP);
|
||||||
pattLenTicksByNotes = Math.max(pattLenTicksByNotes, pos + len);
|
pattLenTicksByNotes = Math.max(pattLenTicksByNotes, pos + len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const pattLenTicksBySteps = (patt.steps?.length || 0) * LMMS_TICKS_PER_STEP;
|
const pattLenTicksBySteps =
|
||||||
|
(patt.steps?.length || 0) * LMMS_TICKS_PER_STEP;
|
||||||
|
|
||||||
// garante pelo menos 1 step
|
const pattLenTicks = Math.max(
|
||||||
const pattLenTicks = Math.max(pattLenTicksByNotes, pattLenTicksBySteps, LMMS_TICKS_PER_STEP);
|
pattLenTicksByNotes,
|
||||||
|
pattLenTicksBySteps,
|
||||||
|
LMMS_TICKS_PER_STEP
|
||||||
|
);
|
||||||
|
|
||||||
// tick atual dentro do pattern (loopando)
|
// tick atual dentro do pattern (loop interno ao esticar clip)
|
||||||
const tickInPattern = (hit.localStep * LMMS_TICKS_PER_STEP) % pattLenTicks;
|
const tickInPattern =
|
||||||
|
(hit.localStep * LMMS_TICKS_PER_STEP) % pattLenTicks;
|
||||||
|
|
||||||
// step index (só pra lógica de steps)
|
// step index (para patterns de steps)
|
||||||
const pattLenSteps = patt.steps?.length || 0;
|
const pattLenSteps = patt.steps?.length || 0;
|
||||||
const stepInPattern = pattLenSteps > 0
|
const stepInPattern =
|
||||||
? (Math.floor(tickInPattern / LMMS_TICKS_PER_STEP) % pattLenSteps)
|
pattLenSteps > 0
|
||||||
: hit.localStep;
|
? (Math.floor(tickInPattern / LMMS_TICKS_PER_STEP) % pattLenSteps)
|
||||||
|
: hit.localStep;
|
||||||
|
|
||||||
// ✅ 1) PLUGIN com piano roll (notes)
|
// ✅ 1) PLUGIN com piano roll (notes)
|
||||||
if (
|
if (
|
||||||
|
|
@ -623,39 +635,44 @@ export function startSongPatternPlaybackOnTransport() {
|
||||||
|
|
||||||
if (!inWindow) continue;
|
if (!inWindow) continue;
|
||||||
|
|
||||||
const offsetTicks = wraps && nPos < stepStartTick
|
const offsetTicks =
|
||||||
? (pattLenTicks - stepStartTick) + nPos
|
wraps && nPos < stepStartTick
|
||||||
: nPos - stepStartTick;
|
? (pattLenTicks - stepStartTick) + nPos
|
||||||
|
: nPos - stepStartTick;
|
||||||
|
|
||||||
const t2 = time + ticksToSec(offsetTicks, stepIntervalSec);
|
const t2 = time + ticksToSec(offsetTicks, stepIntervalSec);
|
||||||
|
|
||||||
|
const rawLen = Number(n.len) || 0;
|
||||||
|
const lenTicks = rawLen < 0 ? LMMS_TICKS_PER_STEP : Math.max(rawLen, LMMS_TICKS_PER_STEP);
|
||||||
|
const durSec = Math.max(0.01, ticksToSec(lenTicks, stepIntervalSec));
|
||||||
|
const vel = (Number(n.vol) || 100) / 100;
|
||||||
|
|
||||||
|
const midi = Number(n.key) || 0;
|
||||||
|
const freq = Tone.Frequency(midi, "midi").toFrequency();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
track.instrument.triggerAttackRelease(freq, durSec, t2, vel);
|
track.instrument.triggerAttackRelease(freq, durSec, t2, vel);
|
||||||
} catch {
|
} catch (e) {
|
||||||
try {
|
console.warn("[Playlist] Falha ao tocar plugin note:", track.name, e);
|
||||||
track.instrument.triggerAttackRelease(noteName, durSec, t2);
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
continue; // 👈 importante: não cair na lógica abaixo
|
continue; // não cai na lógica de steps
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 1b) SAMPLER com piano roll (notes) — loop interno ao esticar o clip
|
// ✅ 1b) SAMPLER com piano roll (notes)
|
||||||
if (
|
if (
|
||||||
track.type === "sampler" &&
|
track.type === "sampler" &&
|
||||||
track.buffer &&
|
track.buffer &&
|
||||||
Array.isArray(patt.notes) &&
|
Array.isArray(patt.notes) &&
|
||||||
patt.notes.length > 0
|
patt.notes.length > 0
|
||||||
) {
|
) {
|
||||||
// 👇 chave do loop interno: usa tickInPattern (já calculado acima)
|
|
||||||
const stepStartTick = tickInPattern;
|
const stepStartTick = tickInPattern;
|
||||||
const stepEndTick = stepStartTick + LMMS_TICKS_PER_STEP;
|
const stepEndTick = stepStartTick + LMMS_TICKS_PER_STEP;
|
||||||
|
|
||||||
for (const n of patt.notes) {
|
for (const n of patt.notes) {
|
||||||
const nPos = Number(n.pos) || 0;
|
const nPos = Number(n.pos) || 0;
|
||||||
|
|
||||||
// janela do step, tratando “wrap” no fim do pattern
|
|
||||||
const wraps = stepEndTick > pattLenTicks;
|
const wraps = stepEndTick > pattLenTicks;
|
||||||
const inWindow = wraps
|
const inWindow = wraps
|
||||||
? (nPos >= stepStartTick || nPos < (stepEndTick - pattLenTicks))
|
? (nPos >= stepStartTick || nPos < (stepEndTick - pattLenTicks))
|
||||||
|
|
@ -663,38 +680,45 @@ export function startSongPatternPlaybackOnTransport() {
|
||||||
|
|
||||||
if (!inWindow) continue;
|
if (!inWindow) continue;
|
||||||
|
|
||||||
const offsetTicks = wraps && nPos < stepStartTick
|
const offsetTicks =
|
||||||
? (pattLenTicks - stepStartTick) + nPos
|
wraps && nPos < stepStartTick
|
||||||
: nPos - stepStartTick;
|
? (pattLenTicks - stepStartTick) + nPos
|
||||||
|
: nPos - stepStartTick;
|
||||||
|
|
||||||
const t2 = time + ticksToSec(offsetTicks, stepIntervalSec);
|
const t2 = time + ticksToSec(offsetTicks, stepIntervalSec);
|
||||||
|
|
||||||
const lenTicks = Math.max(1, Number(n.len) || LMMS_TICKS_PER_STEP);
|
const rawLen = Number(n.len) || 0;
|
||||||
|
const lenTicks = rawLen < 0 ? LMMS_TICKS_PER_STEP : Math.max(rawLen, LMMS_TICKS_PER_STEP);
|
||||||
const durSec = Math.max(0.01, ticksToSec(lenTicks, stepIntervalSec));
|
const durSec = Math.max(0.01, ticksToSec(lenTicks, stepIntervalSec));
|
||||||
|
|
||||||
playSamplerNoteAtTime(track, Number(n.key) || 0, t2, durSec);
|
playSamplerNoteAtTime(track, Number(n.key) || 0, t2, durSec);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue; // mantém: não cair na lógica de steps
|
continue; // não cai na lógica de steps
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 2) Lógica de STEP (sampler / plugin sem notes)
|
// ✅ 2) STEP (sampler/plugin sem notes)
|
||||||
if (!patt.steps) continue;
|
if (!patt.steps) continue;
|
||||||
|
|
||||||
if (patt.steps[stepInPattern]) {
|
if (patt.steps[stepInPattern]) {
|
||||||
if (track.type === "sampler" && track.player) {
|
if (track.type === "sampler" && track.player) {
|
||||||
if (track.type === "sampler" && track.player) {
|
try {
|
||||||
try {
|
// ✅ retrigger LMMS-like: não “some” quando sample é longo
|
||||||
|
if (typeof track.player.restart === "function") {
|
||||||
|
track.player.restart(time);
|
||||||
|
} else {
|
||||||
if (track.player.state === "started") track.player.stop(time);
|
if (track.player.state === "started") track.player.stop(time);
|
||||||
track.player.start(time);
|
track.player.start(time);
|
||||||
} catch {}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("[Playlist] Falha ao retrigger sample:", track.name, e);
|
||||||
}
|
}
|
||||||
} else if (track.type === "plugin" && track.instrument) {
|
} else if (track.type === "plugin" && track.instrument) {
|
||||||
// plugin sem piano roll
|
try {
|
||||||
try { track.instrument.triggerAttackRelease("C5", "16n", time); } catch {}
|
track.instrument.triggerAttackRelease("C5", "16n", time);
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, "16n");
|
}, "16n");
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export function initializePatternState() {
|
||||||
try { track.player?.dispose(); } catch {}
|
try { track.player?.dispose(); } catch {}
|
||||||
try { track.buffer?.dispose?.(); } catch {}
|
try { track.buffer?.dispose?.(); } catch {}
|
||||||
try { track.instrument?.dispose(); } catch {}
|
try { track.instrument?.dispose(); } catch {}
|
||||||
|
try { track.previewPlayer?.dispose(); } catch {}
|
||||||
});
|
});
|
||||||
|
|
||||||
appState.pattern.tracks = [];
|
appState.pattern.tracks = [];
|
||||||
|
|
@ -29,7 +30,7 @@ export function initializePatternState() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadAudioForTrack(track) {
|
export async function loadAudioForTrack(track) {
|
||||||
// 1. Garante a criação dos nós de Volume e Pan
|
// 1) Garante Volume/Pan
|
||||||
try {
|
try {
|
||||||
if (!track.volumeNode) {
|
if (!track.volumeNode) {
|
||||||
track.volumeNode = new Tone.Volume(
|
track.volumeNode = new Tone.Volume(
|
||||||
|
|
@ -46,130 +47,157 @@ export async function loadAudioForTrack(track) {
|
||||||
track.pannerNode.pan.value = track.pan ?? 0;
|
track.pannerNode.pan.value = track.pan ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Desconecta o que existir
|
||||||
try { track.instrument?.disconnect(); } catch {}
|
try { track.instrument?.disconnect(); } catch {}
|
||||||
try { track.player?.disconnect(); } catch {}
|
try { track.player?.disconnect(); } catch {}
|
||||||
|
try { track.previewPlayer?.disconnect(); } catch {}
|
||||||
|
|
||||||
|
// Reconecta cadeia base
|
||||||
try { track.volumeNode.disconnect(); } catch {}
|
try { track.volumeNode.disconnect(); } catch {}
|
||||||
try { track.pannerNode.disconnect(); } catch {}
|
try { track.pannerNode.disconnect(); } catch {}
|
||||||
|
|
||||||
track.volumeNode.connect(track.pannerNode);
|
track.volumeNode.connect(track.pannerNode);
|
||||||
track.pannerNode.connect(getMainGainNode());
|
track.pannerNode.connect(getMainGainNode());
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Erro ao criar nós de áudio base:", e);
|
console.error("Erro ao criar nós de áudio base:", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- DETECÇÃO DE TIPO DE ARQUIVO ---
|
// 2) Detecta se é um arquivo de áudio suportado
|
||||||
// Verifica se é um formato de áudio que o navegador suporta
|
const isStandardAudio =
|
||||||
const isStandardAudio = track.samplePath && /\.(wav|mp3|ogg|flac|m4a)$/i.test(track.samplePath);
|
track.samplePath && /\.(wav|mp3|ogg|flac|m4a)$/i.test(track.samplePath);
|
||||||
|
|
||||||
// Se não for áudio padrão, assumimos Plugin (ou Kicker padrão)
|
// 3) Se não for áudio padrão → Plugin (ou fallback)
|
||||||
if (!track.samplePath || !isStandardAudio) {
|
if (!track.samplePath || !isStandardAudio) {
|
||||||
try {
|
try {
|
||||||
if (track.instrument) { try { track.instrument.dispose(); } catch {} }
|
// limpa sampler/preview/buffer
|
||||||
|
try { track.player?.dispose(); } catch {}
|
||||||
|
try { track.previewPlayer?.dispose(); } catch {}
|
||||||
|
try { track.buffer?.dispose?.(); } catch {}
|
||||||
|
track.player = null;
|
||||||
|
track.previewPlayer = null;
|
||||||
|
track.buffer = null;
|
||||||
|
|
||||||
|
if (track.instrument) {
|
||||||
|
try { track.instrument.dispose(); } catch {}
|
||||||
|
track.instrument = null;
|
||||||
|
}
|
||||||
|
|
||||||
let synth;
|
let synth;
|
||||||
// Normaliza o nome do instrumento. Se vazio, assume kicker.
|
|
||||||
const name = (track.instrumentName || "kicker").toLowerCase();
|
const name = (track.instrumentName || "kicker").toLowerCase();
|
||||||
|
|
||||||
const pluginData = {};
|
const pluginData = {};
|
||||||
|
|
||||||
// SELETOR DE PLUGINS
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "tripleoscillator":
|
case "tripleoscillator":
|
||||||
case "3osc":
|
case "3osc":
|
||||||
synth = new TripleOscillator(Tone.getContext(), pluginData);
|
synth = new TripleOscillator(Tone.getContext(), pluginData);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "kicker":
|
case "kicker":
|
||||||
synth = new Kicker(Tone.getContext(), pluginData);
|
synth = new Kicker(Tone.getContext(), pluginData);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "lb302":
|
case "lb302":
|
||||||
synth = new Lb302(Tone.getContext(), pluginData);
|
synth = new Lb302(Tone.getContext(), pluginData);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "nes":
|
case "nes":
|
||||||
case "freeboy":
|
case "freeboy":
|
||||||
case "papu":
|
case "papu":
|
||||||
case "sid":
|
case "sid":
|
||||||
synth = new Nes(Tone.getContext(), pluginData);
|
synth = new Nes(Tone.getContext(), pluginData);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "zynaddsubfx":
|
case "zynaddsubfx":
|
||||||
case "watsyn":
|
case "watsyn":
|
||||||
case "monstro":
|
case "monstro":
|
||||||
case "vibedstrings":
|
case "vibedstrings":
|
||||||
case "supersaw":
|
case "supersaw":
|
||||||
synth = new SuperSaw(Tone.getContext(), pluginData);
|
synth = new SuperSaw(Tone.getContext(), pluginData);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "organic":
|
case "organic":
|
||||||
synth = new Tone.PolySynth(Tone.Synth, {
|
synth = new Tone.PolySynth(Tone.Synth, {
|
||||||
oscillator: { type: "sine", count: 8, spread: 20 }
|
oscillator: { type: "sine", count: 8, spread: 20 },
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.warn(`Plugin ${name} desconhecido, usando fallback (Kicker).`);
|
console.warn(`Plugin ${name} desconhecido, usando fallback (Kicker).`);
|
||||||
// Fallback seguro: Kicker
|
synth = new Kicker(Tone.getContext(), pluginData);
|
||||||
synth = new Kicker(Tone.getContext(), pluginData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (synth.output) {
|
// Conecta plugin na cadeia
|
||||||
synth.connect(track.volumeNode);
|
if (synth.output) synth.connect(track.volumeNode);
|
||||||
} else {
|
else synth.connect(track.volumeNode);
|
||||||
synth.connect(track.volumeNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
track.instrument = synth;
|
track.instrument = synth;
|
||||||
track.player = null;
|
track.type = "plugin";
|
||||||
track.type = 'plugin';
|
|
||||||
// Atualiza o nome se ele estava vazio
|
|
||||||
if (!track.instrumentName) track.instrumentName = name;
|
if (!track.instrumentName) track.instrumentName = name;
|
||||||
|
|
||||||
console.log(`[Audio] Plugin carregado: ${name}`);
|
console.log(`[Audio] Plugin carregado: ${name}`);
|
||||||
|
return track;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Erro ao carregar plugin:", track.instrumentName, e);
|
console.error("Erro ao carregar plugin:", track.instrumentName, e);
|
||||||
|
return track;
|
||||||
}
|
}
|
||||||
return track;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Lógica para SAMPLERS
|
// 4) SAMPLER (áudio)
|
||||||
try {
|
try {
|
||||||
try { track.player?.dispose(); } catch {}
|
// limpa plugin
|
||||||
track.player = null;
|
|
||||||
try { track.buffer?.dispose?.(); } catch {}
|
|
||||||
track.buffer = null;
|
|
||||||
|
|
||||||
if (track.instrument) {
|
if (track.instrument) {
|
||||||
try { track.instrument.dispose(); } catch {}
|
try { track.instrument.dispose(); } catch {}
|
||||||
track.instrument = null;
|
track.instrument = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const player = new Tone.Player({ url: track.samplePath, autostart: false, retrigger: true });
|
// limpa players/buffer antigos
|
||||||
|
try { track.player?.dispose(); } catch {}
|
||||||
|
try { track.previewPlayer?.dispose(); } catch {}
|
||||||
|
try { track.buffer?.dispose?.(); } catch {}
|
||||||
|
track.player = null;
|
||||||
|
track.previewPlayer = null;
|
||||||
|
track.buffer = null;
|
||||||
|
|
||||||
|
// Player principal (Playlist/steps)
|
||||||
|
const player = new Tone.Player({
|
||||||
|
url: track.samplePath,
|
||||||
|
autostart: false,
|
||||||
|
retrigger: true,
|
||||||
|
});
|
||||||
|
|
||||||
// redundância segura p/ builds diferentes do Tone:
|
// redundância segura p/ builds diferentes do Tone:
|
||||||
try { player.retrigger = true; } catch {}
|
try { player.retrigger = true; } catch {}
|
||||||
|
|
||||||
await player.load(track.samplePath);
|
await player.load(track.samplePath);
|
||||||
|
|
||||||
player.connect(track.volumeNode);
|
player.connect(track.volumeNode);
|
||||||
|
|
||||||
const buffer = new Tone.Buffer();
|
// ✅ reutiliza o MESMO buffer do player (sem segundo download)
|
||||||
await buffer.load(track.samplePath);
|
track.buffer = player.buffer;
|
||||||
|
|
||||||
track.player = player;
|
track.player = player;
|
||||||
track.buffer = buffer;
|
|
||||||
track.type = 'sampler';
|
|
||||||
|
|
||||||
|
// Preview player (Pattern Editor) — separado pra não brigar com a playlist
|
||||||
|
const previewPlayer = new Tone.Player({
|
||||||
|
autostart: false,
|
||||||
|
retrigger: true,
|
||||||
|
});
|
||||||
|
try { previewPlayer.retrigger = true; } catch {}
|
||||||
|
previewPlayer.buffer = track.buffer;
|
||||||
|
previewPlayer.connect(track.volumeNode);
|
||||||
|
|
||||||
|
track.previewPlayer = previewPlayer;
|
||||||
|
|
||||||
|
track.type = "sampler";
|
||||||
|
return track;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao carregar sample:', track.samplePath);
|
console.error("Erro ao carregar sample:", track.samplePath, error);
|
||||||
try { track.player?.dispose(); } catch {}
|
try { track.player?.dispose(); } catch {}
|
||||||
|
try { track.previewPlayer?.dispose(); } catch {}
|
||||||
try { track.buffer?.dispose?.(); } catch {}
|
try { track.buffer?.dispose?.(); } catch {}
|
||||||
track.player = null;
|
track.player = null;
|
||||||
|
track.previewPlayer = null;
|
||||||
track.buffer = null;
|
track.buffer = null;
|
||||||
|
return track;
|
||||||
}
|
}
|
||||||
return track;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addTrackToState() {
|
export function addTrackToState() {
|
||||||
|
|
@ -229,6 +257,7 @@ export function removeTrackById(trackId) {
|
||||||
try { trackToRemove.instrument?.dispose(); } catch {}
|
try { trackToRemove.instrument?.dispose(); } catch {}
|
||||||
try { trackToRemove.pannerNode?.disconnect(); } catch {}
|
try { trackToRemove.pannerNode?.disconnect(); } catch {}
|
||||||
try { trackToRemove.volumeNode?.disconnect(); } catch {}
|
try { trackToRemove.volumeNode?.disconnect(); } catch {}
|
||||||
|
try { trackToRemove.previewPlayer?.dispose(); } catch {}
|
||||||
|
|
||||||
// Remove do array
|
// Remove do array
|
||||||
appState.pattern.tracks.splice(index, 1);
|
appState.pattern.tracks.splice(index, 1);
|
||||||
|
|
@ -248,6 +277,7 @@ export function removeLastTrackFromState() {
|
||||||
const trackToRemove = appState.pattern.tracks[appState.pattern.tracks.length - 1];
|
const trackToRemove = appState.pattern.tracks[appState.pattern.tracks.length - 1];
|
||||||
|
|
||||||
try { trackToRemove.player?.dispose(); } catch {}
|
try { trackToRemove.player?.dispose(); } catch {}
|
||||||
|
try { trackToRemove.previewPlayer?.dispose(); } catch {}
|
||||||
try { trackToRemove.buffer?.dispose?.(); } catch {}
|
try { trackToRemove.buffer?.dispose?.(); } catch {}
|
||||||
try { trackToRemove.instrument?.dispose(); } catch {}
|
try { trackToRemove.instrument?.dispose(); } catch {}
|
||||||
try { trackToRemove.pannerNode?.disconnect(); } catch {}
|
try { trackToRemove.pannerNode?.disconnect(); } catch {}
|
||||||
|
|
@ -260,6 +290,7 @@ export function removeLastTrackFromState() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function updateTrackSample(trackIndex, samplePath) {
|
export async function updateTrackSample(trackIndex, samplePath) {
|
||||||
const track = appState.pattern.tracks[trackIndex];
|
const track = appState.pattern.tracks[trackIndex];
|
||||||
if (track) {
|
if (track) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue