Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions demos/demos/audio/audioOscillator.js
Original file line number Diff line number Diff line change
@@ -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()
}
3 changes: 2 additions & 1 deletion demos/demos/audio/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as audioGraph } from './audioGraph.js'
export { default as audioPlayer } from './audioPlayer.js'
export { default as audioPlayer } from './audioPlayer.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,
audioOscillator,
keyboard,
mouse,
touch,
Expand Down Expand Up @@ -71,6 +72,7 @@ app
touch,
audioGraph,
audioPlayer,
audioOscillator,
lineStyle2d,
arcs2d,
shapes2d,
Expand Down
89 changes: 89 additions & 0 deletions src/audio/components/audiooscillator.js
Original file line number Diff line number Diff line change
@@ -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({ duration: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]
*/
3 changes: 2 additions & 1 deletion src/audio/components/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './audioplayer.js'
export * from './audioplayer.js'
export * from './audiooscillator.js'
11 changes: 9 additions & 2 deletions src/audio/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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({
Expand All @@ -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)

Expand Down
75 changes: 74 additions & 1 deletion src/audio/systems/index.js
Original file line number Diff line number Diff line change
@@ -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'


/**
Expand Down Expand Up @@ -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
*/
Expand All @@ -61,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
*/
Expand All @@ -75,4 +124,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.'
}
}