Skip to content

Instantly share code, notes, and snippets.

@vurpo
Created November 27, 2024 17:38
Show Gist options
  • Save vurpo/2ae0c1b070281c08f21df465c673f721 to your computer and use it in GitHub Desktop.
Save vurpo/2ae0c1b070281c08f21df465c673f721 to your computer and use it in GitHub Desktop.
AudioBufferPlayer with loop point functionality
// input ports
const audioBufferPort = op.inObject("Audio Buffer", null, "audioBuffer");
const playPort = op.inBool("Start / Stop", false);
const inResetStart = op.inTriggerButton("Restart");
const offsetPort = op.inFloat("Offset", 0);
const loopPointPort = op.inFloat("Loop point", 0);
const playbackRatePort = op.inFloat("Playback Rate", 1);
const detunePort = op.inFloat("Detune", 0);
op.setPortGroup("Playback Controls", [playPort, inResetStart]);
op.setPortGroup("Time Controls", [offsetPort, loopPointPort]);
op.setPortGroup("Miscellaneous", [playbackRatePort, detunePort]);
// output ports
const audioOutPort = op.outObject("Audio Out", null, "audioNode");
const outPlaying = op.outBool("Is Playing", false);
const outLoading = op.outBool("Loading", false);
// vars
let source = null;
let isPlaying = false;
let hasEnded = false;
let pausedAt = null;
let startedAt = null;
let isLoading = false;
const audioCtx = CABLES.WEBAUDIO.createAudioContext(op);
const gainNode = audioCtx.createGain();
if (!audioBufferPort.isLinked())
{
op.setUiError("inputNotConnected", "To be able to play back sound, you need to connect an AudioBuffer to this op.", 0);
}
else
{
op.setUiError("inputNotConnected", null);
}
audioBufferPort.onLinkChanged = () =>
{
if (!audioBufferPort.isLinked())
{
op.setUiError("inputNotConnected", "To be able to play back sound, you need to connect an AudioBuffer to this op.", 0);
}
else
{
op.setUiError("inputNotConnected", null);
}
};
if (!audioOutPort.isLinked())
{
op.setUiError("outputNotConnected", "To be able to hear sound playing, you need to connect this op to an Output op.", 0);
}
else
{
op.setUiError("outputNotConnected", null);
}
audioOutPort.onLinkChanged = () =>
{
if (!audioOutPort.isLinked())
{
op.setUiError("outputNotConnected", "To be able to hear sound playing, you need to connect this op to an Output op.", 0);
}
else
{
op.setUiError("outputNotConnected", null);
}
};
// change listeners
audioBufferPort.onChange = function ()
{
if (audioBufferPort.get()) createAudioBufferSource();
else
{
if (isLoading)
{
isLoading = false;
outLoading.set(isLoading);
}
if (isPlaying)
{
stop(0);
source.buffer = null;
source = null;
}
}
};
playPort.onChange = function ()
{
if (!audioBufferPort.get()) return;
if (!source)
{
if (!isLoading) createAudioBufferSource();
}
if (playPort.get())
{
const startTime = 0;
start(startTime);
}
else
{
const stopTime = 0;
stop(stopTime);
}
};
detunePort.onChange = setDetune;
function setDetune()
{
if (!source) return;
const detune = detunePort.get() || 0;
if (source.detune)
{
source.detune.setValueAtTime(
detune,
audioCtx.currentTime
);
}
}
playbackRatePort.onChange = setPlaybackRate;
function setPlaybackRate()
{
if (!source) return;
const playbackRate = playbackRatePort.get() || 0;
if (playbackRate >= source.playbackRate.minValue && playbackRate <= source.playbackRate.maxValue)
{
source.playbackRate.setValueAtTime(
playbackRate,
audioCtx.currentTime
);
}
}
let resetTriggered = false;
inResetStart.onTriggered = function ()
{
if (!source) return;
if (!audioBufferPort.get()) return;
else
{
if (!(audioBufferPort.get() instanceof AudioBuffer)) return;
}
if (playPort.get())
{
if (isPlaying)
{
resetTriggered = true;
stop(0);
}
else
{
start(0);
}
}
};
// functions
function createAudioBufferSource(dontStart = false)
{
if (isLoading) return;
if (!(audioBufferPort.get() instanceof AudioBuffer)) return;
isLoading = true;
outLoading.set(isLoading);
if (source)
{
source.onended = null;
if (source.buffer)
{
stop(0);
source.disconnect(gainNode);
source.buffer = null;
}
source = null;
}
source = audioCtx.createBufferSource();
const buffer = audioBufferPort.get();
if (!buffer)
{
isLoading = false;
outLoading.set(isLoading);
return;
}
source.buffer = buffer;
source.loop = true;
source.loopStart = loopPointPort.get();
source.loopEnd = buffer.duration;
source.connect(gainNode);
setPlaybackRate();
setDetune();
audioOutPort.set(gainNode);
isLoading = false;
outLoading.set(isLoading);
if (resetTriggered)
{
start(0);
resetTriggered = false;
return;
}
if (playPort.get() && !dontStart)
{
// if (!isPlaying)
start(0);
}
}
let timeOuting = false;
let timerId = null;
offsetPort.onChange = () =>
{
if (offsetPort.get() >= 0) op.setUiError("offsetNegative", null);
else
{
op.setUiError("offsetNegative", "Offset cannot be negative. Setting to 0.", 1);
}
if (source)
{
if (source.buffer)
{
if (offsetPort.get() > source.buffer.duration)
{
op.setUiError("offsetTooLong", "Your offset value is higher than the total time of your audio file. Please decrease the duration to be able to hear sound when playing back your buffer.", 1);
}
else
{
op.setUiError("offsetTooLong", null);
}
}
}
};
loopPointPort.onChange = () => {
if (source) {
source.loopStart = loopPointPort.get();
}
}
function start(time)
{
try
{
if (source)
{
let offset = Math.max(0, offsetPort.get());
source.start(time, offset); // 0 = now
isPlaying = true;
hasEnded = false;
outPlaying.set(true);
}
else
{
op.log("start() but no src...");
}
}
catch (e)
{
op.log("Error on start: ", e.message);
outPlaying.set(false);
isPlaying = false;
}
}
function recreateBuffer()
{
createAudioBufferSource(false);
}
function stop(time)
{
try
{
if (source)
{
source.stop();
if (!resetTriggered) recreateBuffer();
}
isPlaying = false;
outPlaying.set(false);
}
catch (e)
{
op.setUiError(e);
outPlaying.set(false);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment