frontispiece / apps / frontispiece-editor / src / state / audio-state.ts
audio-state.ts
Raw
/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

import { hookstate, State, useHookstate } from '@hookstate/core'
import {
	AudioEngine,
	AudioEngineListenerInterface,
	PlaySoundOptions,
	SOUND_TYPE,
	StopSoundOptions,
} from '../audio/audio-engine'

export type AudioHookStateType = {
	volume: Record<SOUND_TYPE, number>
	currentlyPlayingSounds: string[]
}

const AudioEngineInstance = new AudioEngine()

const globalAudioHookState = hookstate<AudioHookStateType>({
	volume: {
		[SOUND_TYPE.MUSIC]: AudioEngineInstance.getVolume(SOUND_TYPE.MUSIC),
		[SOUND_TYPE.SFX]: AudioEngineInstance.getVolume(SOUND_TYPE.SFX),
		[SOUND_TYPE.AMBIENT]: AudioEngineInstance.getVolume(SOUND_TYPE.AMBIENT),
	},
	currentlyPlayingSounds: [],
})

const AudioEngineHookstateListener: AudioEngineListenerInterface = {
	setVolume: (type: SOUND_TYPE, volume: number) => {
		if (globalAudioHookState.volume[type].get() === volume) {
			return
		}
		globalAudioHookState.volume[type].set(volume)
	},
	playSound: (_sound) => {
		const sounds = AudioEngineInstance.getPlayingSounds().map((s) => {
			return s.name
		})
		globalAudioHookState.currentlyPlayingSounds.set(sounds)
	},
	stopSound: (_sound) => {
		const sounds = AudioEngineInstance.getPlayingSounds().map((s) => {
			return s.name
		})
		globalAudioHookState.currentlyPlayingSounds.set(sounds)
	},
}

AudioEngineInstance.addListener(AudioEngineHookstateListener)

const getAudioHookStateController = (state: State<AudioHookStateType>) => {
	return {
		_state: state,
		addSound: (urls: string[], name: string, type: SOUND_TYPE) => {
			AudioEngineInstance.addSound(urls, name, type);
		},

		exists: (name: string) => {
			return AudioEngineInstance.exists(name)
		},

		getVolume: (type: SOUND_TYPE) => {
			return state.volume[type].get()
		},
		setVolume: (type: SOUND_TYPE, volume: number) => {
			return AudioEngineInstance.setVolume(type, volume)
		},

		/**
		 * Helper to play sound.
		 * Since this interacts with the AudioEngine, you can just call that directly.
		 * @param sound
		 * @param opts
		 */
		play: (sound: string, opts: PlaySoundOptions = {}) => {
			if (AudioEngineInstance.getType(sound) === SOUND_TYPE.MUSIC) {
				AudioEngineInstance.changeMusic(sound, opts, {
					fadeOut: true,
					fadeTime: 500,
				})
			} else {
				AudioEngineInstance.playSound(sound, opts)
			}
		},
		stop: (sound: string, opts: StopSoundOptions = {}) => {
			AudioEngineInstance.stopSound(sound, opts)
		},
		stopAll: (opts: StopSoundOptions = {}) => {
			AudioEngineInstance.stopAllSounds(opts)
		},
		stopMusic: (opts: StopSoundOptions = {}) => {
			AudioEngineInstance.stopAllSoundsOfType(SOUND_TYPE.MUSIC, opts)
		},

		getCurrentlyPlayingSoundIds: () => {
			return state.currentlyPlayingSounds.get()
		},
		getCurrentlyPlayingSounds: () => {
			return [...AudioEngineInstance.getPlayingSounds()]
		},
		getCurrentlyPlayingSoundsOfType: (type: SOUND_TYPE) => {
			return [...AudioEngineInstance.getPlayingSoundsOfType(type)]
		},

		isPlaying: (name: string) => {
			const _status = state.currentlyPlayingSounds.get().indexOf(name) > -1
			return _status
		},
	}
}

export const AudioHookStateController = getAudioHookStateController(globalAudioHookState)

export const useAudioHookState = () => {
	const state = useHookstate(globalAudioHookState)
	return getAudioHookStateController(state)
}