23 lines
2.1 KiB
JavaScript
23 lines
2.1 KiB
JavaScript
import fs from 'node:fs';import path from 'node:path';
|
|
const rate=22050;
|
|
function writeWav(root,name,samples){const n=samples.length,b=Buffer.alloc(44+n*2);b.write('RIFF');b.writeUInt32LE(b.length-8,4);b.write('WAVEfmt ',8);b.writeUInt32LE(16,16);b.writeUInt16LE(1,20);b.writeUInt16LE(1,22);b.writeUInt32LE(rate,24);b.writeUInt32LE(rate*2,28);b.writeUInt16LE(2,32);b.writeUInt16LE(16,34);b.write('data',36);b.writeUInt32LE(n*2,40);for(let i=0;i<n;i++)b.writeInt16LE(Math.round(Math.max(-1,Math.min(1,samples[i]))*32767),44+i*2);fs.writeFileSync(path.join(root,'music',name+'.wav'),b);}
|
|
export function buildMusic(root){
|
|
fs.mkdirSync(path.join(root,'music'),{recursive:true});
|
|
// Selection music is byte-for-byte unchanged.
|
|
const select=new Float64Array(rate*16),notes=[60,67,72,64,69,67,64,62];
|
|
for(let k=0;k<notes.length;k++){const f=440*2**((notes[k]-69)/12),offset=k*2;for(let j=0;j<rate*4;j++){const t=j/rate,env=(1-Math.exp(-t/.025))*Math.exp(-t/.65)*Math.min(1,(4-t)/.15);select[(offset*rate+j)%select.length]+=(Math.sin(2*Math.PI*f*t)+.12*Math.sin(4*Math.PI*f*t))*env*.24;}}
|
|
writeWav(root,'select',select);
|
|
// Original 32-second warm pad: no percussion, note attacks or recurring beat.
|
|
// Integer cycles and periodic crossfades preserve both waveform and slope at wrap.
|
|
const seconds=32,samples=new Float64Array(rate*seconds),chords=[[48,55,60,64],[45,52,55,60],[48,55,62,67]];
|
|
const voices=chords.map((notes,k)=>notes.map((midi,j)=>({f:Math.round(440*2**((midi-69)/12)*seconds)/seconds,phase:(k*1.7+j*.9),gain:j===0?.75:1})));
|
|
let sum=0,peak=0;
|
|
for(let i=0;i<samples.length;i++){const t=i/rate;let value=0;
|
|
for(let k=0;k<voices.length;k++){const weight=(1-Math.cos(2*Math.PI*(t/seconds-k/voices.length)))/2;
|
|
for(const v of voices[k]){const phase=2*Math.PI*v.f*t+v.phase;value+=weight*v.gain*(Math.sin(phase)+.055*Math.sin(2*phase)+.012*Math.sin(3*phase));}}
|
|
samples[i]=value;sum+=value*value;peak=Math.max(peak,Math.abs(value));
|
|
}
|
|
const gain=Math.min(.035/Math.sqrt(sum/samples.length),.16/peak);for(let i=0;i<samples.length;i++)samples[i]*=gain;
|
|
writeWav(root,'play',samples);
|
|
}
|