ditty.bpm = 40; // BPM original ≈ 92
// --- Sintetizador: piano suave tipo Rhodes (onda sinusoidal con sustain largo)
const piano = synth.def((phase, env, tick, opt) => {
// Onda sinusoidal suave + ligera distorsión armónica sutil
const sine = Math.sin(phase * 2 * Math.PI);
// Añadir un armónico suave para dar calidez (como un piano real)
const harmonic = 0.3 * Math.sin(2 * phase * 2 * Math.PI);
return (sine + harmonic) * env.value;
});
// --- Convertir PNt a formato [start, midi, end] ---
const notes = [
[0.00000,24.0,0.25000], [0.00000,28.0,0.25000], [0.00000,33.0,0.25000], [0.00000,52.0,0.25000],
[0.25000,24.0,0.50000], [0.25000,28.0,0.50000], [0.25000,33.0,0.50000], [0.25000,52.0,0.50000],
[0.50000,23.0,1.50000], [0.50000,28.0,1.50000], [0.50000,31.0,1.50000], [0.50000,52.0,0.87500],
[1.50000,57.0,1.56250], [1.56250,55.0,1.62500],
[1.62500,26.0,2.12500], [1.62500,30.0,2.12500], [1.62500,33.0,2.12500], [1.62500,57.0,1.87500],
[1.87500,59.0,2.12500],
[2.12500,24.0,3.12500], [2.12500,28.0,3.12500], [2.12500,31.0,3.12500], [2.12500,55.0,2.50000],
[2.87500,52.0,3.12500],
[3.12500,50.0,3.37500],
[3.37500,23.0,4.00000], [3.37500,26.0,4.00000], [3.37500,31.0,4.00000], [3.37500,43.0,3.62500],
[3.62500,45.0,3.75000], [3.75000,50.0,3.87500], [3.87500,55.0,4.00000],
[4.00000,57.0,4.12500],
[4.12500,26.0,4.75000], [4.12500,30.0,4.75000], [4.12500,33.0,4.75000], [4.12500,54.0,4.37500],
[4.37500,52.0,4.50000], [4.50000,54.0,4.75000], [4.75000,52.0,4.87500],
[4.81250,24.0,7.31250], [4.81250,28.0,7.31250], [4.81250,31.0,7.31250],
[4.87500,50.0,5.37500],
[6.12500,48.0,6.18750], [6.18750,50.0,6.25000], [6.25000,52.0,6.31250], [6.31250,55.0,6.37500],
[6.37500,60.0,6.43750], [6.43750,62.0,6.50000], [6.43750,48.0,6.50000],
[6.50000,50.0,6.56250], [6.56250,52.0,6.62500], [6.62500,55.0,6.68750],
[6.68750,60.0,6.75000], [6.75000,62.0,6.81250], [6.81250,64.0,6.87500],
[6.87500,67.0,7.62500],
[7.62500,24.0,8.12500], [7.62500,28.0,8.12500], [7.62500,33.0,8.12500], [7.62500,60.0,8.12500],
[8.18750,23.0,8.68750], [8.18750,28.0,8.68750], [8.18750,31.0,8.68750], [8.18750,59.0,8.68750],
[8.75000,26.0,9.25000], [8.75000,30.0,9.25000], [8.75000,33.0,9.25000], [8.75000,57.0,9.00000],
[9.00000,55.0,9.25000],
[9.25000,24.0,9.87500], [9.25000,26.0,9.87500], [9.25000,31.0,9.87500], [9.25000,55.0,9.62500],
[9.62500,55.0,9.87500],
[9.93750,23.0,10.31250], [9.93750,26.0,10.31250], [9.93750,31.0,10.31250], [9.93750,47.0,10.12500],
[10.12500,45.0,10.31250],
[10.37500,26.0,10.87500], [10.37500,30.0,10.87500], [10.37500,33.0,10.87500], [10.37500,47.0,10.50000],
[10.50000,54.0,10.68750], [10.68750,52.0,10.87500],
[10.93750,24.0,11.93750], [10.93750,26.0,11.93750], [10.93750,31.0,11.93750], [10.93750,50.0,11.06250],
[11.06250,43.0,11.43750], [11.43750,43.0,11.68750], [11.68750,43.0,11.93750],
[11.93750,57.0,12.00000], [12.00000,59.0,12.06250],
[12.06250,24.0,12.56250], [12.06250,28.0,12.56250], [12.06250,33.0,12.56250], [12.06250,60.0,12.56250],
[12.62500,23.0,13.12500], [12.62500,28.0,13.12500], [12.62500,31.0,13.12500], [12.62500,59.0,13.12500],
[13.18750,26.0,13.56250], [13.18750,30.0,13.56250], [13.18750,33.0,13.56250], [13.18750,57.0,13.37500],
[13.37500,55.0,13.56250],
[13.59375,24.0,14.09375], [13.59375,26.0,14.09375], [13.59375,31.0,14.09375], [13.59375,55.0,13.96875],
[13.96875,55.0,14.09375],
[14.15625,23.0,14.53125], [14.15625,26.0,14.53125], [14.15625,31.0,14.53125], [14.15625,47.0,14.34375],
[14.34375,45.0,14.53125],
[14.56250,26.0,15.06250], [14.56250,30.0,15.06250], [14.56250,33.0,15.06250], [14.56250,47.0,14.68750],
[14.68750,54.0,14.93750], [14.93750,52.0,15.12500],
[15.12500,24.0,16.12500], [15.12500,26.0,16.12500], [15.12500,31.0,16.12500], [15.12500,50.0,15.25000],
[15.25000,43.0,15.75000], [15.75000,43.0,15.93750], [15.93750,43.0,16.12500]
];
// Ordenar por tiempo de inicio (por si acaso)
notes.sort((a, b) => a[0] - b[0]);
let noteIndex = 0;
const totalDuration = notes[notes.length - 1][2]; // último "end"
// --- Loop principal (como en tu ejemplo) ---
loop(() => {
let currentTime = 0;
const minStep = 0.0625; // resolución de 1/16
while (currentTime <= totalDuration + 0.1) {
// Disparar todas las notas que empiezan en currentTime
while (
noteIndex < notes.length &&
Math.abs(notes[noteIndex][0] - currentTime) < 1e-5
) {
const [start, midi, end] = notes[noteIndex];
const duration = end - start;
piano.play(midi+36, {
duration: duration,
attack: 0.1, // ataque suave (como un piano real)
decay: 0.3,
sustain: 0.5, // sustain alto para que los acordes suenen
release: 0.6, // release largo = notas se funden
amp: 0.85
});
noteIndex++;
}
sleep(minStep);
currentTime += minStep;
}
// Reiniciar para repetir
noteIndex = 0;
}, { name: '2Pac - Changes (Piano Sample)' });