Skip to content
7 changes: 6 additions & 1 deletion demos/demos/audio/audioOscillator.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {
World,
AudioOscillator,
EntityCommands,
Cleanup
Cleanup,
Timer,
TimerMode
} from 'wima'
import { addDefaultCamera3D } from '../utils.js'

Expand All @@ -22,6 +24,9 @@ function init(world) {
.spawn()
.insertPrefab([
new AudioOscillator(),
new Timer({
mode: TimerMode.Repeat
}),
new Cleanup()
])
.build()
Expand Down
86 changes: 86 additions & 0 deletions demos/demos/audio/audioPlayback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
Demo,
World,
Audio,
AudioPlayer,
TimerMode,
EntityCommands,
Cleanup,
Timer,
Query,
VirtualClock,
AssetServer
} from 'wima'
import { addDefaultCamera3D } from '../utils.js'

export default new Demo(
'audio/audio playback',
[init, addDefaultCamera3D],
[update]
)

/**
* @param {World} world
*/
function init(world) {
const server = world.getResource(AssetServer)
const commands = world.getResource(EntityCommands)

world.setResource(new AudioTimer({
mode: TimerMode.Repeat,
duration: 5
}))
commands
.spawn()
.insertPrefab([
new AudioPlayer({
audio: server.load(Audio, '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 {}
12 changes: 8 additions & 4 deletions demos/demos/audio/audioPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
TimerMode,
EntityCommands,
Cleanup,
Audio
Audio,
Timer
} from 'wima'
import { addDefaultCamera3D } from '../utils.js'

Expand All @@ -26,9 +27,12 @@ function init(world) {
.spawn()
.insertPrefab([
new AudioPlayer({
audio: server.load(Audio, 'assets/audio/bad-apple.m4a'),
playbackMode: TimerMode.Repeat
audio: server.load(Audio, 'assets/audio/bad-apple.m4a')
}),
new Cleanup()])
new Timer({
mode: TimerMode.Repeat
}),
new Cleanup()
])
.build()
}
1 change: 1 addition & 0 deletions demos/demos/audio/index.js
Original file line number Diff line number Diff line change
@@ -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'
2 changes: 2 additions & 0 deletions demos/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
despawn,
audioGraph,
audioPlayer,
audioPlayback,
audioOscillator,
keyboard,
mouse,
Expand Down Expand Up @@ -72,6 +73,7 @@ app
touch,
audioGraph,
audioPlayer,
audioPlayback,
audioOscillator,
lineStyle2d,
arcs2d,
Expand Down
6 changes: 0 additions & 6 deletions src/audio/components/audiooscillator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/** @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 {
Expand Down Expand Up @@ -30,11 +29,6 @@ export class AudioOscillator {
*/
frequency

/**
* @type {Timer}
*/
playback = new Timer({ duration:1000000 })

/**
* @param {AudioOscillatorOptions} [options]
*/
Expand Down
13 changes: 1 addition & 12 deletions src/audio/components/audioplayer.js
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -22,21 +21,12 @@ export class AudioPlayer {
*/
audio

/**
* @type {Timer}
*/
playback

/**
* @param {AudioPlayerOptions} [options]
*/
constructor({ attach, audio, playbackMode = TimerMode.Once } = {}) {
constructor({ attach, audio } = {}) {
this.attach = attach
this.audio = audio
this.playback = new Timer({
duration:1000000,
mode: playbackMode
})
}
}

Expand Down Expand Up @@ -68,5 +58,4 @@ export function removeAudioPlayerSink(entity, world) {
* @typedef AudioPlayerOptions
* @property {NodeId} [attach]
* @property {Handle<Audio>} [audio]
* @property {TimerMode} [playbackMode]
*/
4 changes: 1 addition & 3 deletions src/audio/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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)

Expand Down
110 changes: 60 additions & 50 deletions src/audio/systems/index.js
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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

Expand All @@ -26,23 +25,43 @@ 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)
} else {
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
}
})
Expand All @@ -53,60 +72,51 @@ 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())
oscillator.sourceNode = id
}
})
}

/**
* @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)
})
}
const elapsed = playback.elapsed()

/**
* @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)
playback.reset()
playback.seek(elapsed)
oscillator.sourceNode = id
}
})
}

Expand Down