Просмотр исходного кода

Switch to premade SoundSets

master
Fen Dweller 4 лет назад
Родитель
Сommit
4172de5a07
9 измененных файлов: 117 добавлений и 175 удалений
  1. +35
    -19
      src/audio.ts
  2. +6
    -14
      src/components/Menu.vue
  3. +19
    -13
      src/components/SoundscapeComp.vue
  4. +13
    -1
      src/components/nodes/SourceNode.vue
  5. +15
    -0
      src/data/sound-sets.ts
  6. +4
    -2
      src/sources/IntervalSource.ts
  7. +3
    -3
      src/sources/LoopingSource.ts
  8. +0
    -114
      src/sources/PremadeSources.ts
  9. +22
    -9
      src/sources/Source.ts

+ 35
- 19
src/audio.ts Просмотреть файл

@@ -1,6 +1,6 @@
import "reflect-metadata"; import "reflect-metadata";
import { Filter } from "./filters/Filter"; import { Filter } from "./filters/Filter";
import { Source } from "./sources/Source";
import { SoundSet, Source } from "./sources/Source";


let ogg_support = false; let ogg_support = false;


@@ -18,6 +18,19 @@ export class Soundscape {
source.start(); source.start();
} }


removeSource(source: Source): void {
if (this.sources.includes(source)) {
source.output.disconnect();
this.sources = this.sources.filter((x) => x !== source);
} else {
console.warn(
"Tried to remove a source from a Soundscape that wasn't using it"
);
console.warn(this);
console.warn(source);
}
}

addFilter(filter: Filter): void { addFilter(filter: Filter): void {
if (this.filters.length > 0) { if (this.filters.length > 0) {
const last: Filter = this.filters[this.filters.length - 1]; const last: Filter = this.filters[this.filters.length - 1];
@@ -90,7 +103,7 @@ export let context: AudioContext;


const audioBaseUrl = "/audio/"; const audioBaseUrl = "/audio/";


const waiting: Map<string, Array<Source>> = new Map();
const waiting: Map<string, Array<SoundSet>> = new Map();
const audioDict: Map<string, AudioBuffer> = new Map(); const audioDict: Map<string, AudioBuffer> = new Map();


// decide if we can load oggs // decide if we can load oggs
@@ -100,28 +113,31 @@ export function audioTest(): void {
} }
// asynchronously load an audio file // asynchronously load an audio file


export function loadAudio(name: string, source: Source, flush = false): void {
export function loadAudio(name: string, client: SoundSet, flush = false): void {
// pick a format // pick a format


name += ogg_support ? ".ogg" : ".mp3"; name += ogg_support ? ".ogg" : ".mp3";
// are we already trying to get the audio?

if (!waiting.has(name)) {
waiting.set(name, []);
}

const list: Array<Source> | undefined = waiting.get(name);

if (list !== undefined) list.push(source);


// do we already have the audio? // do we already have the audio?


if (audioDict.has(name) && !flush) { if (audioDict.has(name) && !flush) {
const buf: AudioBuffer | undefined = audioDict.get(name); const buf: AudioBuffer | undefined = audioDict.get(name);


if (buf !== undefined) source.addLoadedSound(buf);
if (buf !== undefined) client.loadSound(name, buf);

return;
}

// are we already trying to get the audio?

if (!waiting.has(name)) {
waiting.set(name, []);
} }


const list: Array<SoundSet> | undefined = waiting.get(name);

if (list !== undefined) list.push(client);

// is the audio already stored locally? // is the audio already stored locally?


if (!flush) { if (!flush) {
@@ -139,14 +155,14 @@ function cacheAndParse(name: string, data: ArrayBuffer) {
function parseAudioData(name: string, data: ArrayBuffer) { function parseAudioData(name: string, data: ArrayBuffer) {
context.decodeAudioData( context.decodeAudioData(
data, data,
function (buffer) {
audioDict.set(name, buffer);
function (buf) {
audioDict.set(name, buf);


const waitingSources: Array<Source> | undefined = waiting.get(name);
const waitingClients: Array<SoundSet> | undefined = waiting.get(name);


if (waitingSources !== undefined) {
waitingSources.forEach((source) => {
source.addLoadedSound(buffer);
if (waitingClients !== undefined) {
waitingClients.forEach((client) => {
client.loadSound(name, buf);
}); });
} }
}, },


+ 6
- 14
src/components/Menu.vue Просмотреть файл

@@ -1,11 +1,11 @@
<template> <template>
<div id="menu"> <div id="menu">
<div class="list-label">Sources</div>
<div class="list-label">Sounds</div>
<div class="list"> <div class="list">
<draggable <draggable
v-for="(source, index) in sourceTypes"
v-for="(source, index) in soundSets"
:key="index" :key="index"
:label="source"
:label="source.name"
/> />
</div> </div>
</div> </div>
@@ -14,6 +14,8 @@
<script lang="ts"> <script lang="ts">
import { Options, Vue } from "vue-class-component"; import { Options, Vue } from "vue-class-component";
import Draggable from "@/components/Draggable.vue"; import Draggable from "@/components/Draggable.vue";
import * as SoundSets from "@/data/sound-sets";
import { SoundSet } from "@/sources/Source";


@Options({ @Options({
components: { components: {
@@ -21,17 +23,7 @@ import Draggable from "@/components/Draggable.vue";
}, },
}) })
export default class Menu extends Vue { export default class Menu extends Vue {
sourceTypes = [
"Rumble",
"Glorps",
"Heartbeat",
"Breathing",
"Squishing",
"Burps",
"Gurgles",
];

foo = 3;
soundSets: Array<SoundSet> = Array.from(Object.values(SoundSets));
} }
</script> </script>




+ 19
- 13
src/components/SoundscapeComp.vue Просмотреть файл

@@ -4,6 +4,7 @@
v-for="(source, index) in soundscape.sources" v-for="(source, index) in soundscape.sources"
:key="index" :key="index"
:source="source" :source="source"
v-on:delete="deleteSource(source)"
> >
</source-node> </source-node>
<source-node <source-node
@@ -26,8 +27,10 @@ import { Soundscape } from "@/audio";
import { Options, Vue } from "vue-class-component"; import { Options, Vue } from "vue-class-component";
import SourceNode from "./nodes/SourceNode.vue"; import SourceNode from "./nodes/SourceNode.vue";
import FilterNode from "./nodes/FilterNode.vue"; import FilterNode from "./nodes/FilterNode.vue";
import * as Sources from "@/sources/PremadeSources";
import { Source } from "@/sources/Source";
import * as SoundSets from "@/data/sound-sets";
import { SoundSet, Source } from "@/sources/Source";
import { IntervalSource } from "@/sources/IntervalSource";
import { LoopingSource } from "@/sources/LoopingSource";


@Options({ @Options({
props: { props: {
@@ -42,16 +45,11 @@ export default class SoundscapeComp extends Vue {
soundscape!: Soundscape; soundscape!: Soundscape;
started = false; started = false;
context!: AudioContext; context!: AudioContext;

makers: Record<string, () => Source> = {
Gurgles: Sources.makeGurgles,
Burps: Sources.makeBurps,
Glorps: Sources.makeGlorps,
Squishing: Sources.makeSquishing,
Heartbeat: Sources.makeHeartbeat,
Breathing: Sources.makeBreathing,
Rumble: Sources.makeRumble,
sources: { [key: string]: new (name: string) => Source } = {
IntervalSource: IntervalSource,
LoopingSource: LoopingSource,
}; };
soundSets: { [key: string]: SoundSet } = SoundSets;


drag(ev: DragEvent): void { drag(ev: DragEvent): void {
ev.preventDefault(); ev.preventDefault();
@@ -63,11 +61,19 @@ export default class SoundscapeComp extends Vue {
if (event.dataTransfer) { if (event.dataTransfer) {
const label = event.dataTransfer.getData("text/plain"); const label = event.dataTransfer.getData("text/plain");


console.log(this.makers[label])
this.soundscape.addSource(this.makers[label]());
const soundSet = this.soundSets[label];

const source = new this.sources[soundSet.defaultSource](soundSet.name);
source.soundSet = soundSet;
this.soundscape.addSource(source);
// TODO
} }
} }


deleteSource(source: Source): void {
this.soundscape.removeSource(source);
}

mounted(): void { mounted(): void {
this.soundscape.start(); this.soundscape.start();
} }


+ 13
- 1
src/components/nodes/SourceNode.vue Просмотреть файл

@@ -5,7 +5,8 @@
:class="source.active ? '' : 'inactive'" :class="source.active ? '' : 'inactive'"
class="source-node" class="source-node"
> >
<Toggle class="active-toggle" v-model="source.active" />
<button class="delete-button" v-on:click="$emit('delete')">X</button>
<toggle class="active-toggle" v-model="source.active" />
<div class="node-name">{{ source.name }}</div> <div class="node-name">{{ source.name }}</div>
<node-props :node="source"></node-props> <node-props :node="source"></node-props>
</div> </div>
@@ -28,9 +29,11 @@ import Toggle from "@vueform/toggle";
NodeProps, NodeProps,
Toggle, Toggle,
}, },
emits: ["delete"],
}) })
export default class SourceNode extends Vue { export default class SourceNode extends Vue {
source!: Source; source!: Source;
dummy = false;
} }
</script> </script>


@@ -69,4 +72,13 @@ export default class SourceNode extends Vue {
.dummy { .dummy {
min-height: 200px; min-height: 200px;
} }

.delete-button {
position: absolute;
top: 5px;
right: 5px;
width: 25px;
height: 25px;
font-size: 24px;
}
</style> </style>

+ 15
- 0
src/data/sound-sets.ts Просмотреть файл

@@ -0,0 +1,15 @@
import { SoundSet } from "@/sources/Source";

export const Gurgles: SoundSet = new SoundSet(
"Gurgles",
Array(21)
.fill(0)
.map((x, i) => "gurgles/gurgle (" + (i + 1) + ")"),
"IntervalSource"
);

export const Squishing: SoundSet = new SoundSet(
"Squishing",
["squishing"],
"LoopingSource"
);

+ 4
- 2
src/sources/IntervalSource.ts Просмотреть файл

@@ -71,11 +71,13 @@ export class IntervalSource extends Source {
if (this.started) { if (this.started) {
this.remaining -= dt; this.remaining -= dt;
if (this.remaining <= 0) { if (this.remaining <= 0) {
const index = Math.floor(Math.random() * this.sounds.length);
const index = Math.floor(
Math.random() * this.soundSet.soundList.length
);


const node = context.createBufferSource(); const node = context.createBufferSource();


node.buffer = this.sounds[index];
node.buffer = this.soundSet.soundList[index];


const pan = context.createStereoPanner(); const pan = context.createStereoPanner();
pan.pan.value = pan.pan.value =


+ 3
- 3
src/sources/LoopingSource.ts Просмотреть файл

@@ -26,9 +26,9 @@ export class LoopingSource extends Source {
} }


private pickRandom(): void { private pickRandom(): void {
const index = Math.floor(Math.random() * this.sounds.length);
const index = Math.floor(Math.random() * this.soundSet.soundList.length);
this.source = context.createBufferSource(); this.source = context.createBufferSource();
this.source.buffer = this.sounds[index];
this.source.buffer = this.soundSet.soundList[index];
this.source.connect(this.gain); this.source.connect(this.gain);
this.source.onended = () => { this.source.onended = () => {
this.pickRandom(); this.pickRandom();
@@ -37,7 +37,7 @@ export class LoopingSource extends Source {
} }
public tick(dt: number): void { public tick(dt: number): void {
super.tick(dt); super.tick(dt);
if (this.started && this.sounds.length > 0 && !this.running) {
if (this.started && this.soundSet.soundList.length > 0 && !this.running) {
this.pickRandom(); this.pickRandom();
this.source.start(); this.source.start();
this.running = true; this.running = true;


+ 0
- 114
src/sources/PremadeSources.ts Просмотреть файл

@@ -1,114 +0,0 @@
import { IntervalSource } from "./IntervalSource";
import { LoopingSource } from "./LoopingSource";
import { Source } from "./Source";

export function makeGlorps(): Source {
const source: IntervalSource = new IntervalSource("Guts");
source.loadSound("bowels-to-intestines");
source.loadSound("intestines-to-bowels");
source.loadSound("intestines-to-stomach");
source.loadSound("intestines-to-stomach-forced");
source.loadSound("stomach-to-intestines");
source.loadSound("stomach-to-intestines-fail");
source.loadSound("stomach-churn");
source.loadSound("bowels-churn-safe");
source.loadSound("bowels-churn-danger");

console.log(source);

source.interval = [4, 8];
source.pitch = [0.75, 1.25];

return source;
}

export function makeBurps(): Source {
const source: IntervalSource = new IntervalSource("Burps");
source.loadSound("belch (1)");
source.loadSound("belch (2)");
source.loadSound("belch (3)");
source.loadSound("belch (4)");
source.loadSound("belch (5)");
source.loadSound("belch (6)");
source.loadSound("belch (7)");
source.loadSound("belch (8)");
source.loadSound("belch (9)");
source.loadSound("belch (10)");
source.loadSound("belch (11)");
source.loadSound("belch (12)");
source.loadSound("belch (13)");
source.loadSound("belch (14)");
source.loadSound("belch (15)");
source.loadSound("belch (16)");

source.interval = [10, 30];

source.pitch = [0.8, 1.1];

source.active = false;

return source;
}

export function makeGurgles(): Source {
const source: IntervalSource = new IntervalSource("Gurgles");
source.loadSound("gurgles/gurgle (1)");
source.loadSound("gurgles/gurgle (2)");
source.loadSound("gurgles/gurgle (3)");
source.loadSound("gurgles/gurgle (4)");
source.loadSound("gurgles/gurgle (5)");
source.loadSound("gurgles/gurgle (6)");
source.loadSound("gurgles/gurgle (7)");
source.loadSound("gurgles/gurgle (8)");
source.loadSound("gurgles/gurgle (9)");
source.loadSound("gurgles/gurgle (10)");
source.loadSound("gurgles/gurgle (11)");
source.loadSound("gurgles/gurgle (12)");
source.loadSound("gurgles/gurgle (13)");
source.loadSound("gurgles/gurgle (14)");
source.loadSound("gurgles/gurgle (15)");
source.loadSound("gurgles/gurgle (16)");
source.loadSound("gurgles/gurgle (17)");
source.loadSound("gurgles/gurgle (18)");
source.loadSound("gurgles/gurgle (19)");
source.loadSound("gurgles/gurgle (20)");
source.loadSound("gurgles/gurgle (21)");

source.pitch = [0.6, 1.2];
source.interval = [2, 10];
source.panning = [-0.6, 0.6];
return source;
}

export function makeHeartbeat(): LoopingSource {
const source: LoopingSource = new LoopingSource("Heartbeat");

source.loadSound("heartbeat");
source.volume = 0.3;

return source;
}

export function makeBreathing(): LoopingSource {
const source: LoopingSource = new LoopingSource("Breathing");

source.loadSound("breaths");

return source;
}

export function makeRumble(): LoopingSource {
const source: LoopingSource = new LoopingSource("Rumble");

source.loadSound("rumble");

return source;
}

export function makeSquishing(): LoopingSource {
const source: LoopingSource = new LoopingSource("Squishing");

source.loadSound("squishing");

return source;
}

+ 22
- 9
src/sources/Source.ts Просмотреть файл

@@ -1,8 +1,28 @@
import { Node, context, exposedNumber, loadAudio } from "../audio"; import { Node, context, exposedNumber, loadAudio } from "../audio";


export class SoundSet {
soundMap: Map<string, AudioBuffer> = new Map();
soundList: Array<AudioBuffer> = [];

constructor(
public name: string,
public soundKeys: Array<string>,
public defaultSource: string
) {
this.soundKeys.forEach((sound) => {
loadAudio(sound, this);
});
}

public loadSound(name: string, buf: AudioBuffer): void {
this.soundList.push(buf);
this.soundMap.set(name, buf);
}
}

export abstract class Source extends Node { export abstract class Source extends Node {
public abstract kind: string; public abstract kind: string;
public sounds: Array<AudioBuffer> = [];
public soundSet: SoundSet = new SoundSet("Empty", [], "IntervalSource");
public gain: GainNode; public gain: GainNode;
public output: GainNode; public output: GainNode;
public _active = true; public _active = true;
@@ -35,16 +55,9 @@ export abstract class Source extends Node {
this.gain.connect(this.output); this.gain.connect(this.output);
} }


public loadSound(name: string): void {
loadAudio(name, this);
}

public addLoadedSound(sound: AudioBuffer): void {
this.sounds.push(sound);
}

public abstract start(): void; public abstract start(): void;


// eslint-disable-next-line @typescript-eslint/no-unused-vars
public tick(dt: number): void { public tick(dt: number): void {
this.gain.gain.value = this.volume; this.gain.gain.value = this.volume;
} }


Загрузка…
Отмена
Сохранить