Also goes back to avoiding default-export where possiblemaster
| @@ -1,33 +0,0 @@ | |||
| <template> | |||
| <VoreAudio /> | |||
| </template> | |||
| <script lang="ts"> | |||
| import { Options, Vue } from "vue-class-component"; | |||
| import VoreAudio from "./components/VoreAudio.vue"; | |||
| @Options({ | |||
| components: { | |||
| VoreAudio, | |||
| }, | |||
| }) | |||
| export default class App extends Vue {} | |||
| </script> | |||
| <style> | |||
| body { | |||
| background: #111; | |||
| } | |||
| #app { | |||
| font-family: Avenir, Helvetica, Arial, sans-serif; | |||
| -webkit-font-smoothing: antialiased; | |||
| -moz-osx-font-smoothing: grayscale; | |||
| text-align: center; | |||
| color: #ddd; | |||
| background: #111; | |||
| margin-top: 60px; | |||
| } | |||
| </style> | |||
| <style src="@vueform/slider/themes/default.css"></style> | |||
| <style src="@vueform/toggle/themes/default.css"></style> | |||
| @@ -0,0 +1,63 @@ | |||
| <template> | |||
| <h1>Dissolve</h1> | |||
| <div>This is a mega-early-alpha vore audio generator.</div> | |||
| <div> | |||
| Follow <a href="https://twitter.com/causticcrux">@causticcrux</a> for more. | |||
| </div> | |||
| <div> | |||
| Many sounds by <a href="https://www.furaffinity.net/user/jeschke">Jit</a>! | |||
| </div> | |||
| <button v-on:click="start" class="start-button"> | |||
| {{ started ? "Add" : "Start" }} | |||
| </button> | |||
| <SoundscapeComp | |||
| v-for="(soundscape, index) in soundscapes" | |||
| :key="index" | |||
| :soundscape="soundscape" | |||
| /> | |||
| </template> | |||
| <script lang="ts"> | |||
| import { Options, Vue } from "vue-class-component"; | |||
| import { setup, Soundscape } from "./audio"; | |||
| import SoundscapeComp from "./components/SoundscapeComp.vue"; | |||
| @Options({ | |||
| components: { | |||
| SoundscapeComp, | |||
| }, | |||
| }) | |||
| export default class Dissolve extends Vue { | |||
| started = false; | |||
| soundscapes: Array<Soundscape> = []; | |||
| start(): void { | |||
| this.started = true; | |||
| const scape: Soundscape = new Soundscape(); | |||
| this.soundscapes.push(scape); | |||
| } | |||
| mounted(): void { | |||
| setup(); | |||
| } | |||
| } | |||
| </script> | |||
| <style> | |||
| body { | |||
| background: #111; | |||
| } | |||
| #app { | |||
| font-family: Avenir, Helvetica, Arial, sans-serif; | |||
| -webkit-font-smoothing: antialiased; | |||
| -moz-osx-font-smoothing: grayscale; | |||
| text-align: center; | |||
| color: #ddd; | |||
| background: #111; | |||
| margin-top: 60px; | |||
| } | |||
| </style> | |||
| <style src="@vueform/slider/themes/default.css"></style> | |||
| <style src="@vueform/toggle/themes/default.css"></style> | |||
| @@ -1,8 +1,49 @@ | |||
| import "reflect-metadata"; | |||
| import Source from "./sources/Source"; | |||
| import { Filter } from "./filters/Filter"; | |||
| import { Source } from "./sources/Source"; | |||
| let ogg_support = false; | |||
| export class Soundscape { | |||
| public sources: Array<Source> = []; | |||
| public filters: Array<Filter> = []; | |||
| public filterBus: GainNode; | |||
| addSource(source: Source): void { | |||
| source.output.connect(this.filterBus); | |||
| this.sources.push(source); | |||
| source.start(); | |||
| } | |||
| addFilter(filter: Filter): void { | |||
| if (this.filters.length > 0) { | |||
| const last: Filter = this.filters[this.filters.length - 1]; | |||
| last.output.disconnect(); | |||
| last.output.connect(filter.input); | |||
| filter.output.connect(context.destination); | |||
| } else { | |||
| this.filterBus.disconnect(); | |||
| this.filterBus.connect(filter.input); | |||
| filter.output.connect(context.destination); | |||
| } | |||
| filter.start(); | |||
| this.filters.push(filter); | |||
| } | |||
| start(): void { | |||
| setInterval(() => { | |||
| this.sources.forEach((source) => source.tick(100)); | |||
| this.filters.forEach((filter) => filter.tick(100)); | |||
| }, 100); | |||
| context.resume(); | |||
| } | |||
| constructor() { | |||
| this.filterBus = context.createGain(); | |||
| } | |||
| } | |||
| export abstract class Node { | |||
| constructor(public name: string) {} | |||
| } | |||
| @@ -0,0 +1,87 @@ | |||
| <template> | |||
| <div class="soundscape"> | |||
| <source-node | |||
| v-for="(source, index) in soundscape.sources" | |||
| :key="index" | |||
| :source="source" | |||
| > | |||
| </source-node> | |||
| <filter-node | |||
| v-for="(filter, index) in soundscape.filters" | |||
| :key="index" | |||
| :filter="filter" | |||
| > | |||
| </filter-node> | |||
| </div> | |||
| <div></div> | |||
| <button v-on:click="clear">Delete all cached sound (if it gets stuck)</button> | |||
| </template> | |||
| <script lang="ts"> | |||
| import { clearCache, Soundscape } from "@/audio"; | |||
| import { Options, Vue } from "vue-class-component"; | |||
| import SourceNode from "./nodes/SourceNode.vue"; | |||
| import FilterNode from "./nodes/FilterNode.vue"; | |||
| import { Filter } from "@/filters/Filter"; | |||
| import { BiquadFilter } from "@/filters/LowpassFilter"; | |||
| import { StereoWidthFilter } from "@/filters/StereoWidthFilter"; | |||
| import { HighpassFilter } from "@/filters/HighpassFilter"; | |||
| import * as Sources from "@/sources/PremadeSources"; | |||
| @Options({ | |||
| props: { | |||
| soundscape: Soundscape, | |||
| }, | |||
| components: { | |||
| SourceNode, | |||
| FilterNode, | |||
| }, | |||
| }) | |||
| export default class SoundscapeComp extends Vue { | |||
| soundscape!: Soundscape; | |||
| started = false; | |||
| context!: AudioContext; | |||
| clear(): void { | |||
| clearCache(); | |||
| } | |||
| mounted(): void { | |||
| this.soundscape.addSource(Sources.makeGlorps()); | |||
| this.soundscape.addSource(Sources.makeDigestion()); | |||
| this.soundscape.addSource(Sources.makeBurps()); | |||
| this.soundscape.addSource(Sources.makeGurgles()); | |||
| const biquad: Filter = new BiquadFilter(); | |||
| biquad.active = false; | |||
| this.soundscape.addFilter(biquad); | |||
| const stereo: Filter = new StereoWidthFilter(); | |||
| stereo.active = false; | |||
| this.soundscape.addFilter(stereo); | |||
| const highpass: Filter = new HighpassFilter(); | |||
| highpass.active = false; | |||
| this.soundscape.addFilter(highpass); | |||
| this.soundscape.start(); | |||
| console.log(this.soundscape); | |||
| } | |||
| } | |||
| </script> | |||
| <style scoped> | |||
| .soundscape { | |||
| margin: auto; | |||
| padding: 20px; | |||
| height: 100%; | |||
| display: grid; | |||
| grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); | |||
| grid-auto-rows: 200px; | |||
| grid-gap: 20px; | |||
| } | |||
| .start-button { | |||
| font-size: 60pt; | |||
| } | |||
| </style> | |||
| @@ -1,211 +0,0 @@ | |||
| <template> | |||
| <h1>Dissolve</h1> | |||
| <div>This is a mega-early-alpha vore audio generator.</div> | |||
| <div> | |||
| Follow <a href="https://twitter.com/causticcrux">@causticcrux</a> for more. | |||
| </div> | |||
| <div> | |||
| Many sounds by <a href="https://www.furaffinity.net/user/jeschke">Jit</a>! | |||
| </div> | |||
| <button v-on:click="start" class="start-button" v-if="!started">Start</button> | |||
| <div class="soundscape"> | |||
| <source-node | |||
| v-for="(source, index) in sources" | |||
| :key="index" | |||
| :source="source" | |||
| > | |||
| </source-node> | |||
| <filter-node | |||
| v-for="(filter, index) in filters" | |||
| :key="index" | |||
| :filter="filter" | |||
| > | |||
| </filter-node> | |||
| </div> | |||
| <div></div> | |||
| <button v-on:click="clear">Delete all cached sound (if it gets stuck)</button> | |||
| </template> | |||
| <script lang="ts"> | |||
| import { clearCache, setup } from "@/audio"; | |||
| import { Options, Vue } from "vue-class-component"; | |||
| import Source from "@/sources/Source"; | |||
| import SourceNode from "./nodes/SourceNode.vue"; | |||
| import FilterNode from "./nodes/FilterNode.vue"; | |||
| import LoopingSource from "@/sources/LoopingSource"; | |||
| import IntervalSource from "@/sources/IntervalSource"; | |||
| import Filter from "@/filters/Filter"; | |||
| import BiquadFilter from "@/filters/LowpassFilter"; | |||
| import StereoWidthFilter from "@/filters/StereoWidthFilter"; | |||
| import HighpassFilter from "@/filters/HighpassFilter"; | |||
| @Options({ | |||
| props: { | |||
| msg: String, | |||
| }, | |||
| components: { | |||
| SourceNode, | |||
| FilterNode, | |||
| }, | |||
| }) | |||
| export default class VoreAudio extends Vue { | |||
| started = false; | |||
| context!: AudioContext; | |||
| sources: Array<Source> = []; | |||
| filters: Array<Filter> = []; | |||
| filterBus!: GainNode; | |||
| addSource(source: Source): void { | |||
| source.output.connect(this.filterBus); | |||
| this.sources.push(source); | |||
| source.start(); | |||
| } | |||
| addFilter(filter: Filter): void { | |||
| if (this.filters.length > 0) { | |||
| const last: Filter = this.filters[this.filters.length - 1]; | |||
| last.output.disconnect(); | |||
| last.output.connect(filter.input); | |||
| filter.output.connect(this.context.destination); | |||
| } else { | |||
| this.filterBus.disconnect(); | |||
| this.filterBus.connect(filter.input); | |||
| filter.output.connect(this.context.destination); | |||
| } | |||
| filter.start(); | |||
| this.filters.push(filter); | |||
| } | |||
| startGlorps(): void { | |||
| const source: Source = new IntervalSource("Guts", 5, 8); | |||
| 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"); | |||
| source.active = false; | |||
| this.addSource(source); | |||
| } | |||
| startDigestion(): void { | |||
| const source: Source = new LoopingSource("Digestion"); | |||
| source.loadSound("fen-stomach"); | |||
| source.loadSound("fen-intestines"); | |||
| source.loadSound("fen-bowels"); | |||
| this.addSource(source); | |||
| } | |||
| startBurps(): void { | |||
| const source: Source = new IntervalSource("Burps", 5, 15); | |||
| 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.active = false; | |||
| this.addSource(source); | |||
| } | |||
| startGurgles(): void { | |||
| const source: Source = new IntervalSource("Gurgles", 3, 10); | |||
| 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)"); | |||
| this.addSource(source); | |||
| } | |||
| clear(): void { | |||
| clearCache(); | |||
| } | |||
| start(): void { | |||
| this.context.resume(); | |||
| if (this.started) { | |||
| return; | |||
| } | |||
| this.started = true; | |||
| this.startGlorps(); | |||
| this.startGurgles(); | |||
| this.startDigestion(); | |||
| this.startBurps(); | |||
| const biquad: Filter = new BiquadFilter(); | |||
| biquad.active = false; | |||
| this.addFilter(biquad); | |||
| const stereo: Filter = new StereoWidthFilter(); | |||
| stereo.active = false; | |||
| this.addFilter(stereo); | |||
| const highpass: Filter = new HighpassFilter(); | |||
| highpass.active = false; | |||
| this.addFilter(highpass); | |||
| setInterval(() => { | |||
| this.sources.forEach((source) => source.tick(100)); | |||
| this.filters.forEach((filter) => filter.tick(100)); | |||
| }, 100); | |||
| } | |||
| mounted(): void { | |||
| this.context = setup(); | |||
| this.filterBus = this.context.createGain(); | |||
| this.filterBus.connect(this.context.destination); | |||
| } | |||
| } | |||
| </script> | |||
| <style scoped> | |||
| .soundscape { | |||
| margin: auto; | |||
| padding: 20px; | |||
| width: minmax(50vw, 1500px); | |||
| height: 100%; | |||
| display: grid; | |||
| grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); | |||
| grid-auto-rows: 200px; | |||
| grid-gap: 20px; | |||
| } | |||
| .start-button { | |||
| font-size: 60pt; | |||
| } | |||
| </style> | |||
| @@ -7,7 +7,7 @@ | |||
| </template> | |||
| <script lang="ts"> | |||
| import Filter from "@/filters/Filter"; | |||
| import { Filter } from "@/filters/Filter"; | |||
| import { Options, Vue } from "vue-class-component"; | |||
| import NodeProps from "@/components/NodeProps.vue"; | |||
| @@ -7,7 +7,7 @@ | |||
| </template> | |||
| <script lang="ts"> | |||
| import Source from "@/sources/Source"; | |||
| import { Source } from "@/sources/Source"; | |||
| import { Options, Vue } from "vue-class-component"; | |||
| import NodeProps from "@/components/NodeProps.vue"; | |||
| import Toggle from "@vueform/toggle"; | |||
| @@ -15,7 +15,7 @@ import Toggle from "@vueform/toggle"; | |||
| @Options({ | |||
| props: { | |||
| source: Source, | |||
| }, | |||
| }, | |||
| components: { | |||
| NodeProps, | |||
| Toggle, | |||
| @@ -1,6 +1,6 @@ | |||
| import { Node, context } from "../audio"; | |||
| export default abstract class Filter extends Node { | |||
| export abstract class Filter extends Node { | |||
| public abstract kind: string; | |||
| public input: GainNode; | |||
| protected filterInput: GainNode; | |||
| @@ -1,6 +1,6 @@ | |||
| import Filter from "./Filter"; | |||
| import { Filter } from "./Filter"; | |||
| import { context, exposedNumber } from "../audio"; | |||
| export default class HighpassFilter extends Filter { | |||
| export class HighpassFilter extends Filter { | |||
| public kind = "Biquad Filter"; | |||
| private biquad: BiquadFilterNode; | |||
| @@ -1,6 +1,6 @@ | |||
| import Filter from "./Filter"; | |||
| import { Filter } from "./Filter"; | |||
| import { context, exposedNumber } from "../audio"; | |||
| export default class BiquadFilter extends Filter { | |||
| export class BiquadFilter extends Filter { | |||
| public kind = "Biquad Filter"; | |||
| private biquad: BiquadFilterNode; | |||
| @@ -1,6 +1,6 @@ | |||
| import Filter from "./Filter"; | |||
| import { Filter } from "./Filter"; | |||
| import { context, exposedNumber } from "../audio"; | |||
| export default class StereoWidthFilter extends Filter { | |||
| export class StereoWidthFilter extends Filter { | |||
| public kind = "Stereo Width"; | |||
| private mono: GainNode; | |||
| private stereo: GainNode; | |||
| @@ -1,4 +1,4 @@ | |||
| import { createApp } from "vue"; | |||
| import App from "./App.vue"; | |||
| import Dissolve from "./Dissolve.vue"; | |||
| createApp(App).mount("#app"); | |||
| createApp(Dissolve).mount("#app"); | |||
| @@ -1,7 +1,7 @@ | |||
| import Source from "./Source"; | |||
| import { Source } from "./Source"; | |||
| import { exposedRange, context } from "../audio"; | |||
| export default class IntervalSource extends Source { | |||
| export class IntervalSource extends Source { | |||
| kind = "Interval"; | |||
| @exposedRange("Interval", 0.25, 30) | |||
| @@ -1,7 +1,7 @@ | |||
| import Source from "./Source"; | |||
| import { Source } from "./Source"; | |||
| import { context } from "../audio"; | |||
| export default class LoopingSource extends Source { | |||
| export class LoopingSource extends Source { | |||
| kind = "Looping"; | |||
| private source!: AudioBufferSourceNode; | |||
| private started = false; | |||
| @@ -0,0 +1,78 @@ | |||
| import { IntervalSource } from "./IntervalSource"; | |||
| import { LoopingSource } from "./LoopingSource"; | |||
| import { Source } from "./Source"; | |||
| export function makeGlorps(): Source { | |||
| const source: Source = new IntervalSource("Guts", 5, 8); | |||
| 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"); | |||
| return source; | |||
| } | |||
| export function makeDigestion(): Source { | |||
| const source: Source = new LoopingSource("Digestion"); | |||
| source.loadSound("fen-stomach"); | |||
| source.loadSound("fen-intestines"); | |||
| source.loadSound("fen-bowels"); | |||
| return source; | |||
| } | |||
| export function makeBurps(): Source { | |||
| const source: Source = new IntervalSource("Burps", 5, 15); | |||
| 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.active = false; | |||
| return source; | |||
| } | |||
| export function makeGurgles(): Source { | |||
| const source: Source = new IntervalSource("Gurgles", 3, 10); | |||
| 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)"); | |||
| return source; | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| import { Node, context, exposedNumber, loadAudio } from "../audio"; | |||
| export default abstract class Source extends Node { | |||
| export abstract class Source extends Node { | |||
| public abstract kind: string; | |||
| public sounds: Array<AudioBuffer> = []; | |||
| public gain: GainNode; | |||