sounds, better files
This commit is contained in:
177
src/model/Audio/SoundNode.ts
Normal file
177
src/model/Audio/SoundNode.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { MountedFs } from "../FileSystem/MountedFs";
|
||||
import { FileSystemSource, VirtualFile } from "../FileSystem/types";
|
||||
import { SoundboardContext } from "./types";
|
||||
|
||||
|
||||
export interface PartiaSoundNodeOptions {
|
||||
vFile: VirtualFile;
|
||||
volume?: number;
|
||||
type: 'buffered' | 'mediaElement';
|
||||
}
|
||||
|
||||
export interface SoundNodeOptions extends PartiaSoundNodeOptions {
|
||||
volume: number;
|
||||
}
|
||||
|
||||
export abstract class SoundNode {
|
||||
|
||||
protected gainNode: GainNode;
|
||||
protected file?: File;
|
||||
public options: SoundNodeOptions;
|
||||
public readonly numberOfInputs = 0;
|
||||
public readonly numberOfOutputs = 1;
|
||||
protected activeSources: AudioBufferSourceNode[] = [];
|
||||
protected audioContext: AudioContext;
|
||||
protected readonly fs: MountedFs;
|
||||
|
||||
constructor(
|
||||
options: PartiaSoundNodeOptions,
|
||||
ctx: SoundboardContext,
|
||||
) {
|
||||
this.options = {
|
||||
...options,
|
||||
volume: options.volume ?? 100
|
||||
}
|
||||
this.audioContext = ctx.audioContext;
|
||||
this.fs = ctx.fs;
|
||||
this.gainNode = this.audioContext.createGain();
|
||||
this.gainNode.gain.value = (this.options.volume / 100);
|
||||
}
|
||||
|
||||
get isPlaying() {
|
||||
return this.activeSources.length > 0;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.options.vFile.name.replace(/\.[^/.]+$/, "");
|
||||
}
|
||||
|
||||
get key() {
|
||||
return this.options.vFile.path.join('/');
|
||||
}
|
||||
|
||||
protected async preload() {
|
||||
if (!this.file) {
|
||||
this.file = await this.fs.download(this.options.vFile);
|
||||
}
|
||||
}
|
||||
|
||||
connect(destination: AudioNode) {
|
||||
console.log('connecting', this, destination);
|
||||
this.gainNode.connect(destination);
|
||||
}
|
||||
|
||||
disconnect(destinationNode?: AudioNode) {
|
||||
if (destinationNode) {
|
||||
this.gainNode.disconnect(destinationNode);
|
||||
} else {
|
||||
this.gainNode.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
setVolume(volume: number) {
|
||||
this.gainNode.gain.value = volume / 100;
|
||||
}
|
||||
|
||||
getVolume() {
|
||||
return this.gainNode.gain.value * 100;
|
||||
}
|
||||
|
||||
abstract play(): Promise<void>;
|
||||
abstract stop(): Promise<void>;
|
||||
}
|
||||
|
||||
export class BufferedSoundNode extends SoundNode {
|
||||
private buffer?: AudioBuffer;
|
||||
|
||||
async preload() {
|
||||
await super.preload();
|
||||
if (!this.buffer) {
|
||||
const arrayBuffer = await this.file!.arrayBuffer();
|
||||
this.buffer = await this.audioContext.decodeAudioData(arrayBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
async play() {
|
||||
await this.preload();
|
||||
const source = this.audioContext.createBufferSource();
|
||||
source.buffer = this.buffer!;
|
||||
source.connect(this.gainNode);
|
||||
source.start();
|
||||
this.audioContext.resume();
|
||||
this.activeSources.push(source);
|
||||
source.addEventListener('ended', () => {
|
||||
const index = this.activeSources.indexOf(source);
|
||||
if (index !== -1) {
|
||||
this.activeSources.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async stop() {
|
||||
this.activeSources.forEach((source) => source.stop());
|
||||
this.activeSources = [];
|
||||
}
|
||||
}
|
||||
|
||||
export class MediaElementSoundNode extends SoundNode {
|
||||
private mediaElement?: HTMLMediaElement;
|
||||
private source?: MediaElementAudioSourceNode;
|
||||
private _isPlaying = false;
|
||||
|
||||
async preload() {
|
||||
await super.preload();
|
||||
if (!this.mediaElement) {
|
||||
this.mediaElement = document.createElement('audio');
|
||||
this.mediaElement.src = URL.createObjectURL(this.file!);
|
||||
this.source = this.audioContext.createMediaElementSource(this.mediaElement);
|
||||
this.source.connect(this.gainNode);
|
||||
this.audioContext.resume();
|
||||
this.mediaElement.addEventListener('ended', ()=> {
|
||||
this._isPlaying = false;
|
||||
});
|
||||
this.mediaElement.addEventListener('playing', (event) => {
|
||||
console.log(event);
|
||||
this._isPlaying = true;
|
||||
});
|
||||
this.mediaElement.addEventListener('pause', () => {
|
||||
this._isPlaying = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get isPlaying() {
|
||||
return this._isPlaying;
|
||||
}
|
||||
|
||||
get currentTime() {
|
||||
return this.mediaElement!.currentTime;
|
||||
}
|
||||
|
||||
get duration() {
|
||||
return this.mediaElement!.duration;
|
||||
}
|
||||
|
||||
async play() {
|
||||
await this.preload();
|
||||
await this.mediaElement?.play();
|
||||
}
|
||||
|
||||
async stop() {
|
||||
await this.mediaElement?.pause();
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.mediaElement?.pause();
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.mediaElement?.play();
|
||||
}
|
||||
|
||||
seek(time: number) {
|
||||
if (this.mediaElement) {
|
||||
this.mediaElement.currentTime = time;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user