gameboy-sound
Advanced tools
Comparing version 0.1.1 to 0.1.2
@@ -27,3 +27,3 @@ window.AudioContext = window.AudioContext || window.webkitAudioContext; | ||
function baseChannel(ctx, dest=ctx.destination) { | ||
function baseChannel(/**@type{AudioContext}*/ctx, dest=ctx.destination) { | ||
const gainNode = ctx.createGain(); | ||
@@ -43,5 +43,28 @@ gainNode.gain.setValueAtTime(0, ctx.currentTime); | ||
/** | ||
* Previous hz changes are tracked and applied retroactively | ||
* to all future buffers played later on. This is to keep the | ||
* waveforms in sync to prevent popping when a new note is | ||
* played, making vibrato possible | ||
*/ | ||
let prevHertzChanges = []; | ||
/** @type{AudioBufferSourceNode} */ | ||
let currentSourceNode = null; | ||
return function play({ buffer, rate, rateChanges=[], trigger=true, length=Infinity, volume=15, fade=0, left=true, right=true, time=0 }) { | ||
/** | ||
* TODO!!! | ||
* two wrongs do make a right! | ||
* the value passed in to "hertz" is not actually the hertz of the waveform! | ||
* however, this function also calculates things wrong, in exactly the right way | ||
* so it all still works... | ||
* | ||
* when refactoring it to use the real hertz value (maybe?), | ||
* make sure to test with vibrato to ensure that there's no popping | ||
*/ | ||
return function play({ buffer, hertz, hertzChanges=[], trigger=true, length=Infinity, volume=15, fade=0, left=true, right=true, time=0 }) { | ||
// calculate playback rate(s) | ||
prevHertzChanges = prevHertzChanges.filter(x => x.time < time); | ||
prevHertzChanges.push({ time, hertz }); | ||
// no left/right = no sound | ||
if (!left && !right) { | ||
@@ -58,6 +81,19 @@ volume = 0; | ||
sourceNode.buffer = buffer; | ||
sourceNode.playbackRate.setValueAtTime(rate, time) | ||
for (const x of prevHertzChanges) { | ||
const rate = buffer.sampleRate/buffer.length/x.hertz; | ||
sourceNode.playbackRate.setValueAtTime(rate, x.time); | ||
} | ||
sourceNode.loop = true; | ||
const prevWiggles = prevHertzChanges.reduce((sum, x, i, src) => { | ||
if (i === 0) return 0; | ||
return (sum + (x.time-src[i-1].time) * buffer.sampleRate/buffer.length/src[i-1].hertz); | ||
// TODO "real hertz" calculations | ||
// const realhertz = 1/(src[i-1].hertz * buffer.duration**2); | ||
// console.log('realh',realhertz) | ||
// return (sum + (x.time-src[i-1].time) * realhertz); | ||
}, 0); | ||
const offset = prevWiggles % buffer.duration | ||
// console.log(buffer.duration, offset); | ||
sourceNode.connect(gainNode); | ||
sourceNode.start(time); | ||
sourceNode.start(time, offset); | ||
currentSourceNode = sourceNode; | ||
@@ -80,8 +116,11 @@ // volume + envelope + length | ||
// rate changes | ||
rateChanges.forEach(({ rate, time }) => { | ||
if (rate == null) { | ||
hertzChanges.forEach(({ hertz, time }) => { | ||
if (hertz == null) { | ||
gainNode.gain.cancelScheduledValues(time); | ||
gainNode.gain.setValueAtTime(0, time); | ||
} | ||
else sourceNode.playbackRate.setValueAtTime(rate, time) | ||
else { | ||
const rate = buffer.sampleRate/buffer.length/hertz; | ||
sourceNode.playbackRate.setValueAtTime(rate, time) | ||
} | ||
}); | ||
@@ -115,5 +154,10 @@ // pan | ||
const buffer = pulseWaves[duty]; | ||
const rate = 256 / (2048-freq); | ||
const hertz = 2 * (2048-freq); | ||
// TODO "real hertz" calculations | ||
// console.log(buffer.sampleRate/buffer.length, buffer.duration) | ||
// const realhertz = 1/((2 * (2048 - freq)) * buffer.duration**2); | ||
const rateChanges = []; | ||
// console.log(realhertz, realhertz*buffer.duration); | ||
const hertzChanges = []; | ||
if (sweepFactor !== 0) { | ||
@@ -132,12 +176,12 @@ let shadowFreq = freq; | ||
if (shadowFreq + dFreq >= 2048) { | ||
rateChanges.push({ time: t1, rate: null }); | ||
hertzChanges.push({ time: t1, hertz: null }); | ||
break; | ||
} | ||
const rate = 256 / (2048-shadowFreq); | ||
rateChanges.push({ time: t1, rate }); | ||
const hertz = 2 * (2048-shadowFreq); | ||
hertzChanges.push({ time: t1, hertz }); | ||
} | ||
} | ||
ch({ buffer, rate, rateChanges, trigger, length, volume, fade, left, right, time }); | ||
ch({ buffer, hertz, hertzChanges, trigger, length, volume, fade, left, right, time }); | ||
}; | ||
@@ -163,6 +207,7 @@ } | ||
return function ({ samples=defaultPCM, freq=1798, trigger, length, left, right, time }) { | ||
/** @type{AudioBuffer} */ | ||
const buffer = (lastSamples && samples.join() === lastSamples) ? | ||
cachedBuffer : | ||
samplesToBuffer(samples); | ||
const rate = 256 / (2048-freq); | ||
const hertz = (2048-freq); | ||
@@ -172,3 +217,3 @@ lastSamples = samples.join(); | ||
ch({ buffer, rate, trigger, length, left, right, time }); | ||
ch({ buffer, hertz, trigger, length, left, right, time }); | ||
}; | ||
@@ -208,9 +253,10 @@ } | ||
const cats = upsampleAmounts.map(n=>256*n); | ||
cats[cats.length-1]=Infinity; | ||
return function ({ buzzy=false, freq=3<<7, trigger, length, volume, fade, left, right, time }) { | ||
const cats = upsampleAmounts.map(n=>256*n); | ||
cats[cats.length-1]=Infinity; | ||
const resampleCategory = cats.findIndex(c => freq < c); | ||
const buffer = resampledNoiseWaves[resampleCategory][+buzzy]; | ||
const rate = 0x40 / freq * upsampleAmounts[resampleCategory]; | ||
ch({ buffer, rate, trigger, length, volume, fade, left, right, time }) | ||
const hertz = (buffer.sampleRate/buffer.length) * freq / upsampleAmounts[resampleCategory] / 0x40; | ||
ch({ buffer, hertz, trigger, length, volume, fade, left, right, time }) | ||
}; | ||
@@ -217,0 +263,0 @@ } |
{ | ||
"name": "gameboy-sound", | ||
"version": "0.1.1", | ||
"version": "0.1.2", | ||
"description": "Easy 3 kB library for 98% accurate Gameboy audio", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
15965
315