From 96df0d544e4a317efc70b13bc65aaad944df6a80 Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Fri, 12 Sep 2025 14:38:47 +0300 Subject: [PATCH 1/7] Add `AudioOscillator` --- src/audio/components/audiooscillator.js | 89 +++++++++++++++++++++++++ src/audio/components/index.js | 3 +- 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/audio/components/audiooscillator.js diff --git a/src/audio/components/audiooscillator.js b/src/audio/components/audiooscillator.js new file mode 100644 index 00000000..414093db --- /dev/null +++ b/src/audio/components/audiooscillator.js @@ -0,0 +1,89 @@ +/** @import { NodeId } from '../../datastructures/index.js' */ +/** @import { ComponentHook } from '../../ecs/index.js' */ +import { Timer } from '../../time/index.js' +import { AudioGraph } from '../resources/index.js' + +export class AudioOscillator { + + /** + * @type {NodeId | undefined} + */ + sourceNode + + /** + * @type {AudioOscillatorType} + */ + type + + /** + * @type {NodeId | undefined} + */ + attach + + /** + * @type {number} + */ + detune + + /** + * @type {number} + */ + frequency + + /** + * @type {Timer} + */ + playback = new Timer(1000000) + + /** + * @param {AudioOscillatorOptions} [options] + */ + constructor({ + attach, + type = AudioOscillatorType.Sine, + detune = 0, + frequency = 440 + } = {}) { + this.attach = attach + this.type = type + this.detune = detune + this.frequency = frequency + } +} + +/** + * @type {ComponentHook} + */ +export function removeOscillatorSink(entity, world) { + const graph = world.getResource(AudioGraph) + const audio = world.get(entity, AudioOscillator) + + // SAFETY: The node referenced by the player is guaranteed to be a `OscillatorNode`. + const node = /** @type {OscillatorNode | undefined} */ (graph.graph.getNode(audio.sourceNode)?.weight) + + if (node) { + node.stop() + + // TODO: Remove the audio sink from the graph when removing nodes on a graph + // is available. + } +} + +/** + * @readonly + * @enum {number} + */ +export const AudioOscillatorType = { + SawTooth: 0, + Sine: 1, + Triangle: 2, + Square: 3 +} + +/** + * @typedef AudioOscillatorOptions + * @property {NodeId} [attach] + * @property {AudioOscillatorType} [type] + * @property {number} [frequency] + * @property {number} [detune] + */ \ No newline at end of file diff --git a/src/audio/components/index.js b/src/audio/components/index.js index 711ef554..7a24542e 100644 --- a/src/audio/components/index.js +++ b/src/audio/components/index.js @@ -1 +1,2 @@ -export * from './audioplayer.js' \ No newline at end of file +export * from './audioplayer.js' +export * from './audiooscillator.js' \ No newline at end of file From b240b68275cdc2c6a49a9aa481b364a853813e26 Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Fri, 12 Sep 2025 14:39:56 +0300 Subject: [PATCH 2/7] Add `playOscillators` system --- src/audio/systems/index.js | 62 +++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/audio/systems/index.js b/src/audio/systems/index.js index b3e715af..7a4ccc88 100644 --- a/src/audio/systems/index.js +++ b/src/audio/systems/index.js @@ -1,7 +1,7 @@ import { Query, World } from '../../ecs/index.js' import { VirtualClock, TimerMode } from '../../time/index.js' import { AudioAssets, AudioGraph } from '../resources/index.js' -import { AudioPlayer } from '../components/index.js' +import { AudioPlayer, AudioOscillator, AudioOscillatorType } from '../components/index.js' /** @@ -48,6 +48,42 @@ export function playAudio(world) { }) } +/** + * @param {World} world + */ +export function playOscillators(world) { + const graph = world.getResource(AudioGraph) + const oscillators = new Query(world, [AudioOscillator]) + const ctx = graph.getContext() + const root = graph.getRoot() + + + oscillators.each(([oscillator]) => { + const { type, frequency, sourceNode, detune, playback, attach } = oscillator + + if (sourceNode) { + + // update node if a playback is requested + } else { + const node = new OscillatorNode(ctx, { + detune, + frequency, + type: mapType(type) + }) + const id = graph.add(node) + + if (attach) { + graph.connect(id, attach) + } else { + graph.connect(id, root) + } + + node.start(playback.elapsed) + oscillator.sourceNode = id + } + }) +} + /** * @param {World} world */ @@ -75,4 +111,28 @@ function looped(playbackMode) { default: return false } +} + +/** + * @param {AudioOscillatorType} type + * @throws {string} + * @returns {OscillatorType} + */ +function mapType(type) { + switch (type) { + case AudioOscillatorType.SawTooth: + return 'sawtooth' + + case AudioOscillatorType.Sine: + return 'sine' + + case AudioOscillatorType.Square: + return 'square' + + case AudioOscillatorType.Triangle: + return 'triangle' + + default: + throw 'No such `AudioOscillatorType` exists.' + } } \ No newline at end of file From ebd56c334543ba29be0c85e454d5c425184d6f21 Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Fri, 12 Sep 2025 14:40:15 +0300 Subject: [PATCH 3/7] Add `updateOscillators` system --- src/audio/systems/index.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/audio/systems/index.js b/src/audio/systems/index.js index 7a4ccc88..a2b16246 100644 --- a/src/audio/systems/index.js +++ b/src/audio/systems/index.js @@ -97,6 +97,19 @@ export function updatePlayers(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 39930a2ecd58e858521b8364dc8fe2b217ea797b Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Fri, 12 Sep 2025 14:40:45 +0300 Subject: [PATCH 4/7] Register `AudioOscillator` into `AudioPlugin` This adds the systems and component hooks required by the component in order for it to function. --- src/audio/plugin.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/audio/plugin.js b/src/audio/plugin.js index c2f739bd..cc4a9fe7 100644 --- a/src/audio/plugin.js +++ b/src/audio/plugin.js @@ -3,10 +3,10 @@ import { AssetParserPlugin, AssetPlugin, Assets } from '../asset/index.js' import { ComponentHooks } from '../ecs/index.js' import { typeidGeneric } from '../reflect/index.js' import { Audio } from './assets/index.js' -import { AudioPlayer, removeAudioPlayerSink } from './components/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 } from './systems/index.js' +import { playAudio, updatePlayers, playOscillators, updateOscillators } from './systems/index.js' export class AudioPlugin extends Plugin { @@ -23,6 +23,11 @@ export class AudioPlugin extends Plugin { null, removeAudioPlayerSink )) + .registerType(AudioOscillator) + .setComponentHooks(AudioOscillator, new ComponentHooks( + null, + removeOscillatorSink + )) .setResource(new AudioGraph()) .setResource(handler) .registerPlugin(new AssetPlugin({ @@ -38,7 +43,9 @@ export class AudioPlugin extends Plugin { parser: new AudioParser() })) .registerSystem(AppSchedule.Update, playAudio) + .registerSystem(AppSchedule.Update, playOscillators) .registerSystem(AppSchedule.Update, updatePlayers) + .registerSystem(AppSchedule.Update, updateOscillators) window.addEventListener('pointerdown', resumeAudio) From 39b9771b0fa822f7cd7384ce6a0338cbe0c5bbc8 Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Fri, 12 Sep 2025 14:42:23 +0300 Subject: [PATCH 5/7] Add audio oscillator demo --- demos/demos/audio/audioOscillator.js | 28 ++++++++++++++++++++++++++++ demos/demos/audio/index.js | 3 ++- demos/main.js | 2 ++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 demos/demos/audio/audioOscillator.js diff --git a/demos/demos/audio/audioOscillator.js b/demos/demos/audio/audioOscillator.js new file mode 100644 index 00000000..2f1245c8 --- /dev/null +++ b/demos/demos/audio/audioOscillator.js @@ -0,0 +1,28 @@ +import { + Demo, + World, + AudioOscillator, + EntityCommands, + Cleanup +} from 'wima' +import { addDefaultCamera3D } from '../utils.js' + +export default new Demo( + 'audio/audio oscillator', + [init, addDefaultCamera3D] +) + +/** + * @param {World} world + */ +function init(world) { + const commands = world.getResource(EntityCommands) + + commands + .spawn() + .insertPrefab([ + new AudioOscillator(), + new Cleanup() + ]) + .build() +} \ No newline at end of file diff --git a/demos/demos/audio/index.js b/demos/demos/audio/index.js index a32ad610..7c52d6ac 100644 --- a/demos/demos/audio/index.js +++ b/demos/demos/audio/index.js @@ -1,2 +1,3 @@ export { default as audioGraph } from './audioGraph.js' -export { default as audioPlayer } from './audioPlayer.js' \ No newline at end of file +export { default as audioPlayer } from './audioPlayer.js' +export { default as audioOscillator } from './audioOscillator.js' diff --git a/demos/main.js b/demos/main.js index e4bc16d7..e2bbb9c8 100644 --- a/demos/main.js +++ b/demos/main.js @@ -21,6 +21,7 @@ import { despawn, audioGraph, audioPlayer, + audioOscillator, keyboard, mouse, touch, @@ -71,6 +72,7 @@ app touch, audioGraph, audioPlayer, + audioOscillator, lineStyle2d, arcs2d, shapes2d, From 40a1733bcb96043c68e5cd99a08d55009308845c Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Sat, 13 Sep 2025 10:07:45 +0300 Subject: [PATCH 6/7] Update to recent changes --- src/audio/components/audiooscillator.js | 2 +- src/audio/systems/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio/components/audiooscillator.js b/src/audio/components/audiooscillator.js index 414093db..62d256f7 100644 --- a/src/audio/components/audiooscillator.js +++ b/src/audio/components/audiooscillator.js @@ -33,7 +33,7 @@ export class AudioOscillator { /** * @type {Timer} */ - playback = new Timer(1000000) + playback = new Timer({ duration:1000000 }) /** * @param {AudioOscillatorOptions} [options] diff --git a/src/audio/systems/index.js b/src/audio/systems/index.js index a2b16246..37d74372 100644 --- a/src/audio/systems/index.js +++ b/src/audio/systems/index.js @@ -78,7 +78,7 @@ export function playOscillators(world) { graph.connect(id, root) } - node.start(playback.elapsed) + node.start(playback.elapsed()) oscillator.sourceNode = id } }) From 1c5125437ef5c8825015471fb3522e40959a9248 Mon Sep 17 00:00:00 2001 From: waynemwashuma <94756970+waynemwashuma@users.noreply.github.com> Date: Sat, 13 Sep 2025 10:08:07 +0300 Subject: [PATCH 7/7] Lint files --- demos/demos/audio/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/demos/audio/index.js b/demos/demos/audio/index.js index 7c52d6ac..bff6271d 100644 --- a/demos/demos/audio/index.js +++ b/demos/demos/audio/index.js @@ -1,3 +1,3 @@ export { default as audioGraph } from './audioGraph.js' export { default as audioPlayer } from './audioPlayer.js' -export { default as audioOscillator } from './audioOscillator.js' +export { default as audioOscillator } from './audioOscillator.js' \ No newline at end of file