Skip to content

Commit

Permalink
First pass on music playback
Browse files Browse the repository at this point in the history
  • Loading branch information
hubol committed Jan 23, 2024
1 parent ac7bcee commit 484f342
Show file tree
Hide file tree
Showing 14 changed files with 122 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
*.FLAC filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.WAV filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
*.OGG filter=lfs diff=lfs merge=lfs -text

*.png filter=lfs diff=lfs merge=lfs -text
*.PNG filter=lfs diff=lfs merge=lfs -text
Expand Down
1 change: 1 addition & 0 deletions esbuild.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function options(overrides) {
sourcemap: true,
loader: {
'.fnt': 'file',
'.ogg': 'file',
'.png': 'file',
'.zip': 'file',
},
Expand Down
3 changes: 3 additions & 0 deletions raw/music/test1.flac
Git LFS file not shown
3 changes: 3 additions & 0 deletions raw/music/test2.flac
Git LFS file not shown
13 changes: 13 additions & 0 deletions smooch.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@
"out": "src/assets/generated/sounds/index.ts",
"program": "src/assets/template/sounds.js"
}
},
{
"glob": "raw/music/**/*.{wav,flac}",
"convert": [
{
"directory": "src/assets/generated/music/ogg",
"format": "ogg"
}
],
"template": {
"out": "src/assets/generated/music/index.ts",
"program": "src/assets/template/music.js"
}
}
],
"jsonFiles": []
Expand Down
7 changes: 7 additions & 0 deletions src/assets/generated/music/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

// This file is generated

export const GeneratedMusicData = {
"Test2": { ogg: require("./ogg/test2.ogg") },
"Test1": { ogg: require("./ogg/test1.ogg") }
}
3 changes: 3 additions & 0 deletions src/assets/generated/music/ogg/test1.ogg
Git LFS file not shown
3 changes: 3 additions & 0 deletions src/assets/generated/music/ogg/test2.ogg
Git LFS file not shown
8 changes: 8 additions & 0 deletions src/assets/music.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GeneratedMusicData } from "./generated/music";

type MusicId = keyof typeof GeneratedMusicData;

export const Mzk: Record<MusicId, string> = <any>{};

for (const key in GeneratedMusicData)
Mzk[key] = GeneratedMusicData[key].ogg;
14 changes: 14 additions & 0 deletions src/assets/template/music.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
@param {import("smooch/template-api").TemplateContext.AudioConvert} context;
@param {import("smooch/template-api").Utils} utils;
*/
module.exports = function ({ files }, { pascal, noext }) {
return `
// This file is generated
export const GeneratedMusicData = {
${files.map(file =>
` "${pascal(noext(file.path))}": { ogg: require("./ogg/${file.convertedPaths.ogg}") }`).join(`,
`)}
}`;
}
5 changes: 4 additions & 1 deletion src/igua/igua-audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Logging } from "../lib/logging";
import { intervalWait } from "../lib/browser/interval-wait";
import { AsshatAudioContext } from "../lib/game-engine/audio/asshat-audiocontext";
import { Sound } from "../lib/game-engine/audio/sound";
import { AsshatJukebox } from "../lib/game-engine/audio/asshat-jukebox";

class IguaAudioImpl {
constructor(private readonly _context: AudioContext) {
Expand All @@ -12,16 +13,18 @@ class IguaAudioImpl {
const audio = await this._context.decodeAudioData(buffer);
// TODO could be routed to special node for environmental effects
// e.g. reverb, delay
return new Sound(audio, this._context.destination, this._context);
return new Sound(audio, this._context.destination);
}
}

export let IguaAudio: IguaAudioImpl;
export let Jukebox: AsshatJukebox;

export const IguaAudioInitializer = {
async initialize() {
await intervalWait(() => !!AsshatAudioContext);
IguaAudio = new IguaAudioImpl(AsshatAudioContext);
Jukebox = new AsshatJukebox(AsshatAudioContext.destination);
},
get initialized() {
return !!IguaAudio;
Expand Down
4 changes: 4 additions & 0 deletions src/igua/scenes/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { createDebugPanel } from "../../lib/game-engine/debug/debug-panel";
import { approachLinear } from "../../lib/math/number";
import { SceneLocal } from "../core/scene-local";
import { show } from "../cutscene/show";
import { Jukebox } from "../igua-audio";
import { Mzk } from "../../assets/music";

const TailTextures = Tx.Iguana.Tail.split({ width: 28, trimFrame: true });

Expand Down Expand Up @@ -337,6 +339,8 @@ export function SceneTest() {
await show("Bye");
}).show();

Jukebox.play(Rng.bool() ? Mzk.Test1 : Mzk.Test2);

// document.body.appendChild(createDebugPanel(scene.stage));
}

Expand Down
51 changes: 51 additions & 0 deletions src/lib/game-engine/audio/asshat-jukebox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { intervalWait } from "../../browser/interval-wait";
import { Sound, SoundInstance } from "./sound";

type Url = string;
type MusicTrack = Url;

// TODO generics?
export class AsshatJukebox {
private readonly _loader: MusicTrackLoader;
private _current?: SoundInstance;

constructor(private readonly _destination: AudioNode) {
this._loader = new MusicTrackLoader(_destination);
}

play(track: MusicTrack) {
setTimeout(() => this.playAsync(track));
}

async playAsync(track: MusicTrack) {
const sound = await this._loader.load(track);
this._current?.stop();
this._current = sound.with.loop(true).playInstance();
}
}

class MusicTrackLoader {
private readonly _loaded: Record<Url, Sound> = {};
private readonly _loading = new Set<Url>();

constructor(private readonly _destination: AudioNode) {

}

async load(track: Url) {
if (this._loaded[track])
return this._loaded[track];

if (!this._loading.has(track)) {
this._loading.add(track);
const arrayBuffer = await fetch(track).then(x => x.arrayBuffer());
const audioBuffer = await this._destination.context.decodeAudioData(arrayBuffer);
this._loading.delete(track);
const sound = new Sound(audioBuffer, this._destination);
return this._loaded[track] = sound;
}

await intervalWait(() => !!this._loaded[track]);
return this._loaded[track];
}
}
9 changes: 6 additions & 3 deletions src/lib/game-engine/audio/sound.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
export class Sound {
private readonly _gainNode: GainNode;
private readonly _context: BaseAudioContext;

rate = 1;
loop = false;
with = new SoundWith(this);

constructor(private readonly _buffer: AudioBuffer, destination: AudioNode, private readonly _context: AudioContext) {
this._gainNode = new GainNode(_context);
constructor(private readonly _buffer: AudioBuffer, destination: AudioNode) {
this._context = destination.context;
this._gainNode = new GainNode(this._context);
this._gainNode.connect(destination);
}

Expand Down Expand Up @@ -40,7 +43,7 @@ export class Sound {

type RampableParam = 'rate' | 'gain';

class SoundInstance {
export class SoundInstance {
constructor(private readonly _sourceNode: AudioBufferSourceNode, private readonly _gainNode: GainNode) {

}
Expand Down

0 comments on commit 484f342

Please sign in to comment.