| @@ -61,7 +61,9 @@ export class Soundscape { | |||||
| this.filterBus.connect(this.output); | this.filterBus.connect(this.output); | ||||
| } | } | ||||
| } | } | ||||
| export abstract class Node { | export abstract class Node { | ||||
| abstract kind: string; | |||||
| constructor(public name: string) {} | constructor(public name: string) {} | ||||
| } | } | ||||
| @@ -85,6 +87,8 @@ export type RangeMetadata = PropMetadata & { | |||||
| unmap?: (value: number) => number; | unmap?: (value: number) => number; | ||||
| }; | }; | ||||
| export type SoundSetMetadata = PropMetadata; | |||||
| export const exposedMetadataNumber = Symbol("exposedNumber"); | export const exposedMetadataNumber = Symbol("exposedNumber"); | ||||
| // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types | ||||
| @@ -99,6 +103,13 @@ export function exposedRange(options: RangeMetadata) { | |||||
| return Reflect.metadata(exposedRangeMetadata, options); | return Reflect.metadata(exposedRangeMetadata, options); | ||||
| } | } | ||||
| export const exposedSoundSetMetadata = Symbol("exposedSoundSet"); | |||||
| // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types | |||||
| export function exposedSoundSet(options: SoundSetMetadata) { | |||||
| return Reflect.metadata(exposedSoundSetMetadata, options); | |||||
| } | |||||
| export let context: AudioContext; | export let context: AudioContext; | ||||
| const audioBaseUrl = "/audio/"; | const audioBaseUrl = "/audio/"; | ||||
| @@ -255,19 +266,22 @@ export function clearCache(): void { | |||||
| // if the indexedDB table doesn't exist at all, make it | // if the indexedDB table doesn't exist at all, make it | ||||
| function createCache(): void { | function createCache(): void { | ||||
| const idb = window.indexedDB; | const idb = window.indexedDB; | ||||
| const req = idb.open("cache", 1); | const req = idb.open("cache", 1); | ||||
| console.log("Create cache"); | |||||
| req.onupgradeneeded = (event) => { | req.onupgradeneeded = (event) => { | ||||
| const db = req.result; | const db = req.result; | ||||
| if (event.oldVersion > 0 && event.oldVersion < 3) { | |||||
| console.log("Version change"); | |||||
| if (event.oldVersion > 0) { | |||||
| db.deleteObjectStore("audio"); | db.deleteObjectStore("audio"); | ||||
| } | } | ||||
| db.createObjectStore("audio", { keyPath: ["name"] }); | db.createObjectStore("audio", { keyPath: ["name"] }); | ||||
| }; | }; | ||||
| console.log(req); | |||||
| req.onerror = () => { | req.onerror = () => { | ||||
| alert("Couldn't open the database?"); | alert("Couldn't open the database?"); | ||||
| }; | }; | ||||
| @@ -1,6 +1,6 @@ | |||||
| <template> | <template> | ||||
| <div class="draggable" draggable="true" v-on:dragstart="dragstart"> | <div class="draggable" draggable="true" v-on:dragstart="dragstart"> | ||||
| <div class="label">{{ label }}</div> | |||||
| <div class="label">{{ node.name }}</div> | |||||
| </div> | </div> | ||||
| </template> | </template> | ||||
| @@ -9,15 +9,14 @@ import { Options, Vue } from "vue-class-component"; | |||||
| @Options({ | @Options({ | ||||
| props: { | props: { | ||||
| label: String, | |||||
| node: { name: String, kind: String }, | |||||
| }, | }, | ||||
| }) | }) | ||||
| export default class Draggable extends Vue { | export default class Draggable extends Vue { | ||||
| label!: string; | |||||
| node!: { name: string; kind: string }; | |||||
| dragstart(event: DragEvent): void { | dragstart(event: DragEvent): void { | ||||
| console.log(event?.dataTransfer); | |||||
| event?.dataTransfer?.setData("text/plain", this.label); | |||||
| event?.dataTransfer?.setData(this.node.kind, JSON.stringify(this.node)); | |||||
| } | } | ||||
| } | } | ||||
| </script> | </script> | ||||
| @@ -3,9 +3,17 @@ | |||||
| <div class="list-label">Sounds</div> | <div class="list-label">Sounds</div> | ||||
| <div class="list"> | <div class="list"> | ||||
| <draggable | <draggable | ||||
| v-for="(source, index) in soundSets" | |||||
| v-for="(source, index) in presetSources" | |||||
| :key="index" | :key="index" | ||||
| :label="source.name" | |||||
| :node="source" | |||||
| /> | |||||
| </div> | |||||
| <div class="list-label">Filters</div> | |||||
| <div class="list"> | |||||
| <draggable | |||||
| v-for="(source, index) in presetFilters" | |||||
| :key="index" | |||||
| :node="source" | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -14,8 +22,7 @@ | |||||
| <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"; | |||||
| import { PresetSources, PresetFilters } from "@/data/presets"; | |||||
| @Options({ | @Options({ | ||||
| components: { | components: { | ||||
| @@ -23,7 +30,8 @@ import { SoundSet } from "@/sources/Source"; | |||||
| }, | }, | ||||
| }) | }) | ||||
| export default class Menu extends Vue { | export default class Menu extends Vue { | ||||
| soundSets: Array<SoundSet> = Array.from(Object.values(SoundSets)); | |||||
| presetSources: Array<{ name: string; kind: "Source" }> = PresetSources; | |||||
| presetFilters: Array<{ name: string }> = PresetFilters; | |||||
| } | } | ||||
| </script> | </script> | ||||
| @@ -8,8 +8,8 @@ | |||||
| > | > | ||||
| </source-node> | </source-node> | ||||
| <source-node | <source-node | ||||
| v-on:drop="drop" | |||||
| v-on:dragover="drag" | |||||
| v-on:drop="dropSource" | |||||
| v-on:dragover="dragSource" | |||||
| :dummy="true" | :dummy="true" | ||||
| ></source-node> | ></source-node> | ||||
| <filter-node | <filter-node | ||||
| @@ -18,6 +18,11 @@ | |||||
| :filter="filter" | :filter="filter" | ||||
| > | > | ||||
| </filter-node> | </filter-node> | ||||
| <filter-node | |||||
| v-on:drop="dropFilter" | |||||
| v-on:dragover="dragFilter" | |||||
| :dummy="true" | |||||
| ></filter-node> | |||||
| </div> | </div> | ||||
| <div></div> | <div></div> | ||||
| </template> | </template> | ||||
| @@ -27,10 +32,9 @@ 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 SoundSets from "@/data/sound-sets"; | |||||
| import { SoundSet, Source } from "@/sources/Source"; | |||||
| import { IntervalSource } from "@/sources/IntervalSource"; | |||||
| import { LoopingSource } from "@/sources/LoopingSource"; | |||||
| import { Source } from "@/sources/Source"; | |||||
| import { deserializeNode } from "@/serialize"; | |||||
| import { Filter } from "@/filters/Filter"; | |||||
| @Options({ | @Options({ | ||||
| props: { | props: { | ||||
| @@ -45,28 +49,38 @@ export default class SoundscapeComp extends Vue { | |||||
| soundscape!: Soundscape; | soundscape!: Soundscape; | ||||
| started = false; | started = false; | ||||
| context!: AudioContext; | context!: AudioContext; | ||||
| sources: { [key: string]: new (name: string) => Source } = { | |||||
| IntervalSource: IntervalSource, | |||||
| LoopingSource: LoopingSource, | |||||
| }; | |||||
| soundSets: { [key: string]: SoundSet } = SoundSets; | |||||
| drag(ev: DragEvent): void { | |||||
| ev.preventDefault(); | |||||
| dragSource(event: DragEvent): void { | |||||
| if (event.dataTransfer) { | |||||
| if (event.dataTransfer.types.includes("source")) event.preventDefault(); | |||||
| } | |||||
| } | } | ||||
| drop(event: DragEvent): void { | |||||
| dropSource(event: DragEvent): void { | |||||
| event.preventDefault(); | event.preventDefault(); | ||||
| if (event.dataTransfer) { | if (event.dataTransfer) { | ||||
| const label = event.dataTransfer.getData("text/plain"); | |||||
| const data = event.dataTransfer.getData("source"); | |||||
| const node = deserializeNode(JSON.parse(data)); | |||||
| const soundSet = this.soundSets[label]; | |||||
| this.soundscape.addSource(node as Source); | |||||
| } | |||||
| } | |||||
| const source = new this.sources[soundSet.defaultSource](soundSet.name); | |||||
| source.soundSet = soundSet; | |||||
| this.soundscape.addSource(source); | |||||
| // TODO | |||||
| dragFilter(event: DragEvent): void { | |||||
| if (event.dataTransfer) { | |||||
| if (event.dataTransfer.types.includes("filter")) event.preventDefault(); | |||||
| } | |||||
| } | |||||
| dropFilter(event: DragEvent): void { | |||||
| event.preventDefault(); | |||||
| if (event.dataTransfer) { | |||||
| const data = event.dataTransfer.getData("filter"); | |||||
| const node = deserializeNode(JSON.parse(data)); | |||||
| this.soundscape.addFilter(node as Filter); | |||||
| } | } | ||||
| } | } | ||||
| @@ -76,6 +90,8 @@ export default class SoundscapeComp extends Vue { | |||||
| mounted(): void { | mounted(): void { | ||||
| this.soundscape.start(); | this.soundscape.start(); | ||||
| console.log(this.soundscape); | |||||
| } | } | ||||
| } | } | ||||
| </script> | </script> | ||||
| @@ -1,8 +1,15 @@ | |||||
| <template> | <template> | ||||
| <div :class="filter.active ? '' : 'inactive'" class="filter-node"> | |||||
| <Toggle class="active-toggle" v-model="filter.active" /> | |||||
| <div class="node-name">{{ filter.name }}</div> | |||||
| <node-props :node="filter"></node-props> | |||||
| <div> | |||||
| <div | |||||
| v-if="!dummy" | |||||
| :class="filter.active ? '' : 'inactive'" | |||||
| class="filter-node" | |||||
| > | |||||
| <Toggle class="active-toggle" v-model="filter.active" /> | |||||
| <div class="node-name">{{ filter.name }}</div> | |||||
| <node-props :node="filter"></node-props> | |||||
| </div> | |||||
| <div v-if="dummy" class="filter-node dummy">Drop filters here!</div> | |||||
| </div> | </div> | ||||
| </template> | </template> | ||||
| @@ -16,6 +23,7 @@ import Toggle from "@vueform/toggle"; | |||||
| @Options({ | @Options({ | ||||
| props: { | props: { | ||||
| filter: Filter, | filter: Filter, | ||||
| dummy: Boolean, | |||||
| }, | }, | ||||
| components: { | components: { | ||||
| NodeProps, | NodeProps, | ||||
| @@ -24,6 +32,7 @@ import Toggle from "@vueform/toggle"; | |||||
| }) | }) | ||||
| export default class FilterNode extends Vue { | export default class FilterNode extends Vue { | ||||
| filter!: Filter; | filter!: Filter; | ||||
| dummy = false; | |||||
| } | } | ||||
| </script> | </script> | ||||
| @@ -59,4 +68,8 @@ export default class FilterNode extends Vue { | |||||
| top: 10px; | top: 10px; | ||||
| left: 10px; | left: 10px; | ||||
| } | } | ||||
| .dummy { | |||||
| min-height: 200px; | |||||
| } | |||||
| </style> | </style> | ||||
| @@ -10,7 +10,7 @@ | |||||
| <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> | ||||
| <div v-if="dummy" class="source-node dummy">Drop here!</div> | |||||
| <div v-if="dummy" class="source-node dummy">Drop sounds here!</div> | |||||
| </div> | </div> | ||||
| </template> | </template> | ||||
| @@ -0,0 +1,63 @@ | |||||
| export const PresetSources: Array<{ | |||||
| name: string; | |||||
| kind: "Source"; | |||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |||||
| [x: string]: any; | |||||
| }> = [ | |||||
| { | |||||
| soundSet: { | |||||
| name: "Gurgles", | |||||
| soundKeys: [ | |||||
| "gurgles/gurgle (1)", | |||||
| "gurgles/gurgle (2)", | |||||
| "gurgles/gurgle (3)", | |||||
| "gurgles/gurgle (4)", | |||||
| "gurgles/gurgle (5)", | |||||
| "gurgles/gurgle (6)", | |||||
| "gurgles/gurgle (7)", | |||||
| "gurgles/gurgle (8)", | |||||
| "gurgles/gurgle (9)", | |||||
| "gurgles/gurgle (10)", | |||||
| "gurgles/gurgle (11)", | |||||
| "gurgles/gurgle (12)", | |||||
| "gurgles/gurgle (13)", | |||||
| "gurgles/gurgle (14)", | |||||
| "gurgles/gurgle (15)", | |||||
| "gurgles/gurgle (16)", | |||||
| "gurgles/gurgle (17)", | |||||
| "gurgles/gurgle (18)", | |||||
| "gurgles/gurgle (19)", | |||||
| "gurgles/gurgle (20)", | |||||
| "gurgles/gurgle (21)", | |||||
| ], | |||||
| }, | |||||
| kind: "Source", | |||||
| volume: 1, | |||||
| interval: [4, 6], | |||||
| pitch: [0.9, 1.1], | |||||
| panning: [-0.2, 0.2], | |||||
| name: "Gurgles", | |||||
| type: "IntervalSource", | |||||
| }, | |||||
| { | |||||
| soundSet: { | |||||
| name: "Squishing", | |||||
| soundKeys: ["squishing"], | |||||
| }, | |||||
| kind: "Source", | |||||
| volume: 1, | |||||
| pitch: 1, | |||||
| name: "Squishing", | |||||
| type: "LoopingSource", | |||||
| }, | |||||
| ]; | |||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |||||
| export const PresetFilters: Array<{ name: string; [x: string]: any }> = [ | |||||
| { | |||||
| cutoff: 1000, | |||||
| name: "Lowpass Filter", | |||||
| kind: "Filter", | |||||
| type: "LowpassFilter", | |||||
| }, | |||||
| ]; | |||||
| @@ -1,15 +0,0 @@ | |||||
| 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" | |||||
| ); | |||||
| @@ -1,4 +1,22 @@ | |||||
| /* eslint-disable @typescript-eslint/no-explicit-any */ | |||||
| import { createApp } from "vue"; | import { createApp } from "vue"; | ||||
| import Dissolve from "./Dissolve.vue"; | import Dissolve from "./Dissolve.vue"; | ||||
| import { LowpassFilter } from "./filters/LowpassFilter"; | |||||
| import { | |||||
| deserializeNode, | |||||
| deserializeSoundscape, | |||||
| serializeNode, | |||||
| serializeSoundscape, | |||||
| } from "./serialize"; | |||||
| import { IntervalSource } from "./sources/IntervalSource"; | |||||
| import { SoundSet } from "./sources/Source"; | |||||
| createApp(Dissolve).mount("#app"); | createApp(Dissolve).mount("#app"); | ||||
| (window as any).IntervalSource = IntervalSource; | |||||
| (window as any).LowpassFilter = LowpassFilter; | |||||
| (window as any).SoundSet = SoundSet; | |||||
| (window as any).serializeNode = serializeNode; | |||||
| (window as any).deserializeNode = deserializeNode; | |||||
| (window as any).serializeSoundscape = serializeSoundscape; | |||||
| (window as any).deserializeSoundscape = deserializeSoundscape; | |||||
| @@ -0,0 +1,150 @@ | |||||
| import { | |||||
| exposedMetadataNumber, | |||||
| exposedRangeMetadata, | |||||
| exposedSoundSetMetadata, | |||||
| NumberMetadata, | |||||
| RangeMetadata, | |||||
| Soundscape, | |||||
| SoundSetMetadata, | |||||
| } from "./audio"; | |||||
| import { IntervalSource } from "./sources/IntervalSource"; | |||||
| import { LoopingSource } from "./sources/LoopingSource"; | |||||
| import { Node } from "./audio"; | |||||
| const constructors: { [key: string]: new (name: string) => Node } = { | |||||
| IntervalSource: IntervalSource, | |||||
| LoopingSource: LoopingSource, | |||||
| LowpassFilter: LowpassFilter, | |||||
| HighpassFilter: HighpassFilter, | |||||
| SterwoWidthFilter: StereoWidthFilter, | |||||
| }; | |||||
| import { SoundSet, Source } from "./sources/Source"; | |||||
| import { Filter } from "./filters/Filter"; | |||||
| import { LowpassFilter } from "./filters/LowpassFilter"; | |||||
| import { HighpassFilter } from "./filters/HighpassFilter"; | |||||
| import { StereoWidthFilter } from "./filters/StereoWidthFilter"; | |||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |||||
| export function serializeNode<T extends Node>(_node: T): any { | |||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |||||
| const results: any = {}; | |||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |||||
| const node: any = _node; | |||||
| Object.keys(node).forEach((key) => { | |||||
| const numberMetadata: NumberMetadata | undefined = Reflect.getMetadata( | |||||
| exposedMetadataNumber, | |||||
| node, | |||||
| key | |||||
| ); | |||||
| if (numberMetadata !== undefined) { | |||||
| results[key] = node[key]; | |||||
| } | |||||
| const rangeMetadata: RangeMetadata | undefined = Reflect.getMetadata( | |||||
| exposedRangeMetadata, | |||||
| node, | |||||
| key | |||||
| ); | |||||
| if (rangeMetadata !== undefined) { | |||||
| results[key] = node[key]; | |||||
| } | |||||
| const soundSetMetadata: SoundSetMetadata | undefined = Reflect.getMetadata( | |||||
| exposedSoundSetMetadata, | |||||
| node, | |||||
| key | |||||
| ); | |||||
| if (soundSetMetadata !== undefined) { | |||||
| const soundSet = node[key] as SoundSet; | |||||
| results[key] = { | |||||
| name: soundSet.name, | |||||
| soundKeys: soundSet.soundKeys, | |||||
| }; | |||||
| } | |||||
| }); | |||||
| results.kind = node.kind; | |||||
| results.name = node.name; | |||||
| results.type = node.constructor.name; | |||||
| return results; | |||||
| } | |||||
| // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any | |||||
| export function deserializeNode(data: any): Node { | |||||
| const constructor = constructors[data.type]; | |||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |||||
| const node: any = new constructor(data.name); | |||||
| Object.keys(node).forEach((key) => { | |||||
| const numberMetadata: NumberMetadata | undefined = Reflect.getMetadata( | |||||
| exposedMetadataNumber, | |||||
| node, | |||||
| key | |||||
| ); | |||||
| if (numberMetadata !== undefined) { | |||||
| node[key] = data[key]; | |||||
| } | |||||
| const rangeMetadata: RangeMetadata | undefined = Reflect.getMetadata( | |||||
| exposedRangeMetadata, | |||||
| node, | |||||
| key | |||||
| ); | |||||
| if (rangeMetadata !== undefined) { | |||||
| node[key] = data[key]; | |||||
| } | |||||
| const soundSetMetadata: SoundSetMetadata | undefined = Reflect.getMetadata( | |||||
| exposedSoundSetMetadata, | |||||
| node, | |||||
| key | |||||
| ); | |||||
| if (soundSetMetadata !== undefined) { | |||||
| node[key] = new SoundSet(data[key].name, data[key].soundKeys); | |||||
| } | |||||
| }); | |||||
| return node as Node; | |||||
| } | |||||
| export type SerializedSoundscape = { | |||||
| name: string; | |||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |||||
| sources: Array<any>; | |||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |||||
| filters: Array<any>; | |||||
| }; | |||||
| export function serializeSoundscape(scape: Soundscape): SerializedSoundscape { | |||||
| const result = { | |||||
| name: scape.name, | |||||
| sources: scape.sources.map((source) => serializeNode(source)), | |||||
| filters: scape.filters.map((filter) => serializeNode(filter)), | |||||
| }; | |||||
| return result; | |||||
| } | |||||
| export function deserializeSoundscape(data: SerializedSoundscape): Soundscape { | |||||
| const scape = new Soundscape(); | |||||
| scape.name = data.name; | |||||
| data.sources | |||||
| .map((source) => deserializeNode(source) as Source) | |||||
| .forEach((source) => scape.addSource(source)); | |||||
| data.filters | |||||
| .map((filter) => deserializeNode(filter) as Filter) | |||||
| .forEach((filter) => scape.addFilter(filter)); | |||||
| return scape; | |||||
| } | |||||
| @@ -2,8 +2,6 @@ import { Source } from "./Source"; | |||||
| import { context, exposedRange } from "../audio"; | import { context, exposedRange } from "../audio"; | ||||
| export class IntervalSource extends Source { | export class IntervalSource extends Source { | ||||
| kind = "Interval"; | |||||
| @exposedRange({ | @exposedRange({ | ||||
| name: "Interval", | name: "Interval", | ||||
| min: 0.25, | min: 0.25, | ||||
| @@ -1,8 +1,13 @@ | |||||
| import { Source } from "./Source"; | import { Source } from "./Source"; | ||||
| import { context, exposedNumber } from "../audio"; | import { context, exposedNumber } from "../audio"; | ||||
| export type SerializedLoopingSource = { | |||||
| name: string; | |||||
| volume: number; | |||||
| pitch: number; | |||||
| }; | |||||
| export class LoopingSource extends Source { | export class LoopingSource extends Source { | ||||
| kind = "Looping"; | |||||
| private source!: AudioBufferSourceNode; | private source!: AudioBufferSourceNode; | ||||
| private started = false; | private started = false; | ||||
| private running = false; | private running = false; | ||||
| @@ -21,6 +26,14 @@ export class LoopingSource extends Source { | |||||
| super(name); | super(name); | ||||
| } | } | ||||
| static deserialize(info: SerializedLoopingSource): LoopingSource { | |||||
| const source = new LoopingSource(info.name); | |||||
| source.volume = info.volume; | |||||
| source.pitch = info.pitch; | |||||
| return source; | |||||
| } | |||||
| public start(): void { | public start(): void { | ||||
| this.started = true; | this.started = true; | ||||
| } | } | ||||
| @@ -1,14 +1,16 @@ | |||||
| import { Node, context, exposedNumber, loadAudio } from "../audio"; | |||||
| import { | |||||
| Node, | |||||
| context, | |||||
| exposedNumber, | |||||
| loadAudio, | |||||
| exposedSoundSet, | |||||
| } from "../audio"; | |||||
| export class SoundSet { | export class SoundSet { | ||||
| soundMap: Map<string, AudioBuffer> = new Map(); | soundMap: Map<string, AudioBuffer> = new Map(); | ||||
| soundList: Array<AudioBuffer> = []; | soundList: Array<AudioBuffer> = []; | ||||
| constructor( | |||||
| public name: string, | |||||
| public soundKeys: Array<string>, | |||||
| public defaultSource: string | |||||
| ) { | |||||
| constructor(public name: string, public soundKeys: Array<string>) { | |||||
| this.soundKeys.forEach((sound) => { | this.soundKeys.forEach((sound) => { | ||||
| loadAudio(sound, this); | loadAudio(sound, this); | ||||
| }); | }); | ||||
| @@ -21,8 +23,12 @@ export class SoundSet { | |||||
| } | } | ||||
| export abstract class Source extends Node { | export abstract class Source extends Node { | ||||
| public abstract kind: string; | |||||
| public soundSet: SoundSet = new SoundSet("Empty", [], "IntervalSource"); | |||||
| kind = "Source"; | |||||
| @exposedSoundSet({ | |||||
| name: "Sounds", | |||||
| }) | |||||
| public soundSet: SoundSet = new SoundSet("Empty", []); | |||||
| public gain: GainNode; | public gain: GainNode; | ||||
| public output: GainNode; | public output: GainNode; | ||||
| public _active = true; | public _active = true; | ||||