From 5974cbacc9d65a08e93ce83b9a88797f18fc7afe Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:54:00 +0300 Subject: [PATCH 01/12] Remove `updatePlayers` system --- src/audio/systems/index.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/audio/systems/index.js b/src/audio/systems/index.js index 37d74372..cd8e1740 100644 --- a/src/audio/systems/index.js +++ b/src/audio/systems/index.js @@ -84,19 +84,6 @@ export function playOscillators(world) { }) } -/** - * @param {World} world - */ -export function updatePlayers(world) { - const players = new Query(world, [AudioPlayer]) - const clock = world.getResource(VirtualClock) - const delta = clock.getDelta() - - players.each(([player]) => { - player.playback.update(delta) - }) -} - /** * @param {World} world */ From 941645fe5a4d4fd2bcace73f1afd139e8a41e5d5 Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:54:00 +0300 Subject: [PATCH 02/12] Remove `updateOscillators` system --- src/audio/systems/index.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/audio/systems/index.js b/src/audio/systems/index.js index cd8e1740..6d03e423 100644 --- a/src/audio/systems/index.js +++ b/src/audio/systems/index.js @@ -84,19 +84,6 @@ export function playOscillators(world) { }) } -/** - * @param {World} world - */ -export function updateOscillators(world) { - const oscillators = new Query(world, [AudioOscillator]) - const clock = world.getResource(VirtualClock) - const delta = clock.getDelta() - - oscillators.each(([oscillator]) => { - oscillator.playback.update(delta) - }) -} - /** * @param {TimerMode} playbackMode */ From f22c1339c52d0f01de71594e343da59a05255b01 Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:55:00 +0300 Subject: [PATCH 03/12] Update `playAudio` system --- src/audio/systems/index.js | 41 ++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/audio/systems/index.js b/src/audio/systems/index.js index 6d03e423..4989f513 100644 --- a/src/audio/systems/index.js +++ b/src/audio/systems/index.js @@ -1,5 +1,5 @@ import { Query, World } from '../../ecs/index.js' -import { VirtualClock, TimerMode } from '../../time/index.js' +import { Timer, TimerMode } from '../../time/index.js' import { AudioAssets, AudioGraph } from '../resources/index.js' import { AudioPlayer, AudioOscillator, AudioOscillatorType } from '../components/index.js' @@ -10,13 +10,12 @@ import { AudioPlayer, AudioOscillator, AudioOscillatorType } from '../components export function playAudio(world) { const graph = world.getResource(AudioGraph) const audioSources = world.getResource(AudioAssets) - const players = new Query(world, [AudioPlayer]) + const players = new Query(world, [AudioPlayer, Timer]) const ctx = graph.getContext() const root = graph.getRoot() - - - players.each(([player]) => { - const { audio, sourceNode, playback, attach } = player + + players.each(([player, playback]) => { + const { audio, sourceNode, attach } = player if (!audio) return @@ -26,14 +25,31 @@ export function playAudio(world) { return } if (sourceNode) { + if (playback.completed()) { + graph.update(sourceNode, undefined) + } + + if (!playback.playbackChanged()) return + if (playback.paused) { + graph.update(sourceNode, undefined) + + return + } - // update node if a play/pause or start/stop is requested - } else { const node = new AudioBufferSourceNode(ctx, { buffer: source.audiobuffer, - loop: looped(playback.mode) + loop: looped(playback.mode), + playbackRate: playback.speed }) - const id = graph.add(node) + + node.start(0, playback.elapsed()) + graph.update(sourceNode, node) + + } else { + + // The Oscillator will play in the next frame.. + // TODO: Maybe do this in a component hook? + const id = graph.add(undefined) if (attach) { graph.connect(id, attach) @@ -41,8 +57,11 @@ export function playAudio(world) { graph.connect(id, root) } - node.start(0, playback.elapsed()) + const elapsed = playback.elapsed() + playback.duration = source.audiobuffer.duration + playback.reset() + playback.seek(elapsed) player.sourceNode = id } }) From b1689d4a96fdc7740b41432bda4739d52b417943 Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:55:00 +0300 Subject: [PATCH 04/12] Update `playOscillators` system --- src/audio/systems/index.js | 43 ++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/audio/systems/index.js b/src/audio/systems/index.js index 4989f513..1ce36ca2 100644 --- a/src/audio/systems/index.js +++ b/src/audio/systems/index.js @@ -72,32 +72,49 @@ export function playAudio(world) { */ export function playOscillators(world) { const graph = world.getResource(AudioGraph) - const oscillators = new Query(world, [AudioOscillator]) + const oscillators = new Query(world, [AudioOscillator, Timer]) const ctx = graph.getContext() const root = graph.getRoot() - - - oscillators.each(([oscillator]) => { - const { type, frequency, sourceNode, detune, playback, attach } = oscillator - + + oscillators.each(([oscillator, playback]) => { + const { type, frequency, sourceNode, detune, attach } = oscillator + if (sourceNode) { - - // update node if a playback is requested - } else { + if (playback.completed()) { + graph.update(sourceNode, undefined) + } + if (!playback.playbackChanged()) return + if (playback.paused) { + graph.update(sourceNode, undefined) + + return + } + const node = new OscillatorNode(ctx, { detune, frequency, type: mapType(type) }) - const id = graph.add(node) - + + node.start(0) + graph.update(sourceNode, node) + + } else { + + // SAFETY: Since we dont know when the actual audio source will load, we set this node's weight to undefined. + // TODO: Maybe do this in a component hook? + const id = graph.add(undefined) + if (attach) { graph.connect(id, attach) } else { graph.connect(id, root) } - - node.start(playback.elapsed()) + + const elapsed = playback.elapsed() + + playback.reset() + playback.seek(elapsed) oscillator.sourceNode = id } }) From 8a5f4f3011fa5d4205a7a1841fe83b97ef78501f Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:55:00 +0300 Subject: [PATCH 05/12] Remove obsolete systems from `AudioPlugin` --- src/audio/plugin.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/audio/plugin.js b/src/audio/plugin.js index cc4a9fe7..04618933 100644 --- a/src/audio/plugin.js +++ b/src/audio/plugin.js @@ -6,7 +6,7 @@ import { Audio } from './assets/index.js' import { AudioPlayer, AudioOscillator, removeAudioPlayerSink, removeOscillatorSink } from './components/index.js' import { AudioAdded, AudioDropped, AudioModified } from './events/index.js' import { AudioCommands, AudioParser, AudioAssets, AudioGraph } from './resources/index.js' -import { playAudio, updatePlayers, playOscillators, updateOscillators } from './systems/index.js' +import { playAudio, playOscillators } from './systems/index.js' export class AudioPlugin extends Plugin { @@ -44,8 +44,6 @@ export class AudioPlugin extends Plugin { })) .registerSystem(AppSchedule.Update, playAudio) .registerSystem(AppSchedule.Update, playOscillators) - .registerSystem(AppSchedule.Update, updatePlayers) - .registerSystem(AppSchedule.Update, updateOscillators) window.addEventListener('pointerdown', resumeAudio) From dccd9ff5e993eb4eb42bc07627a4780232534ba1 Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:55:00 +0300 Subject: [PATCH 06/12] Update audio demos --- demos/demos/audio/audioOscillator.js | 7 ++++++- demos/demos/audio/audioPlayer.js | 10 +++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/demos/demos/audio/audioOscillator.js b/demos/demos/audio/audioOscillator.js index 2f1245c8..5b550293 100644 --- a/demos/demos/audio/audioOscillator.js +++ b/demos/demos/audio/audioOscillator.js @@ -3,7 +3,9 @@ import { World, AudioOscillator, EntityCommands, - Cleanup + Cleanup, + Timer, + TimerMode } from 'wima' import { addDefaultCamera3D } from '../utils.js' @@ -22,6 +24,9 @@ function init(world) { .spawn() .insertPrefab([ new AudioOscillator(), + new Timer({ + mode: TimerMode.Repeat + }), new Cleanup() ]) .build() diff --git a/demos/demos/audio/audioPlayer.js b/demos/demos/audio/audioPlayer.js index 7d9057e7..91028b1a 100644 --- a/demos/demos/audio/audioPlayer.js +++ b/demos/demos/audio/audioPlayer.js @@ -6,7 +6,8 @@ import { TimerMode, EntityCommands, Cleanup, - Audio + Audio, + Timer } from 'wima' import { addDefaultCamera3D } from '../utils.js' @@ -27,8 +28,11 @@ function init(world) { .insertPrefab([ new AudioPlayer({ audio: server.load(Audio, 'assets/audio/bad-apple.m4a'), - playbackMode: TimerMode.Repeat }), - new Cleanup()]) + new Timer({ + mode: TimerMode.Repeat + }), + new Cleanup() + ]) .build() } \ No newline at end of file From 9059d90ebe3ddb2a643bc5122316ff4f2d48485b Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:56:00 +0300 Subject: [PATCH 07/12] Add a new demo for audio playback --- demos/demos/audio/audioPlayback.js | 83 ++++++++++++++++++++++++++++++ demos/demos/audio/index.js | 1 + demos/main.js | 2 + 3 files changed, 86 insertions(+) create mode 100644 demos/demos/audio/audioPlayback.js diff --git a/demos/demos/audio/audioPlayback.js b/demos/demos/audio/audioPlayback.js new file mode 100644 index 00000000..3039ea12 --- /dev/null +++ b/demos/demos/audio/audioPlayback.js @@ -0,0 +1,83 @@ +import { + Demo, + World, + AudioAssets, + AudioPlayer, + TimerMode, + EntityCommands, + Cleanup, + Timer, + Query, + VirtualClock +} from 'wima' +import { addDefaultCamera3D } from '../utils.js' + +export default new Demo( + 'audio/audio playback', + [init, addDefaultCamera3D], + [update] +) + +/** + * @param {World} world + */ +function init(world) { + const audioSources = world.getResource(AudioAssets) + const commands = world.getResource(EntityCommands) + + world.setResource(new AudioTimer({ + mode: TimerMode.Repeat, + duration: 5 + })) + commands + .spawn() + .insertPrefab([ + new AudioPlayer({ + audio: audioSources.load('assets/audio/bad-apple.m4a') + }), + new Timer({ + mode: TimerMode.Once, + duration: 0 + }), + new Cleanup() + ]) + .build() +} + +/** + * @param {World} world + */ +function update(world) { + const audios = new Query(world, [AudioPlayer, Timer]) + const clock = world.getResource(VirtualClock) + const timer = world.getResource(AudioTimer) + audios.each(([player, playback]) => { + if (timer.cycleStarted()) { + const count = timer.cyclesCompleted() + if (count === 1) { + playback.pause() + } else if (count === 2) { + playback.play() + } else if (count === 3) { + playback.start() + } else if (count === 4) { + playback.stop() + } else if (count === 5) { + playback.speed = 1.2 + playback.play() + } else if (count === 6) { + playback.speed = 1.5 + playback.play() + } else if (count === 7) { + playback.speed = 1 + playback.seek(200) + } else if(count === 8) { + timer.reset() + } + } + }) + + timer.update(clock.getDelta()) +} + +class AudioTimer extends Timer {} \ No newline at end of file diff --git a/demos/demos/audio/index.js b/demos/demos/audio/index.js index bff6271d..d60b542a 100644 --- a/demos/demos/audio/index.js +++ b/demos/demos/audio/index.js @@ -1,3 +1,4 @@ export { default as audioGraph } from './audioGraph.js' export { default as audioPlayer } from './audioPlayer.js' +export { default as audioPlayback } from './audioPlayback.js' export { default as audioOscillator } from './audioOscillator.js' \ No newline at end of file diff --git a/demos/main.js b/demos/main.js index e2bbb9c8..f8cb8bef 100644 --- a/demos/main.js +++ b/demos/main.js @@ -21,6 +21,7 @@ import { despawn, audioGraph, audioPlayer, + audioPlayback, audioOscillator, keyboard, mouse, @@ -72,6 +73,7 @@ app touch, audioGraph, audioPlayer, + audioPlayback, audioOscillator, lineStyle2d, arcs2d, From b16a7fc4c3922ed91ef8aef0485c00cf1300df02 Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Sat, 20 Sep 2025 08:14:27 +0300 Subject: [PATCH 08/12] Remove `Timer.playback` --- src/audio/components/audioplayer.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/audio/components/audioplayer.js b/src/audio/components/audioplayer.js index bce4b348..178addb5 100644 --- a/src/audio/components/audioplayer.js +++ b/src/audio/components/audioplayer.js @@ -22,21 +22,12 @@ export class AudioPlayer { */ audio - /** - * @type {Timer} - */ - playback - /** * @param {AudioPlayerOptions} [options] */ constructor({ attach, audio, playbackMode = TimerMode.Once } = {}) { this.attach = attach this.audio = audio - this.playback = new Timer({ - duration:1000000, - mode: playbackMode - }) } } From 6313a29800ab71c057bed8617d54b9f64f141d8d Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Sat, 20 Sep 2025 08:27:13 +0300 Subject: [PATCH 09/12] Update after rebase --- demos/demos/audio/audioPlayback.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/demos/demos/audio/audioPlayback.js b/demos/demos/audio/audioPlayback.js index 3039ea12..b4902e85 100644 --- a/demos/demos/audio/audioPlayback.js +++ b/demos/demos/audio/audioPlayback.js @@ -1,14 +1,15 @@ import { Demo, World, - AudioAssets, + Audio, AudioPlayer, TimerMode, EntityCommands, Cleanup, Timer, Query, - VirtualClock + VirtualClock, + AssetServer } from 'wima' import { addDefaultCamera3D } from '../utils.js' @@ -22,7 +23,7 @@ export default new Demo( * @param {World} world */ function init(world) { - const audioSources = world.getResource(AudioAssets) + const server = world.getResource(AssetServer) const commands = world.getResource(EntityCommands) world.setResource(new AudioTimer({ @@ -33,7 +34,7 @@ function init(world) { .spawn() .insertPrefab([ new AudioPlayer({ - audio: audioSources.load('assets/audio/bad-apple.m4a') + audio: server.load(Audio, 'assets/audio/bad-apple.m4a') }), new Timer({ mode: TimerMode.Once, From 9035d4746dbed1ab48014a885caa2ddc8eebb3b3 Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Sat, 20 Sep 2025 08:33:41 +0300 Subject: [PATCH 10/12] Remove `AudioPlayerOptions.playbackMode` --- src/audio/components/audioplayer.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/audio/components/audioplayer.js b/src/audio/components/audioplayer.js index 178addb5..213a55a4 100644 --- a/src/audio/components/audioplayer.js +++ b/src/audio/components/audioplayer.js @@ -1,7 +1,6 @@ /** @import { NodeId } from '../../datastructures/index.js' */ /** @import { ComponentHook } from '../../ecs/index.js' */ import { Handle } from '../../asset/index.js' -import { Timer, TimerMode } from '../../time/index.js' import { Audio } from '../assets/index.js' import { AudioGraph } from '../resources/index.js' @@ -25,7 +24,7 @@ export class AudioPlayer { /** * @param {AudioPlayerOptions} [options] */ - constructor({ attach, audio, playbackMode = TimerMode.Once } = {}) { + constructor({ attach, audio } = {}) { this.attach = attach this.audio = audio } @@ -59,5 +58,4 @@ export function removeAudioPlayerSink(entity, world) { * @typedef AudioPlayerOptions * @property {NodeId} [attach] * @property {Handle