Also removes arguments from the source constructors -- these should be handled by setting exposed properties afterward.master
| @@ -52,34 +52,28 @@ export type NumberMetadata = { | |||||
| name: string; | name: string; | ||||
| min: number; | min: number; | ||||
| max: number; | max: number; | ||||
| format?: (value: number) => string; | |||||
| }; | }; | ||||
| export type RangeMetadata = { | export type RangeMetadata = { | ||||
| name: string; | name: string; | ||||
| min: number; | min: number; | ||||
| max: number; | max: number; | ||||
| format?: (value: number) => string; | |||||
| }; | }; | ||||
| 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 | ||||
| export function exposedNumber(name: string, min: number, max: number) { | |||||
| return Reflect.metadata(exposedMetadataNumber, { | |||||
| name: name, | |||||
| min: min, | |||||
| max: max, | |||||
| }); | |||||
| export function exposedNumber(options: NumberMetadata) { | |||||
| return Reflect.metadata(exposedMetadataNumber, options); | |||||
| } | } | ||||
| export const exposedRangeMetadata = Symbol("exposedRange"); | export const exposedRangeMetadata = Symbol("exposedRange"); | ||||
| // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types | ||||
| export function exposedRange(name: string, min: number, max: number) { | |||||
| return Reflect.metadata(exposedRangeMetadata, { | |||||
| name: name, | |||||
| min: min, | |||||
| max: max, | |||||
| }); | |||||
| export function exposedRange(options: RangeMetadata) { | |||||
| return Reflect.metadata(exposedRangeMetadata, options); | |||||
| } | } | ||||
| export let context: AudioContext; | export let context: AudioContext; | ||||
| @@ -5,23 +5,25 @@ | |||||
| v-for="(metadata, index) in numberProps" | v-for="(metadata, index) in numberProps" | ||||
| :key="index" | :key="index" | ||||
| > | > | ||||
| {{ metadata.name }} | |||||
| <div class="prop-name">{{ metadata.name }}</div> | |||||
| <Slider | <Slider | ||||
| v-model="node[metadata.key]" | v-model="node[metadata.key]" | ||||
| :min="metadata.min" | :min="metadata.min" | ||||
| :max="metadata.max" | :max="metadata.max" | ||||
| :step="-1" | :step="-1" | ||||
| :showTooltip="'drag'" | :showTooltip="'drag'" | ||||
| :format="metadata.format" | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| <div class="node-prop" v-for="(metadata, index) in rangeProps" :key="index"> | <div class="node-prop" v-for="(metadata, index) in rangeProps" :key="index"> | ||||
| {{ metadata.name }} | |||||
| <div class="prop-name">{{ metadata.name }}</div> | |||||
| <Slider | <Slider | ||||
| v-model="node[metadata.key]" | v-model="node[metadata.key]" | ||||
| :min="metadata.min" | :min="metadata.min" | ||||
| :max="metadata.max" | :max="metadata.max" | ||||
| :step="-1" | :step="-1" | ||||
| :showTooltip="'drag'" | :showTooltip="'drag'" | ||||
| :format="metadata.format" | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -62,12 +64,11 @@ export default class NodeProps extends Vue { | |||||
| ); | ); | ||||
| if (metadata !== undefined) { | if (metadata !== undefined) { | ||||
| this.numberProps.push({ | |||||
| name: metadata.name, | |||||
| key: key, | |||||
| min: metadata.min, | |||||
| max: metadata.max, | |||||
| }); | |||||
| const modified: NumberMetadata & { key: string } = Object.assign( | |||||
| metadata, | |||||
| { key: key } | |||||
| ); | |||||
| this.numberProps.push(modified); | |||||
| } | } | ||||
| }); | }); | ||||
| @@ -79,12 +80,11 @@ export default class NodeProps extends Vue { | |||||
| ); | ); | ||||
| if (metadata !== undefined) { | if (metadata !== undefined) { | ||||
| this.rangeProps.push({ | |||||
| name: metadata.name, | |||||
| key: key, | |||||
| min: metadata.min, | |||||
| max: metadata.max, | |||||
| }); | |||||
| const modified: RangeMetadata & { key: string } = Object.assign( | |||||
| metadata, | |||||
| { key: key } | |||||
| ); | |||||
| this.rangeProps.push(modified); | |||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| @@ -96,4 +96,8 @@ export default class NodeProps extends Vue { | |||||
| margin: 20px; | margin: 20px; | ||||
| user-select: none; | user-select: none; | ||||
| } | } | ||||
| .prop-name { | |||||
| font-size: 150%; | |||||
| margin-bottom: 4px; | |||||
| } | |||||
| </style> | </style> | ||||
| @@ -4,7 +4,12 @@ export class HighpassFilter extends Filter { | |||||
| public kind = "Biquad Filter"; | public kind = "Biquad Filter"; | ||||
| private biquad: BiquadFilterNode; | private biquad: BiquadFilterNode; | ||||
| @exposedNumber("Cutoff", 10, 10000) | |||||
| @exposedNumber({ | |||||
| name: "Cutoff Frequency", | |||||
| min: 10, | |||||
| max: 10000, | |||||
| format: (value: number) => value + "Hz", | |||||
| }) | |||||
| public cutoff = 500; | public cutoff = 500; | ||||
| constructor() { | constructor() { | ||||
| @@ -4,7 +4,12 @@ export class BiquadFilter extends Filter { | |||||
| public kind = "Biquad Filter"; | public kind = "Biquad Filter"; | ||||
| private biquad: BiquadFilterNode; | private biquad: BiquadFilterNode; | ||||
| @exposedNumber("Cutoff", 10, 10000) | |||||
| @exposedNumber({ | |||||
| name: "Cutoff Frequency", | |||||
| min: 10, | |||||
| max: 10000, | |||||
| format: (value: number) => value + "Hz", | |||||
| }) | |||||
| public cutoff = 1000; | public cutoff = 1000; | ||||
| constructor() { | constructor() { | ||||
| @@ -5,7 +5,12 @@ export class StereoWidthFilter extends Filter { | |||||
| private mono: GainNode; | private mono: GainNode; | ||||
| private stereo: GainNode; | private stereo: GainNode; | ||||
| @exposedNumber("Width", 0, 1) | |||||
| @exposedNumber({ | |||||
| name: "Width", | |||||
| min: 0, | |||||
| max: 1, | |||||
| format: (value: number) => Math.round(value * 100) + "%", | |||||
| }) | |||||
| public width = 1; | public width = 1; | ||||
| constructor() { | constructor() { | ||||
| @@ -1,31 +1,54 @@ | |||||
| import { Source } from "./Source"; | import { Source } from "./Source"; | ||||
| import { exposedNumber, exposedRange, context } from "../audio"; | |||||
| import { exposedNumber, context, exposedRange } from "../audio"; | |||||
| export class IntervalSource extends Source { | export class IntervalSource extends Source { | ||||
| kind = "Interval"; | kind = "Interval"; | ||||
| @exposedNumber("Pitch", 0.25, 4) | |||||
| @exposedNumber({ | |||||
| name: "Pitch", | |||||
| min: 0.25, | |||||
| max: 4, | |||||
| format: (value: number) => Math.round(value * 100) + "%", | |||||
| }) | |||||
| public pitch = 1; | public pitch = 1; | ||||
| @exposedRange("Interval", 0.25, 30) | |||||
| public interval: [number, number] = [1, 5]; | |||||
| @exposedRange("Panning", -1, 1) | |||||
| // | |||||
| @exposedNumber({ | |||||
| name: "Interval", | |||||
| min: 0.25, | |||||
| max: 30, | |||||
| format: (value: number) => { | |||||
| return ( | |||||
| value.toLocaleString(undefined, { | |||||
| maximumFractionDigits: 2, | |||||
| }) + "s" | |||||
| ); | |||||
| }, | |||||
| }) | |||||
| public interval: [number, number] = [4, 6]; | |||||
| @exposedRange({ | |||||
| name: "Left/Right Range", | |||||
| min: -1, | |||||
| max: 1, | |||||
| format: (value: number) => { | |||||
| if (value < 0) { | |||||
| return Math.round(value * 100) + "L"; | |||||
| } else if (value > 0) { | |||||
| return Math.round(value * 100) + "R"; | |||||
| } else { | |||||
| return "0"; | |||||
| } | |||||
| }, | |||||
| }) | |||||
| public panning: [number, number] = [-0.2, 0.2]; | public panning: [number, number] = [-0.2, 0.2]; | ||||
| private remaining = 0; | private remaining = 0; | ||||
| private started = false; | private started = false; | ||||
| constructor( | |||||
| name: string, | |||||
| minTime: number, | |||||
| maxTime: number, | |||||
| public randomness = 0 | |||||
| ) { | |||||
| constructor(name: string) { | |||||
| super(name); | super(name); | ||||
| this.interval = [minTime, maxTime]; | |||||
| this.setTimer(); | this.setTimer(); | ||||
| } | } | ||||
| @@ -7,7 +7,12 @@ export class LoopingSource extends Source { | |||||
| private started = false; | private started = false; | ||||
| private running = false; | private running = false; | ||||
| @exposedNumber("Pitch", 0.25, 4) | |||||
| @exposedNumber({ | |||||
| name: "Pitch", | |||||
| min: 0.25, | |||||
| max: 4, | |||||
| format: (value: number) => Math.round(value * 100) + "%", | |||||
| }) | |||||
| public pitch = 1; | public pitch = 1; | ||||
| constructor(name: string) { | constructor(name: string) { | ||||
| @@ -3,7 +3,7 @@ import { LoopingSource } from "./LoopingSource"; | |||||
| import { Source } from "./Source"; | import { Source } from "./Source"; | ||||
| export function makeGlorps(): Source { | export function makeGlorps(): Source { | ||||
| const source: Source = new IntervalSource("Guts", 5, 8); | |||||
| const source: Source = new IntervalSource("Guts"); | |||||
| source.loadSound("bowels-to-intestines"); | source.loadSound("bowels-to-intestines"); | ||||
| source.loadSound("intestines-to-bowels"); | source.loadSound("intestines-to-bowels"); | ||||
| source.loadSound("intestines-to-stomach"); | source.loadSound("intestines-to-stomach"); | ||||
| @@ -27,7 +27,7 @@ export function makeDigestion(): Source { | |||||
| } | } | ||||
| export function makeBurps(): Source { | export function makeBurps(): Source { | ||||
| const source: Source = new IntervalSource("Burps", 5, 15); | |||||
| const source: Source = new IntervalSource("Burps"); | |||||
| source.loadSound("belch (1)"); | source.loadSound("belch (1)"); | ||||
| source.loadSound("belch (2)"); | source.loadSound("belch (2)"); | ||||
| source.loadSound("belch (3)"); | source.loadSound("belch (3)"); | ||||
| @@ -51,7 +51,7 @@ export function makeBurps(): Source { | |||||
| } | } | ||||
| export function makeGurgles(): Source { | export function makeGurgles(): Source { | ||||
| const source: Source = new IntervalSource("Gurgles", 3, 10); | |||||
| const source: Source = new IntervalSource("Gurgles"); | |||||
| source.loadSound("gurgles/gurgle (1)"); | source.loadSound("gurgles/gurgle (1)"); | ||||
| source.loadSound("gurgles/gurgle (2)"); | source.loadSound("gurgles/gurgle (2)"); | ||||
| source.loadSound("gurgles/gurgle (3)"); | source.loadSound("gurgles/gurgle (3)"); | ||||
| @@ -20,7 +20,12 @@ export abstract class Source extends Node { | |||||
| ); | ); | ||||
| } | } | ||||
| @exposedNumber("Volume", 0, 1) | |||||
| @exposedNumber({ | |||||
| name: "Volume", | |||||
| min: 0, | |||||
| max: 1, | |||||
| format: (value: number) => Math.round(value * 100) + "%", | |||||
| }) | |||||
| public volume = 1; | public volume = 1; | ||||
| constructor(name: string) { | constructor(name: string) { | ||||