mindboost-rnbo-test-project/stores/player.ts

248 lines
9.0 KiB
TypeScript

// stores/counter.js
import { defineStore, type _ActionsTree, type StoreDefinition, type StateTree } from 'pinia'
import { type AudioNodeItem } from './interfaces/AudioNodeItem'
import { useUserStore } from './user'
import { connectAudioNodes } from '~/lib/AudioFunctions'
import type Player from '~/components/Player/Player'
export const usePlayerStore: StoreDefinition = defineStore<'player', StateTree, _ActionsTree>('player', {
state: () => {
return {
audioContext: Howler.ctx as AudioContext,
nodes: new Map() as Map<string, AudioNodeItem>,
playing: false,
scene: 'Lagoon',
acusticCheckResult: 0,
scenePlayer: null as Player | null,
noisePlayer: null as Player | null
}
},
actions: {
setPlayers ({ scenePlayer, noisePlayer }:any) {
this.scenePlayer = scenePlayer
this.noisePlayer = noisePlayer
},
//
// Audio Context Actions
//
isPlaying (): boolean {
return this.playing
},
pauseContext (): void {
const audioContext = this.getAudioContext()
if (audioContext) {
audioContext.playbackRate = 0
audioContext.suspend()
}
},
async getAudioContext () : Promise<AudioContext> {
if (Howler.ctx) {
// useNuxtApp().$logger.log('Audio Context is there...')
if (Howler.ctx.state === 'suspended') { Howler.ctx.resume() }
return Howler.ctx
} else {
return await this.initializeAudioContext()
}
},
getContext () : AudioContext {
// useNuxtApp().$logger.log('getContext')
if (this.audioContext) { return this.audioContext } else {
this.audioContext = Howler.ctx
return this.audioContext
}
},
async initializeAudioContext (): Promise<AudioContext> {
// useNuxtApp().$logger.log('initializeAudioContext')
this.resetAudioContext()
return await this.audioContext
},
resetAudioContext () {
if (Howler.ctx) {
// useNuxtApp().$logger.log('AudioContext has been reset')
Howler.unload()
} else if (this.audioContext instanceof AudioContext && this.audioContext.state !== 'closed') {
try {
Howler.stop()
Howler.unload()
this.audioContext = null
this.setPlaying(false)
// useNuxtApp().$logger.log('AudioContext has been reset')
} catch (_error) {
// useNuxtApp().$logger.error('Error closing AudioContext:', _error)
}
}
},
setPlaying (stateChange: boolean): void {
this.playing = stateChange
},
//
// Working with Web Audio Nodes
//
removeNodeByType (name: string): void {
if (this.nodes && this.nodes.has(name)) {
this.nodes.delete(name)
}
},
getNodeByType (name: string): AudioNodeItem | null {
if (this.nodes instanceof Map) {
return this.nodes.get(name) as AudioNodeItem
}
return null
},
getMicrophoneNode (): AudioNodeItem | null {
return this.nodes.get('microphone')
},
async getBufferSourceNode (name: string): Promise<AudioNodeItem | null> {
const node:AudioNodeItem = this.nodes.has(name)
if (node) {
// useNuxtApp().$logger.log('Node gibts schon')
return node
} else {
await this.createBufferSource(name)
const createdNode = this.getNodeByType(name)
// useNuxtApp().$logger.log('created node ', { createdNode })
return createdNode
}
},
async createMediaStreamNodeAsync (steam: MediaStream): Promise<MediaStreamAudioSourceNode> {
const audioCtx: AudioContext = await this.getAudioContext()
// useNuxtApp().$logger.log({ audioCtx })
const streamNode = audioCtx.createMediaStreamSource(steam)
this.addNode('microphone', streamNode)
return streamNode
},
createMediaStreamNode (steam: MediaStream, audioContext: AudioContext): MediaStreamAudioSourceNode {
const audioCtx: AudioContext = audioContext
// useNuxtApp().$logger.log({ audioCtx })
const streamNode = audioCtx.createMediaStreamSource(steam)
this.addNode('microphone', streamNode)
return streamNode
},
async createBufferSource (name:string): Promise<AudioNodeItem> {
const bufferStore = this.bufferStore()
const audioBuffer = await bufferStore.getAudioBufferByName(name)
// if (!audioBuffer) { useNuxtApp().$logger.log({ bufferStore }) }
const audioCtx = await this.getAudioContext()
const abn = audioCtx.createBufferSource()
abn.buffer = audioBuffer
const result = this.addNode(name, abn)
return result
},
disconnectAllNodes (): void {
if (this.nodes.length > 0) {
this.nodes.forEach((n:AudioNodeItem) => n.node?.disconnect())
this.nodes = {} as Map<string, AudioNodeItem>
}
},
disconnectNode (node: AudioNodeItem): void {
const prevNode = node.previousNodeName
const nextNode = node.nextNodeName
node.node?.disconnect()
},
startNode (node:AudioNodeItem) {
if (node.node instanceof AudioBufferSourceNode) {
node.node?.start(0)
node.started = true
// useNuxtApp().$logger.log('started Node' + node.type, { nod1 })
} else {
useNuxtApp().$logger.info(`Node ${node.type} is not a AudioBufferSourceNode, cannot be started`)
// useNuxtApp().$logger.error('Node not defined in start node ')
}
},
addNode(type: string, node: AudioNode): AudioNodeItem {
const existingNode = this.nodes.get(type)
const isCriticalType = node instanceof AudioBufferSourceNode || node instanceof OscillatorNode
const wasStarted = existingNode?.started ?? false
if (existingNode) {
if (isCriticalType && wasStarted) {
useNuxtApp().$logger.warn(`[addNode] ⚠️ Node "${type}" ist vom Typ ${node.constructor.name} und wurde bereits gestartet. Ein erneuter Start würde fehlschlagen.`)
}
}
const audioNodeItem: AudioNodeItem = {
type,
node,
started: wasStarted,
previousNodeName: existingNode?.previousNodeName,
nextNodeName: existingNode?.nextNodeName,
}
this.nodes.set(type, audioNodeItem)
return audioNodeItem
},
connectNodes (nodeFrom: AudioNodeItem, nodeTo: AudioNodeItem, channelOutput?: number, channelInput?:number): void {
if (nodeFrom && nodeTo && !channelInput && !channelOutput) {
connectAudioNodes(nodeFrom.node as AudioNode, nodeTo.node as AudioNode)
}
if (nodeFrom && nodeTo && channelOutput && !channelInput) {
connectAudioNodes(nodeFrom.node as AudioNode, nodeTo.node as AudioNode, channelOutput)
}
if (nodeFrom && nodeTo && channelOutput && channelInput) {
connectAudioNodes(nodeFrom.node as AudioNode, nodeTo.node as AudioNode, channelOutput, channelInput)
}
// useNuxtApp().$logger.log('nodeTo tpye: ' + nodeTo.type)
if (nodeTo.type) { nodeFrom.nextNodeName = nodeTo.type }
nodeTo.previousNodeName = nodeFrom.type
},
startAllSources () : void {
const app = useNuxtApp()
// @ts-ignore
const user = useUserStore(app.$pinia)
const userObj = user.user as any
// useNuxtApp().$logger.log({ userObj })
this.nodes.forEach((node: AudioNodeItem, key: string) => {
const userSoundscape = userObj.settings.soundscape === 'Tropics' ? 'tropics' : userObj.settings.soundscape || 'meadow'
if (key === userSoundscape || key === 'noise') {
if (node.node instanceof AudioBufferSourceNode && !node.started) {
this.startBufferSourceNode(node.node)
}
}
})
},
startBufferSourceNode (node: AudioBufferSourceNode) {
// useNuxtApp().$logger.log('startBufferSourceNode ')
this.nodes.forEach((value: AudioNodeItem) => {
if (value.node === node) {
const c = value.node as AudioBufferSourceNode
// useNuxtApp().$logger.log('AudioContext ' + audioContext.state)
c.start(0)
value.started = true
}
})
},
replaceMusicNode (oldNode: AudioNodeItem, newNode: AudioNodeItem):AudioNodeItem {
if (!this.nodes.has(oldNode.type) || !this.nodes.has(newNode.type)) {
// this.$logger.warn('AudioNodes not present in nodes Map')
}
if (oldNode.node ! instanceof AudioBufferSourceNode || oldNode.node ! instanceof AudioBufferSourceNode) {
// createLogger().warn('AudioNodes not AudioBufferSourceNodes')
}
const nextNode = oldNode.nextNodeName ? this.nodes.get(oldNode.nextNodeName) : null
if (!nextNode || (nextNode && nextNode.node ! instanceof ChannelSplitterNode)) {
// createLogger().warn('AudioNodes not ChannelSplitterNodes')
} else {
oldNode.node?.disconnect()
oldNode.started = false
oldNode = {} as AudioNodeItem
if (newNode && newNode.node) {
if (nextNode.type) { newNode.nextNodeName = nextNode.type as string }
newNode.node.connect(nextNode?.node as AudioNode)
return newNode
}
}
return newNode
}
}
})