Created
November 27, 2024 17:38
-
-
Save vurpo/2ae0c1b070281c08f21df465c673f721 to your computer and use it in GitHub Desktop.
AudioBufferPlayer with loop point functionality
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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