// js/plugins/TripleOscillator.js import * as Tone from "https://esm.sh/tone"; export class TripleOscillator { constructor(toneContext, data = {}) { // Não precisamos mais do contexto cru, usamos o Tone direto this.params = data.tripleoscillator || data || {}; // Volume Mestre do Instrumento this.output = new Tone.Gain(1); // Começa com volume 1 // Envelope de Amplitude (ADSR) // Convertendo valores do LMMS (geralmente 0-1 ou 0-100) para segundos/níveis const envData = data.elvol || {}; this.envelope = new Tone.AmplitudeEnvelope({ attack: this.normalizeParam(envData.att, 0, 0.01), decay: this.normalizeParam(envData.dec, 0.5, 0.1), sustain: this.normalizeParam(envData.sustain, 1, 0.5), release: this.normalizeParam(envData.rel, 0, 0.1) }); // Conecta Envelope -> Saída this.envelope.connect(this.output); } normalizeParam(val, defaultVal, scale = 1) { if (val === undefined || val === null) return defaultVal; let v = parseFloat(val); // Se vier > 2, assume escala 0-100 e divide if (v > 2) v /= 100; return v || defaultVal; // Fallback se for 0 ou NaN } getWaveType(lmmsTypeIndex) { const types = ["sine", "triangle", "sawtooth", "square", "sawtooth", "sawtooth"]; // Mapeamento básico const idx = parseInt(lmmsTypeIndex); return types[isNaN(idx) ? 0 : idx]; } // Método chamado pela UI (Step Sequencer) triggerAttackRelease(note, duration, time) { const freq = Tone.Frequency(note).toFrequency(); const dur = Tone.Time(duration).toSeconds(); const now = time || Tone.now(); // Cria os 3 osciladores efêmeros (só para esta nota) // Isso é necessário para polifonia (cada nota tem seus osciladores) for (let i = 0; i < 3; i++) { const volRaw = this.params[`vol${i}`]; // Default: Osc 1 (índice 0) = 100%, outros = 0% const vol = (volRaw !== undefined) ? parseInt(volRaw) : (i === 0 ? 100 : 0); if (vol > 0) { const osc = new Tone.Oscillator({ type: this.getWaveType(this.params[`wavetype${i}`]), frequency: freq, detune: (parseInt(this.params[`coarse${i}`] || 0) * 100) + parseInt(this.params[`fine${i}`] || 0), volume: Tone.gainToDb((vol / 100) * 0.3), // 0.3 para evitar clipar a soma onstop: () => { osc.dispose(); } // Limpa memória ao acabar }); // Conecta Osc -> Envelope (Mestre) osc.connect(this.envelope); osc.start(now); osc.stop(now + dur + this.envelope.release); } } // Dispara o envelope this.envelope.triggerAttackRelease(dur, now); } connect(dest) { this.output.connect(dest); } dispose() { this.output.dispose(); this.envelope.dispose(); } }