Initial commit
This commit is contained in:
376
archive/pages/experiments/AllGainPlayPauseSkip.vue
Normal file
376
archive/pages/experiments/AllGainPlayPauseSkip.vue
Normal file
@@ -0,0 +1,376 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Press Space to start, wait few seconds then the sound should start. You will hear the arifacts right after the music begins</h2>
|
||||
<h2>These file contain both patches, use Gainsliders of AudioElement to control volume and allow to skip. No AudioStore used</h2>
|
||||
<div class="rnboplayer">
|
||||
<div v-if="selectedAudioTitle">
|
||||
{{ selectedAudio.title }}
|
||||
<AudioElement
|
||||
:ref="selectedAudio.title"
|
||||
:key="selectedAudio.id"
|
||||
:src="selectedAudio.src"
|
||||
:title="selectedAudio.title"
|
||||
@update:playing="handlePlayingUpdate"
|
||||
@update:volume="changeMusicVolume"
|
||||
>
|
||||
<template #default="{}">
|
||||
<img style="width: 25px" src="~/assets/image/musicicon.svg">
|
||||
</template>
|
||||
</AudioElement>
|
||||
</div>
|
||||
<AudioElement
|
||||
:ref="noise.title"
|
||||
:key="noise.id"
|
||||
:src="noise.src"
|
||||
:title="noise.title"
|
||||
@update:volume="changeNoiseVolume"
|
||||
>
|
||||
<template #default="{}">
|
||||
<img style="width: 25px" src="~/assets/image/noiseicon.svg">
|
||||
</template>
|
||||
<!-- Slot content for AudioElement, if needed -->
|
||||
</AudioElement>
|
||||
|
||||
<button @click="skip">
|
||||
Skip
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AudioElement from '@/components/experiments/AudioElement.vue'
|
||||
import importedMusicPatcher from '@/assets/patch/music_patch.export.json'
|
||||
import importedNoisePatcher from '@/assets/patch/noise_patch.export.json'
|
||||
import { createRNBODevice } from '@/lib/AudioFunctions'
|
||||
|
||||
// import setupNodes from '@/components/Player/Nodes'
|
||||
// import { useAudioStore } from '~/stores/audio'
|
||||
export default {
|
||||
components: {
|
||||
AudioElement
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
audioList: [
|
||||
{ id: 1, title: 'Lagoon', src: window.location.origin + '/sounds/lagoon.m4a' },
|
||||
{ id: 2, title: 'Forest', src: window.location.origin + '/sounds/forest.m4a' },
|
||||
{ id: 3, title: 'Meadow', src: window.location.origin + '/sounds/meadow.m4a' },
|
||||
{ id: 4, title: 'Tropics', src: window.location.origin + '/sounds/tropics.m4a' }
|
||||
],
|
||||
noise: { id: 5, title: 'Noise', src: window.location.origin + '/masking/noise.flac' },
|
||||
selectedAudioTitle: '',
|
||||
currentElement: {},
|
||||
createdNodes: {},
|
||||
musicPatch: importedMusicPatcher,
|
||||
noisePatch: importedNoisePatcher,
|
||||
audioContext: null,
|
||||
playState: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedAudio () {
|
||||
return this.audioList.find(audio => audio.title === this.selectedAudioTitle) || null
|
||||
},
|
||||
isPlaying () {
|
||||
return this.playState
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
this.disconnectNodes()
|
||||
if (this.audioContext) { this.audioContext.close() }
|
||||
this.audioContext = null
|
||||
},
|
||||
mounted () {
|
||||
this.audioContext = new AudioContext()
|
||||
// Example: Select 'Song Three' by default
|
||||
this.selectAudioByTitle(this.audioList[0].title)
|
||||
this.addUserNavigationHandling()
|
||||
this.musicPatch = importedMusicPatcher
|
||||
this.noisePatch = importedNoisePatcher
|
||||
},
|
||||
methods: {
|
||||
checkAudioContext () {
|
||||
// Check if there are any keys (node names) in the createdNodes object
|
||||
if (Object.keys(this.createdNodes).length === 0) {
|
||||
return false
|
||||
}
|
||||
// Check if the 'musicGain' node exists and has a context
|
||||
if (this.createdNodes.musicGain && this.createdNodes.musicGain.context) {
|
||||
const audioContextOfNodes = this.createdNodes.musicGain.context
|
||||
|
||||
// Compare the contexts
|
||||
if (this.audioContext === audioContextOfNodes) {
|
||||
// useNuxtApp().$logger.log('Same context')
|
||||
return true
|
||||
} else {
|
||||
if (audioContextOfNodes.state === 'closed' || audioContextOfNodes.state === 'suspended') {
|
||||
// useNuxtApp().$logger.log('AudioContext of nodes is closed or suspended')
|
||||
return false
|
||||
} else {
|
||||
this.audioContext = audioContextOfNodes
|
||||
}
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
// useNuxtApp().$logger.log('musicGain node does not exist or has no context')
|
||||
return false
|
||||
}
|
||||
},
|
||||
changeNoiseVolume (volume) {
|
||||
if (this.createdNodes.noiseGain) {
|
||||
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(volume, this.createdNodes.noiseGain.context.currentTime + 0.30)
|
||||
this.noiseGain = this.createdNodes.noiseGain.gain.value
|
||||
}
|
||||
},
|
||||
changeMusicVolume (volume) {
|
||||
if (this.createdNodes.musicGain) {
|
||||
this.createdNodes.musicGain.gain.linearRampToValueAtTime(volume, this.createdNodes.musicGain.context.currentTime + 0.30)
|
||||
this.musicGain = this.createdNodes.musicGain.gain.value
|
||||
}
|
||||
},
|
||||
addUserNavigationHandling () {
|
||||
if ('mediaSession' in navigator) {
|
||||
// Set the handler for the next track action
|
||||
navigator.mediaSession.setActionHandler('nexttrack', () => {
|
||||
this.skip('next')
|
||||
})
|
||||
|
||||
// Set the handler for the previous track action
|
||||
navigator.mediaSession.setActionHandler('previoustrack', () => {
|
||||
this.skip('previous')
|
||||
})
|
||||
}
|
||||
},
|
||||
getSong (currentTitle, direction) {
|
||||
const index = this.audioList.findIndex(song => song.title === currentTitle)
|
||||
let adjacentIndex = index + (direction === 'next' ? 1 : -1)
|
||||
// Loop back to the first song if 'next' goes beyond the last index
|
||||
if (adjacentIndex >= this.audioList.length) {
|
||||
adjacentIndex = 0
|
||||
} else if (adjacentIndex < 0) {
|
||||
adjacentIndex = this.audioList.length - 1
|
||||
}
|
||||
return this.audioList[adjacentIndex]
|
||||
},
|
||||
skip (direction) {
|
||||
const nextSong = this.getSong(this.selectedAudioTitle, direction)
|
||||
this.selectAudioByTitle(nextSong.title)
|
||||
},
|
||||
updateCurrentElement (title) {
|
||||
this.currentElement = this.audioList.find(e => e.title === title)
|
||||
},
|
||||
selectAudioByTitle (title) {
|
||||
this.selectedAudioTitle = title
|
||||
// const audioElement = this.audioList.find(e => e.title === this.selectedAudioTitle)
|
||||
this.currentElement = this.audioList[this.audioList.findIndex(e => e.title === title)]
|
||||
// useNuxtApp().$logger.log('currentElement: ', this.selectedAudioTitle)
|
||||
},
|
||||
areAllNodesAvailable () {
|
||||
// List of required node keys as you've named them in this.createdNodes
|
||||
const requiredNodes = [
|
||||
'musicSource', 'musicSplitter', 'microphoneSource',
|
||||
'musicDevice', 'outputSplitter', 'musicGain',
|
||||
'noiseSource', 'noiseSplitter', 'noiseDevice',
|
||||
'noiseGain', 'merger'
|
||||
]
|
||||
|
||||
// Check if each required node exists and is not undefined in this.createdNodes
|
||||
return requiredNodes.every(nodeKey => this.createdNodes[nodeKey] !== undefined)
|
||||
},
|
||||
connectNodes () {
|
||||
// Destructure for easier access
|
||||
const {
|
||||
musicSource, musicSplitter, microphoneSource,
|
||||
musicDevice, outputSplitter, musicGain,
|
||||
noiseSource, noiseSplitter, noiseDevice,
|
||||
noiseGain, merger
|
||||
} = this.createdNodes
|
||||
// Assuming all nodes are created and references to them are correct
|
||||
|
||||
// Connect music source to splitter (Stereo to 2 Channels)
|
||||
musicSource.connect(musicSplitter) // 2 channels: L, R
|
||||
|
||||
// Connect microphone to musicDevice input 0 (Mono)
|
||||
microphoneSource.connect(musicDevice, 0, 0) // 1 channel: Microphone
|
||||
|
||||
// Connect musicSplitter to musicDevice inputs 1 and 2 (Stereo)
|
||||
musicSplitter.connect(musicDevice, 0, 1) // 1 channel: Left
|
||||
musicSplitter.connect(musicDevice, 1, 2) // 1 channel: Right
|
||||
|
||||
// Connect musicDevice to outputSplitter (Stereo out)
|
||||
musicDevice.connect(outputSplitter) // Assuming musicDevice outputs stereo
|
||||
|
||||
// Optionally connect musicDevice to musicGain for additional gain control (Stereo)
|
||||
musicDevice.connect(musicGain) // Assuming musicDevice outputs stereo, connected to both channels of musicGain
|
||||
|
||||
// Connect noise source to noiseSplitter (Stereo to 2 Channels)
|
||||
noiseSource.connect(noiseSplitter) // 2 channels: L, R
|
||||
|
||||
// Connect microphone to noiseDevice input 0 (Mono)
|
||||
microphoneSource.connect(noiseDevice, 0, 0) // 1 channel: Microphone
|
||||
|
||||
// Connect noiseSplitter to noiseDevice inputs 1 and 2 (Stereo)
|
||||
noiseSplitter.connect(noiseDevice, 0, 1) // 1 channel: Left
|
||||
noiseSplitter.connect(noiseDevice, 1, 2) // 1 channel: Right
|
||||
|
||||
// Connect outputSplitter to noiseDevice inputs 3 and 4 (Stereo from musicDevice)
|
||||
outputSplitter.connect(noiseDevice, 0, 3) // 1 channel: Left from musicDevice
|
||||
outputSplitter.connect(noiseDevice, 1, 4) // 1 channel: Right from musicDevice
|
||||
|
||||
// Assuming noiseDevice outputs stereo, connect to noiseGain (Stereo)
|
||||
noiseDevice.connect(noiseGain) // Assuming noiseDevice outputs stereo, connected to both channels of noiseGain
|
||||
|
||||
// Assuming you want to merge and output both processed signals (Stereo from both musicGain and noiseGain)
|
||||
// Connect noiseGain to the first two inputs of the merger (Stereo)
|
||||
noiseGain.connect(merger, 0, 0) // 1 channel: Left
|
||||
noiseGain.connect(merger, 0, 1) // 1 channel: Right
|
||||
|
||||
// Connect musicGain to the last two inputs of the merger (Stereo)
|
||||
musicGain.connect(merger, 0, 2) // 1 channel: Left
|
||||
musicGain.connect(merger, 0, 3) // 1 channel: Right
|
||||
|
||||
// Finally, connect the merger to the audio context's destination (Stereo to output)
|
||||
merger.connect(merger.context.destination)
|
||||
},
|
||||
handlePlayingUpdate (isPlaying) {
|
||||
const ContextClass = (window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext || window.msAudioContext)
|
||||
const audioContext = this.audioContext ? this.audioContext : new ContextClass()
|
||||
|
||||
let microphoneSource = null
|
||||
// useNuxtApp().$logger.log('Handling playing update = ' + isPlaying) // true when playing
|
||||
if (isPlaying) {
|
||||
// Web Audio API is available.
|
||||
// const musicAudioComponent = this.$refs[this.currentElement.title].audioElement
|
||||
if (this.areAllNodesAvailable() && this.checkAudioContext()) {
|
||||
this.disconnectNodes()
|
||||
const musicAudioElement = this.$refs[this.currentElement.title].$refs.audioElement
|
||||
const musicGainValue = this.createdNodes.musicGain.value
|
||||
const noiseGainValue = this.createdNodes.noiseGain.value
|
||||
// Prepare the audio elements (unmute)
|
||||
this.$refs[this.currentElement.title].$refs.audioElement.muted = false
|
||||
this.$refs.Noise.$refs.audioElement.muted = false
|
||||
// replace music node because all Nodes are there
|
||||
this.createdNodes.musicSource = null
|
||||
this.createdNodes.musicSource = audioContext.createMediaElementSource(musicAudioElement)
|
||||
this.connectNodes()
|
||||
|
||||
if (musicGainValue > 0 && musicGainValue <= 1.0) {
|
||||
this.createdNodes.musicGain.gain.linearRampToValueAtTime(musicGainValue || 1.0, audioContext.currentTime + 2)
|
||||
}
|
||||
if (noiseGainValue > 0 && noiseGainValue <= 1.0) {
|
||||
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(noiseGainValue || 1.0, audioContext.currentTime + 3)
|
||||
}
|
||||
} else {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false,
|
||||
autoGainControl: false
|
||||
},
|
||||
video: false
|
||||
})
|
||||
.then((micStream) => {
|
||||
microphoneSource = this.audioContext.createMediaStreamSource(micStream)
|
||||
this.createdNodes.microphoneSource ||= microphoneSource
|
||||
// Return both audioContext and microphoneSource to the next link in the chain
|
||||
return { audioContext, microphoneSource }
|
||||
})
|
||||
.then(({ audioContext, microphoneSource }) => {
|
||||
// First RNBO device creation
|
||||
return createRNBODevice(audioContext, this.noisePatch).then((noiseRNBODevice) => {
|
||||
this.createdNodes.noiseRNBODevice ||= noiseRNBODevice
|
||||
// Return al.then(() => {hen(() => necessary objects for the next step
|
||||
return { audioContext, microphoneSource, noiseRNBODevice }
|
||||
})
|
||||
})
|
||||
.then(({ audioContext, microphoneSource, noiseRNBODevice }) => {
|
||||
// Second RNBO device creation
|
||||
return createRNBODevice(audioContext, this.musicPatch).then((musicRNBODevice) => {
|
||||
// Return all necessary objects for the final setup
|
||||
this.createdNodes.musicRNBODevice ||= musicRNBODevice
|
||||
return { audioContext, microphoneSource, noiseRNBODevice, musicRNBODevice }
|
||||
})
|
||||
}).then(({ audioContext, microphoneSource, noiseRNBODevice, musicRNBODevice }) => {
|
||||
const musicElement = this.$refs[this.currentElement.title]
|
||||
const musicAudioElement = musicElement.$refs.audioElement
|
||||
|
||||
// Ensure this.createdNodes is initialized
|
||||
this.createdNodes ||= {}
|
||||
// Assign nodes if they don't exist
|
||||
this.createdNodes.microphoneSource ||= microphoneSource
|
||||
this.createdNodes.musicDevice ||= musicRNBODevice.node
|
||||
this.createdNodes.noiseDevice ||= noiseRNBODevice.node
|
||||
this.createdNodes.noiseSource ||= audioContext.createMediaElementSource(this.$refs.Noise.$refs.audioElement)
|
||||
this.createdNodes.musicSource ||= audioContext.createMediaElementSource(musicAudioElement)
|
||||
this.createdNodes.musicSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.noiseSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.outputSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.musicGain ||= audioContext.createGain()
|
||||
this.createdNodes.noiseGain ||= audioContext.createGain()
|
||||
this.createdNodes.merger ||= audioContext.createChannelMerger(4)
|
||||
|
||||
musicAudioElement.muted = false
|
||||
this.$refs.Noise.$refs.audioElement.muted = false
|
||||
this.createdNodes.musicGain.gain.value = 0.0001
|
||||
this.createdNodes.noiseGain.gain.value = 0.0001 // macht nichts
|
||||
this.connectNodes()
|
||||
setTimeout(() => {
|
||||
this.playState = true
|
||||
this.createdNodes.musicGain.gain.exponentialRampToValueAtTime(1, audioContext.currentTime + 3)
|
||||
this.createdNodes.noiseGain.gain.exponentialRampToValueAtTime(1, audioContext.currentTime + 4)
|
||||
}, 150)
|
||||
})
|
||||
.catch((_error) => {
|
||||
this.disconnectNodes()
|
||||
|
||||
if (this.audioContext) {
|
||||
this.audioContext.destination.disconnect()
|
||||
// audioContext.destination = null
|
||||
}
|
||||
|
||||
// useNuxtApp().$logger.error('Error setting up audio:', error)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.disconnectNodes()
|
||||
const newAudioContext = new AudioContext()
|
||||
this.audioContext.close()
|
||||
this.audioContext = newAudioContext
|
||||
}
|
||||
},
|
||||
disconnectNodes () {
|
||||
// Ensure this.createdNodes is defined and is an object
|
||||
if (typeof this.createdNodes === 'object' && this.createdNodes !== null) {
|
||||
Object.values(this.createdNodes).forEach((node) => {
|
||||
// Check if the node exists and has a disconnect method
|
||||
if (node && typeof node.disconnect === 'function') {
|
||||
node.disconnect()
|
||||
}
|
||||
// Additional handling for specific types of nodes, if necessary
|
||||
// For example, stopping an OscillatorNode
|
||||
if (node instanceof OscillatorNode) {
|
||||
node.stop()
|
||||
}
|
||||
})
|
||||
this.playState = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.rnboplayer {
|
||||
position: fixed;
|
||||
width: 220px; /* Or specify a fixed width like 220px if you prefer */
|
||||
max-width: 220px; /* This line might be redundant depending on your width strategy */
|
||||
height: 100px;
|
||||
display: inline-grid;
|
||||
z-index: 2;
|
||||
bottom: 11%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
7
archive/pages/experiments/AudioElementManager2.vue
Normal file
7
archive/pages/experiments/AudioElementManager2.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<AudioElementManager />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AudioElementManager from '~/archive/components/tests/AudioElementManager.vue'
|
||||
</script>
|
365
archive/pages/experiments/audioElementManager.vue
Normal file
365
archive/pages/experiments/audioElementManager.vue
Normal file
@@ -0,0 +1,365 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>FireFox: wait its loading: few seconds then the sound should start. You will hear the arifacts right after the music begins</h2>
|
||||
<h2>These file contain both patches, use Gainsliders of AudioElement to control volume and allow to skip. AudioStore used</h2>
|
||||
<div class="rnboplayer">
|
||||
<div v-if="selectedAudioTitle">
|
||||
{{ selectedAudio.title }}
|
||||
<AudioElement
|
||||
:ref="selectedAudio.title"
|
||||
:key="selectedAudio.id"
|
||||
:src="selectedAudio.src"
|
||||
:title="selectedAudio.title"
|
||||
@update:playing="handlePlayingUpdate"
|
||||
@update:volume="changeMusicVolume"
|
||||
>
|
||||
<template #default="{}" />
|
||||
<!-- Slot content for AudioElement, if needed -->
|
||||
</AudioElement>
|
||||
</div>
|
||||
<AudioElement
|
||||
:ref="noise.title"
|
||||
:key="noise.id"
|
||||
:src="noise.src"
|
||||
:title="noise.title"
|
||||
@update:volume="changeNoiseVolume"
|
||||
>
|
||||
<template #default="{}" />
|
||||
<!-- Slot content for AudioElement, if needed -->
|
||||
</AudioElement>
|
||||
|
||||
<button @click="skip">
|
||||
Skip
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AudioElement from '@/components/experiments/AudioElement.vue'
|
||||
import importedMusicPatcher from '@/assets/patch/music_patch.export.json'
|
||||
import importedNoisePatcher from '@/assets/patch/noise_patch.export.json'
|
||||
import { createRNBODevice } from '@/lib/AudioFunctions'
|
||||
|
||||
// import setupNodes from '@/components/Player/Nodes'
|
||||
// import { useAudioStore } from '~/stores/audio'
|
||||
export default {
|
||||
components: {
|
||||
AudioElement
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
audioList: [
|
||||
{ id: 1, title: 'Lagoon', src: window.location.origin + '/sounds/lagoon.m4a' },
|
||||
{ id: 2, title: 'Forest', src: window.location.origin + '/sounds/forest.m4a' },
|
||||
{ id: 3, title: 'Meadow', src: window.location.origin + '/sounds/meadow.m4a' },
|
||||
{ id: 4, title: 'Tropics', src: window.location.origin + '/sounds/tropics.m4a' }
|
||||
],
|
||||
noise: { id: 5, title: 'Noise', src: window.location.origin + '/masking/noise.flac' },
|
||||
selectedAudioTitle: '',
|
||||
currentElement: {},
|
||||
createdNodes: {},
|
||||
musicPatch: importedMusicPatcher,
|
||||
noisePatch: importedNoisePatcher,
|
||||
audioContext: null,
|
||||
playState: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedAudio () {
|
||||
return this.audioList.find(audio => audio.title === this.selectedAudioTitle) || null
|
||||
},
|
||||
isPlaying () {
|
||||
return this.playState
|
||||
},
|
||||
checkContextCompatibility () {
|
||||
// Assume createdNodes is an object where each property is a node
|
||||
const nodes = Object.values(this.createdNodes)
|
||||
const incompatible = nodes.filter(node => node.context !== this.audioContext)
|
||||
if (nodes.length === nodes.length - incompatible.length) { return true }
|
||||
// useNuxtApp().$logger.log(incompatible.length + '/' + nodes.length + ' sind inkompatibel')
|
||||
this.handleIncompatibeError(incompatible)
|
||||
return false
|
||||
}
|
||||
},
|
||||
handleIncompatibeError (incompatible) {
|
||||
if (incompatible) {
|
||||
const node = incompatible.pop()
|
||||
if (node.context !== this.createdNodes.musicSplitter.context) {
|
||||
const audioContext = this.createdNodes.musicSplitter.context
|
||||
this.createdNodes.musicSource ||= audioContext.createMediaElementSource(musicAudioElement)
|
||||
}
|
||||
} else {
|
||||
// useNuxtApp().$logger.log('no error to solve')
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
this.disconnectNodes()
|
||||
if (this.audioContext) { this.audioContext.close() }
|
||||
this.audioContext = null
|
||||
},
|
||||
mounted () {
|
||||
this.audioContext = new AudioContext()
|
||||
// Example: Select 'Song Three' by default
|
||||
this.selectAudioByTitle(this.audioList[0].title)
|
||||
this.addUserNavigationHandling()
|
||||
this.musicPatch = importedMusicPatcher
|
||||
this.noisePatch = importedNoisePatcher
|
||||
},
|
||||
methods: {
|
||||
|
||||
changeNoiseVolume (volume) {
|
||||
if (this.createdNodes.noiseGain) {
|
||||
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(volume, this.createdNodes.noiseGain.context.currentTime + 0.30)
|
||||
this.noiseGain = this.createdNodes.noiseGain.gain.value
|
||||
}
|
||||
},
|
||||
changeMusicVolume (volume) {
|
||||
if (this.createdNodes.musicGain) {
|
||||
this.createdNodes.musicGain.gain.linearRampToValueAtTime(volume, this.createdNodes.musicGain.context.currentTime + 0.30)
|
||||
this.musicGain = this.createdNodes.musicGain.gain.value
|
||||
}
|
||||
},
|
||||
addUserNavigationHandling () {
|
||||
if ('mediaSession' in navigator) {
|
||||
// Set the handler for the next track action
|
||||
navigator.mediaSession.setActionHandler('nexttrack', () => {
|
||||
this.skip('next')
|
||||
})
|
||||
|
||||
// Set the handler for the previous track action
|
||||
navigator.mediaSession.setActionHandler('previoustrack', () => {
|
||||
this.skip('previous')
|
||||
})
|
||||
}
|
||||
},
|
||||
getSong (currentTitle, direction) {
|
||||
const index = this.audioList.findIndex(song => song.title === currentTitle)
|
||||
let adjacentIndex = index + (direction === 'next' ? 1 : -1)
|
||||
// Loop back to the first song if 'next' goes beyond the last index
|
||||
if (adjacentIndex >= this.audioList.length) {
|
||||
adjacentIndex = 0
|
||||
} else if (adjacentIndex < 0) {
|
||||
adjacentIndex = this.audioList.length - 1
|
||||
}
|
||||
return this.audioList[adjacentIndex]
|
||||
},
|
||||
skip (direction) {
|
||||
const nextSong = this.getSong(this.selectedAudioTitle, direction)
|
||||
this.selectAudioByTitle(nextSong.title)
|
||||
},
|
||||
updateCurrentElement (title) {
|
||||
this.currentElement = this.audioList.find(e => e.title === title)
|
||||
},
|
||||
selectAudioByTitle (title) {
|
||||
this.selectedAudioTitle = title
|
||||
// const audioElement = this.audioList.find(e => e.title === this.selectedAudioTitle)
|
||||
this.currentElement = this.audioList[this.audioList.findIndex(e => e.title === title)]
|
||||
// useNuxtApp().$logger.log('currentElement: ', this.selectedAudioTitle)
|
||||
},
|
||||
areAllNodesAvailable () {
|
||||
// List of required node keys as you've named them in this.createdNodes
|
||||
const requiredNodes = [
|
||||
'musicSource', 'musicSplitter', 'microphoneSource',
|
||||
'musicDevice', 'outputSplitter', 'musicGain',
|
||||
'noiseSource', 'noiseSplitter', 'noiseDevice',
|
||||
'noiseGain', 'merger'
|
||||
]
|
||||
|
||||
// Check if each required node exists and is not undefined in this.createdNodes
|
||||
return requiredNodes.every(nodeKey => this.createdNodes[nodeKey] !== undefined)
|
||||
},
|
||||
connectNodes () {
|
||||
// Destructure for easier access
|
||||
const {
|
||||
musicSource, musicSplitter, microphoneSource,
|
||||
musicDevice, outputSplitter, musicGain,
|
||||
noiseSource, noiseSplitter, noiseDevice,
|
||||
noiseGain, merger
|
||||
} = this.createdNodes
|
||||
// Assuming all nodes are created and references to them are correct
|
||||
|
||||
// Connect music source to splitter (Stereo to 2 Channels)
|
||||
musicSource.connect(musicSplitter) // 2 channels: L, R
|
||||
|
||||
// Connect microphone to musicDevice input 0 (Mono)
|
||||
microphoneSource.connect(musicDevice, 0, 0) // 1 channel: Microphone
|
||||
|
||||
// Connect musicSplitter to musicDevice inputs 1 and 2 (Stereo)
|
||||
musicSplitter.connect(musicDevice, 0, 1) // 1 channel: Left
|
||||
musicSplitter.connect(musicDevice, 1, 2) // 1 channel: Right
|
||||
|
||||
// Connect musicDevice to outputSplitter (Stereo out)
|
||||
musicDevice.connect(outputSplitter) // Assuming musicDevice outputs stereo
|
||||
|
||||
// Optionally connect musicDevice to musicGain for additional gain control (Stereo)
|
||||
musicDevice.connect(musicGain) // Assuming musicDevice outputs stereo, connected to both channels of musicGain
|
||||
|
||||
// Connect noise source to noiseSplitter (Stereo to 2 Channels)
|
||||
noiseSource.connect(noiseSplitter) // 2 channels: L, R
|
||||
|
||||
// Connect microphone to noiseDevice input 0 (Mono)
|
||||
microphoneSource.connect(noiseDevice, 0, 0) // 1 channel: Microphone
|
||||
|
||||
// Connect noiseSplitter to noiseDevice inputs 1 and 2 (Stereo)
|
||||
noiseSplitter.connect(noiseDevice, 0, 1) // 1 channel: Left
|
||||
noiseSplitter.connect(noiseDevice, 1, 2) // 1 channel: Right
|
||||
|
||||
// Connect outputSplitter to noiseDevice inputs 3 and 4 (Stereo from musicDevice)
|
||||
outputSplitter.connect(noiseDevice, 0, 3) // 1 channel: Left from musicDevice
|
||||
outputSplitter.connect(noiseDevice, 1, 4) // 1 channel: Right from musicDevice
|
||||
|
||||
// Assuming noiseDevice outputs stereo, connect to noiseGain (Stereo)
|
||||
noiseDevice.connect(noiseGain) // Assuming noiseDevice outputs stereo, connected to both channels of noiseGain
|
||||
|
||||
// Assuming you want to merge and output both processed signals (Stereo from both musicGain and noiseGain)
|
||||
// Connect noiseGain to the first two inputs of the merger (Stereo)
|
||||
noiseGain.connect(merger, 0, 0) // 1 channel: Left
|
||||
noiseGain.connect(merger, 0, 1) // 1 channel: Right
|
||||
|
||||
// Connect musicGain to the last two inputs of the merger (Stereo)
|
||||
musicGain.connect(merger, 0, 2) // 1 channel: Left
|
||||
musicGain.connect(merger, 0, 3) // 1 channel: Right
|
||||
|
||||
// Finally, connect the merger to the audio context's destination (Stereo to output)
|
||||
merger.connect(merger.context.destination)
|
||||
},
|
||||
handlePlayingUpdate (isPlaying) {
|
||||
const ContextClass = (window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext || window.msAudioContext)
|
||||
const audioContext = this.audioContext ? this.audioContext : new ContextClass()
|
||||
let microphoneSource = null
|
||||
// useNuxtApp().$logger.log('Handling playing update = ' + isPlaying) // true when playing
|
||||
if (isPlaying) {
|
||||
// Web Audio API is available.
|
||||
// const musicAudioComponent = this.$refs[this.currentElement.title].audioElement
|
||||
if (this.areAllNodesAvailable()) {
|
||||
this.disconnectNodes()
|
||||
const musicAudioElement = this.$refs[this.currentElement.title].$refs.audioElement
|
||||
const musicGainValue = this.createdNodes.musicGain.value
|
||||
const noiseGainValue = this.createdNodes.noiseGain.value
|
||||
// Prepare the audio elements (unmute)
|
||||
this.$refs[this.currentElement.title].$refs.audioElement.muted = false
|
||||
this.$refs.Noise.$refs.audioElement.muted = false
|
||||
// replace music node because all Nodes are there
|
||||
this.createdNodes.musicSource = null
|
||||
this.createdNodes.musicSource = audioContext.createMediaElementSource(musicAudioElement)
|
||||
this.connectNodes()
|
||||
|
||||
if (musicGainValue > 0 && musicGainValue <= 1.0) {
|
||||
this.createdNodes.musicGain.gain.linearRampToValueAtTime(musicGainValue || 1.0, audioContext.currentTime + 2)
|
||||
}
|
||||
if (noiseGainValue > 0 && noiseGainValue <= 1.0) {
|
||||
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(noiseGainValue || 1.0, audioContext.currentTime + 3)
|
||||
}
|
||||
} else {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false,
|
||||
autoGainControl: false
|
||||
},
|
||||
video: false
|
||||
})
|
||||
.then((micStream) => {
|
||||
microphoneSource = this.audioContext.createMediaStreamSource(micStream)
|
||||
this.createdNodes.microphoneSource ||= microphoneSource
|
||||
// Return both audioContext and microphoneSource to the next link in the chain
|
||||
return { audioContext, microphoneSource }
|
||||
})
|
||||
.then(({ audioContext, microphoneSource }) => {
|
||||
// First RNBO device creation
|
||||
return createRNBODevice(audioContext, this.noisePatch).then((noiseRNBODevice) => {
|
||||
this.createdNodes.noiseRNBODevice ||= noiseRNBODevice
|
||||
// Return al.then(() => {hen(() => necessary objects for the next step
|
||||
return { audioContext, microphoneSource, noiseRNBODevice }
|
||||
})
|
||||
})
|
||||
.then(({ audioContext, microphoneSource, noiseRNBODevice }) => {
|
||||
// Second RNBO device creation
|
||||
return createRNBODevice(audioContext, this.musicPatch).then((musicRNBODevice) => {
|
||||
// Return all necessary objects for the final setup
|
||||
this.createdNodes.musicRNBODevice ||= musicRNBODevice
|
||||
return { audioContext, microphoneSource, noiseRNBODevice, musicRNBODevice }
|
||||
})
|
||||
}).then(({ audioContext, microphoneSource, noiseRNBODevice, musicRNBODevice }) => {
|
||||
const musicElement = this.$refs[this.currentElement.title]
|
||||
const musicAudioElement = musicElement.$refs.audioElement
|
||||
|
||||
// Ensure this.createdNodes is initialized
|
||||
this.createdNodes ||= {}
|
||||
// Assign nodes if they don't exist
|
||||
this.createdNodes.microphoneSource ||= microphoneSource
|
||||
this.createdNodes.musicDevice ||= musicRNBODevice.node
|
||||
this.createdNodes.noiseDevice ||= noiseRNBODevice.node
|
||||
this.createdNodes.noiseSource ||= audioContext.createMediaElementSource(this.$refs.Noise.$refs.audioElement)
|
||||
this.createdNodes.musicSource ||= audioContext.createMediaElementSource(musicAudioElement)
|
||||
this.createdNodes.musicSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.noiseSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.outputSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.musicGain ||= audioContext.createGain()
|
||||
this.createdNodes.noiseGain ||= audioContext.createGain()
|
||||
this.createdNodes.merger ||= audioContext.createChannelMerger(4)
|
||||
|
||||
musicAudioElement.muted = false
|
||||
this.$refs.Noise.$refs.audioElement.muted = false
|
||||
this.createdNodes.musicGain.gain.value = 0.0001
|
||||
this.createdNodes.noiseGain.gain.value = 0.0001 // macht nichts
|
||||
this.connectNodes()
|
||||
setTimeout(() => {
|
||||
this.playState = true
|
||||
this.createdNodes.musicGain.gain.exponentialRampToValueAtTime(1, audioContext.currentTime + 3)
|
||||
this.createdNodes.noiseGain.gain.exponentialRampToValueAtTime(1, audioContext.currentTime + 4)
|
||||
}, 150)
|
||||
})
|
||||
.catch((_error) => {
|
||||
this.disconnectNodes()
|
||||
|
||||
if (this.audioContext) {
|
||||
this.audioContext.destination.disconnect()
|
||||
// audioContext.destination = null
|
||||
}
|
||||
this.$toast.error('Oouh sorry! Error while setting up audio, please reload.')
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.disconnectNodes()
|
||||
const newAudioContext = new AudioContext()
|
||||
this.audioContext.close()
|
||||
this.audioContext = newAudioContext
|
||||
}
|
||||
},
|
||||
disconnectNodes () {
|
||||
// Ensure this.createdNodes is defined and is an object
|
||||
if (typeof this.createdNodes === 'object' && this.createdNodes !== null) {
|
||||
Object.values(this.createdNodes).forEach((node) => {
|
||||
// Check if the node exists and has a disconnect method
|
||||
if (node && typeof node.disconnect === 'function') {
|
||||
node.disconnect()
|
||||
}
|
||||
// Additional handling for specific types of nodes, if necessary
|
||||
// For example, stopping an OscillatorNode
|
||||
if (node instanceof OscillatorNode) {
|
||||
node.stop()
|
||||
}
|
||||
})
|
||||
this.playState = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.rnboplayer {
|
||||
position: fixed;
|
||||
width: 220px; /* Or specify a fixed width like 220px if you prefer */
|
||||
max-width: 220px; /* This line might be redundant depending on your width strategy */
|
||||
height: 100px;
|
||||
display: inline-grid;
|
||||
z-index: 2;
|
||||
bottom: 11%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
399
archive/pages/experiments/audioElementManagerAudioStore.vue
Normal file
399
archive/pages/experiments/audioElementManagerAudioStore.vue
Normal file
@@ -0,0 +1,399 @@
|
||||
<template>
|
||||
<div>
|
||||
<KeyboardPlayHandler />
|
||||
<h2>Press Space to start, wait few seconds then the sound should start. You will hear the arifacts right after the music begins</h2>
|
||||
<h2>These file contain both patches, use Gainsliders of AudioElement to control volume and allow to skip. AudioStore used for shared AudioContext</h2>
|
||||
<div class="rnboplayer">
|
||||
<div v-if="selectedAudioTitle">
|
||||
{{ selectedAudio.title }}
|
||||
<AudioElement
|
||||
:ref="selectedAudio.title"
|
||||
:key="selectedAudio.id"
|
||||
:src="selectedAudio.src"
|
||||
:title="selectedAudio.title"
|
||||
@update:playing="handlePlayingUpdate"
|
||||
@update:volume="changeMusicVolume"
|
||||
>
|
||||
<template #default="{}" />
|
||||
<!-- Slot content for AudioElement, if needed -->
|
||||
</AudioElement>
|
||||
</div>
|
||||
<AudioElement
|
||||
:ref="noise.title"
|
||||
:key="noise.id"
|
||||
:src="noise.src"
|
||||
:title="noise.title"
|
||||
@update:volume="changeNoiseVolume"
|
||||
>
|
||||
<template #default="{}" />
|
||||
<!-- Slot content for AudioElement, if needed -->
|
||||
</AudioElement>
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<button @click="skip">
|
||||
Skip
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button @click="resumeContext">
|
||||
Resume Context
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button @click="stopContext">
|
||||
Suspend Context
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AudioElement from '@/components/experiments/AudioElement.vue'
|
||||
import importedMusicPatcher from '@/assets/patch/music_patch.export.json'
|
||||
import importedNoisePatcher from '@/assets/patch/noise_patch.export.json'
|
||||
import { createRNBODevice } from '@/lib/AudioFunctions'
|
||||
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
|
||||
// import setupNodes from '@/components/Player/Nodes'
|
||||
import { useAudioStore } from '~/stores/audio'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AudioElement,
|
||||
KeyboardPlayHandler
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
audioList: [
|
||||
{ id: 1, title: 'Lagoon', src: window.location.origin + '/sounds/lagoon.m4a' },
|
||||
{ id: 2, title: 'Forest', src: window.location.origin + '/sounds/forest.m4a' },
|
||||
{ id: 3, title: 'Meadow', src: window.location.origin + '/sounds/meadow.m4a' },
|
||||
{ id: 4, title: 'Tropics', src: window.location.origin + '/sounds/tropics.m4a' }
|
||||
],
|
||||
noise: { id: 5, title: 'Noise', src: window.location.origin + useRuntimeConfig().public.tracks.masking_src_mp3 },
|
||||
selectedAudioTitle: '',
|
||||
currentElement: {},
|
||||
createdNodes: {},
|
||||
musicPatch: importedMusicPatcher,
|
||||
noisePatch: importedNoisePatcher,
|
||||
audioContext: null,
|
||||
playState: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedAudio () {
|
||||
return this.audioList.find(audio => audio.title === this.selectedAudioTitle) || null
|
||||
},
|
||||
checkContextCompatibility () {
|
||||
// Assume createdNodes is an object where each property is a node
|
||||
const nodes = Object.values(this.createdNodes)
|
||||
const incompatible = nodes.filter(node => node.context !== this.audioContext)
|
||||
if (nodes.length === nodes.length - incompatible.length) { return true }
|
||||
// // useNuxtApp().$logger.log(incompatible.length + '/' + nodes.length + ' sind inkompatibel')
|
||||
this.handleIncompatibeError(incompatible)
|
||||
return false
|
||||
},
|
||||
isPlaying () {
|
||||
return this.playState
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
if (this.currentElement) {
|
||||
this.currentElement.pause()
|
||||
this.currentElement.src = ''
|
||||
this.currentElement.load()
|
||||
this.currentElement = null
|
||||
}
|
||||
this.disconnectNodes()
|
||||
if (this.audioContext) { this.audioContext.close() }
|
||||
this.audioContext = null
|
||||
},
|
||||
mounted () {
|
||||
this.audioContext = useAudioStore().getContext()
|
||||
// Example: Select 'Song Three' by default
|
||||
this.selectAudioByTitle(this.audioList[0].title)
|
||||
this.addUserNavigationHandling()
|
||||
this.musicPatch = importedMusicPatcher
|
||||
this.noisePatch = importedNoisePatcher
|
||||
},
|
||||
methods: {
|
||||
|
||||
changeNoiseVolume (volume) {
|
||||
if (this.createdNodes.noiseGain) {
|
||||
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(volume, this.createdNodes.noiseGain.context.currentTime + 0.30)
|
||||
this.noiseGain = this.createdNodes.noiseGain.gain.value
|
||||
}
|
||||
},
|
||||
changeMusicVolume (volume) {
|
||||
if (this.createdNodes.musicGain) {
|
||||
this.createdNodes.musicGain.gain.linearRampToValueAtTime(volume, this.createdNodes.musicGain.context.currentTime + 0.30)
|
||||
this.musicGain = this.createdNodes.musicGain.gain.value
|
||||
}
|
||||
},
|
||||
addUserNavigationHandling () {
|
||||
if ('mediaSession' in navigator) {
|
||||
// Set the handler for the next track action
|
||||
navigator.mediaSession.setActionHandler('nexttrack', () => {
|
||||
this.skip('next')
|
||||
})
|
||||
|
||||
// Set the handler for the previous track action
|
||||
navigator.mediaSession.setActionHandler('previoustrack', () => {
|
||||
this.skip('previous')
|
||||
})
|
||||
}
|
||||
},
|
||||
getSong (currentTitle, direction) {
|
||||
const index = this.audioList.findIndex(song => song.title === currentTitle)
|
||||
let adjacentIndex = index + (direction === 'next' ? 1 : -1)
|
||||
// Loop back to the first song if 'next' goes beyond the last index
|
||||
if (adjacentIndex >= this.audioList.length) {
|
||||
adjacentIndex = 0
|
||||
} else if (adjacentIndex < 0) {
|
||||
adjacentIndex = this.audioList.length - 1
|
||||
}
|
||||
return this.audioList[adjacentIndex]
|
||||
},
|
||||
skip (direction) {
|
||||
const nextSong = this.getSong(this.selectedAudioTitle, direction)
|
||||
this.selectAudioByTitle(nextSong.title)
|
||||
},
|
||||
resumeContext () {
|
||||
if (this.audioContext.state === 'suspended') { this.audioContext.resume() } else {
|
||||
// useNuxtApp().$logger.log('already resumed?')
|
||||
}
|
||||
},
|
||||
stopContext () {
|
||||
if (this.audioContext.state === 'running') { this.audioContext.suspend() } else {
|
||||
// useNuxtApp().$logger.log('already suspended')
|
||||
}
|
||||
},
|
||||
updateCurrentElement (title) {
|
||||
this.currentElement = this.audioList.find(e => e.title === title)
|
||||
},
|
||||
selectAudioByTitle (title) {
|
||||
this.selectedAudioTitle = title
|
||||
// const audioElement = this.audioList.find(e => e.title === this.selectedAudioTitle)
|
||||
this.currentElement = this.audioList[this.audioList.findIndex(e => e.title === title)]
|
||||
// // useNuxtApp().$logger.log('currentElement: ', this.selectedAudioTitle)
|
||||
},
|
||||
areAllNodesAvailable () {
|
||||
// List of required node keys as you've named them in this.createdNodes
|
||||
const requiredNodes = [
|
||||
'musicSource', 'musicSplitter', 'microphoneSource',
|
||||
'musicDevice', 'outputSplitter', 'musicGain',
|
||||
'noiseSource', 'noiseSplitter', 'noiseDevice',
|
||||
'noiseGain', 'merger'
|
||||
]
|
||||
|
||||
// Check if each required node exists and is not undefined in this.createdNodes
|
||||
return requiredNodes.every(nodeKey => this.createdNodes[nodeKey] !== undefined)
|
||||
},
|
||||
handleIncompatibeError (incompatible) {
|
||||
if (incompatible) {
|
||||
const node = incompatible.pop()
|
||||
if (node.context !== this.createdNodes.musicSplitter.context) {
|
||||
const audioContext = this.createdNodes.musicSplitter.context
|
||||
this.createdNodes.musicSource ||= audioContext.createMediaElementSource(musicAudioElement)
|
||||
}
|
||||
} else {
|
||||
// useNuxtApp().$logger.log('no error to solve')
|
||||
}
|
||||
},
|
||||
connectNodes () {
|
||||
try {
|
||||
// Destructure for easier access
|
||||
const {
|
||||
musicSource, musicSplitter, microphoneSource,
|
||||
musicDevice, outputSplitter, musicGain,
|
||||
noiseSource, noiseSplitter, noiseDevice,
|
||||
noiseGain, merger
|
||||
} = this.createdNodes
|
||||
|
||||
// Check context compatibility
|
||||
if (!this.checkContextCompatibility) {
|
||||
if (!this.checkContextCompatibility) { throw new Error('Incompatible audio context among nodes.') }
|
||||
}
|
||||
|
||||
// Connect music source to splitter (Stereo to 2 Channels)
|
||||
musicSource.connect(musicSplitter)
|
||||
// Connect microphone to musicDevice input 0 (Mono)
|
||||
microphoneSource.connect(musicDevice, 0, 0)
|
||||
// Connect musicSplitter to musicDevice inputs 1 and 2 (Stereo)
|
||||
musicSplitter.connect(musicDevice, 0, 1)
|
||||
musicSplitter.connect(musicDevice, 1, 2)
|
||||
// Connect musicDevice to outputSplitter (Stereo out)
|
||||
musicDevice.connect(outputSplitter)
|
||||
// Optionally connect musicDevice to musicGain for additional gain control (Stereo)
|
||||
musicDevice.connect(musicGain)
|
||||
// Connect noise source to noiseSplitter (Stereo to 2 Channels)
|
||||
noiseSource.connect(noiseSplitter)
|
||||
// Connect microphone to noiseDevice input 0 (Mono)
|
||||
microphoneSource.connect(noiseDevice, 0, 0)
|
||||
// Connect noiseSplitter to noiseDevice inputs 1 and 2 (Stereo)
|
||||
noiseSplitter.connect(noiseDevice, 0, 1)
|
||||
noiseSplitter.connect(noiseDevice, 1, 2)
|
||||
// Connect outputSplitter to noiseDevice inputs 3 and 4 (Stereo from musicDevice)
|
||||
outputSplitter.connect(noiseDevice, 0, 3)
|
||||
outputSplitter.connect(noiseDevice, 1, 4)
|
||||
// Assuming noiseDevice outputs stereo, connect to noiseGain (Stereo)
|
||||
noiseDevice.connect(noiseGain)
|
||||
// Assuming you want to merge and output both processed signals (Stereo from both musicGain and noiseGain)
|
||||
// Connect noiseGain to the first two inputs of the merger (Stereo)
|
||||
noiseGain.connect(merger, 0, 0)
|
||||
noiseGain.connect(merger, 0, 1)
|
||||
// Connect musicGain to the last two inputs of the merger (Stereo)
|
||||
musicGain.connect(merger, 0, 2)
|
||||
musicGain.connect(merger, 0, 3)
|
||||
// Finally, connect the merger to the audio context's destination (Stereo to output)
|
||||
merger.connect(merger.context.destination)
|
||||
} catch (e) {
|
||||
// useNuxtApp().$logger.error('Failed to connect nodes: ', e.message)
|
||||
// Additional error handling can be implemented here if necessary
|
||||
}
|
||||
},
|
||||
handlePlayingUpdate (isPlaying) {
|
||||
const audioContext = this.audioContext ? this.audioContext : useAudioStore().getContext()
|
||||
let microphoneSource = null
|
||||
// // useNuxtApp().$logger.log('Handling playing update = ' + isPlaying) // true when playing
|
||||
if (isPlaying) {
|
||||
// Web Audio API is available.
|
||||
// const musicAudioComponent = this.$refs[this.currentElement.title].audioElement
|
||||
if (this.areAllNodesAvailable()) {
|
||||
this.disconnectNodes()
|
||||
const musicAudioElement = this.$refs[this.currentElement.title].$refs.audioElement
|
||||
const musicGainValue = this.createdNodes.musicGain.gain.value
|
||||
const noiseGainValue = this.createdNodes.noiseGain.gain.value
|
||||
// Prepare the audio elements (unmute)
|
||||
this.$refs[this.currentElement.title].$refs.audioElement.muted = false
|
||||
this.$refs.Noise.$refs.audioElement.muted = false
|
||||
// replace music node because all Nodes are there
|
||||
this.createdNodes.musicSource = null
|
||||
this.createdNodes.musicSource = audioContext.createMediaElementSource(musicAudioElement)
|
||||
this.connectNodes()
|
||||
|
||||
if (musicGainValue > 0 && musicGainValue <= 1.0) {
|
||||
this.createdNodes.musicGain.gain.linearRampToValueAtTime(musicGainValue || 1.0, audioContext.currentTime + 2)
|
||||
}
|
||||
if (noiseGainValue > 0 && noiseGainValue <= 1.0) {
|
||||
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(noiseGainValue || 1.0, audioContext.currentTime + 3)
|
||||
}
|
||||
this.audioContext.resume()
|
||||
} else {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false,
|
||||
autoGainControl: false
|
||||
},
|
||||
video: false
|
||||
})
|
||||
.then((micStream) => {
|
||||
microphoneSource = this.audioContext.createMediaStreamSource(micStream)
|
||||
this.createdNodes.microphoneSource ||= microphoneSource
|
||||
// Return both audioContext and microphoneSource to the next link in the chain
|
||||
return { audioContext, microphoneSource }
|
||||
})
|
||||
.then(({ audioContext, microphoneSource }) => {
|
||||
// First RNBO device creation
|
||||
return createRNBODevice(audioContext, this.noisePatch).then((noiseRNBODevice) => {
|
||||
this.createdNodes.noiseRNBODevice ||= noiseRNBODevice
|
||||
// Return al.then(() => {hen(() => necessary objects for the next step
|
||||
return { audioContext, microphoneSource, noiseRNBODevice }
|
||||
})
|
||||
})
|
||||
.then(({ audioContext, microphoneSource, noiseRNBODevice }) => {
|
||||
// Second RNBO device creation
|
||||
return createRNBODevice(audioContext, this.musicPatch).then((musicRNBODevice) => {
|
||||
// Return all necessary objects for the final setup
|
||||
this.createdNodes.musicRNBODevice ||= musicRNBODevice
|
||||
return { audioContext, microphoneSource, noiseRNBODevice, musicRNBODevice }
|
||||
})
|
||||
}).then(({ audioContext, microphoneSource, noiseRNBODevice, musicRNBODevice }) => {
|
||||
const musicElement = this.$refs[this.currentElement.title]
|
||||
const musicAudioElement = musicElement.$refs.audioElement
|
||||
|
||||
// Ensure this.createdNodes is initialized
|
||||
this.createdNodes ||= {}
|
||||
// Assign nodes if they don't exist
|
||||
this.createdNodes.microphoneSource ||= microphoneSource
|
||||
this.createdNodes.musicDevice ||= musicRNBODevice.node
|
||||
this.createdNodes.noiseDevice ||= noiseRNBODevice.node
|
||||
this.createdNodes.noiseSource ||= audioContext.createMediaElementSource(this.$refs.Noise.$refs.audioElement)
|
||||
this.createdNodes.musicSource ||= audioContext.createMediaElementSource(musicAudioElement)
|
||||
this.createdNodes.musicSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.noiseSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.outputSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.musicGain ||= audioContext.createGain()
|
||||
this.createdNodes.noiseGain ||= audioContext.createGain()
|
||||
this.createdNodes.merger ||= audioContext.createChannelMerger(4)
|
||||
|
||||
musicAudioElement.muted = false
|
||||
this.$refs.Noise.$refs.audioElement.muted = false
|
||||
this.createdNodes.musicGain.gain.value = 0.0001
|
||||
this.createdNodes.noiseGain.gain.value = 0.0001 // macht nichts
|
||||
this.connectNodes()
|
||||
|
||||
setTimeout(() => {
|
||||
this.playState = true
|
||||
this.createdNodes.musicGain.gain.exponentialRampToValueAtTime(1, audioContext.currentTime + 3)
|
||||
this.createdNodes.noiseGain.gain.exponentialRampToValueAtTime(1, audioContext.currentTime + 4)
|
||||
if (audioContext.state === 'suspended') { this.resumeContext() }
|
||||
}, 150)
|
||||
})
|
||||
.catch((_error) => {
|
||||
this.disconnectNodes()
|
||||
|
||||
if (this.audioContext) {
|
||||
this.audioContext.destination.disconnect()
|
||||
// audioContext.destination = null
|
||||
}
|
||||
this.$toast.error('Oouh sorry! Error while setting up audio, please reload.')
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// this.audioContext.destination.disconnect()
|
||||
this.audioContext.suspend()
|
||||
// this.disconnectNodes()
|
||||
// const newAudioContext = useAudioStore().getContext()
|
||||
|
||||
// this.audioContext.close()
|
||||
// this.audioContext = newAudioContext
|
||||
}
|
||||
},
|
||||
disconnectNodes () {
|
||||
// Ensure this.createdNodes is defined and is an object
|
||||
if (typeof this.createdNodes === 'object' && this.createdNodes !== null) {
|
||||
Object.values(this.createdNodes).forEach((node) => {
|
||||
// Check if the node exists and has a disconnect method
|
||||
if (node && typeof node.disconnect === 'function') {
|
||||
node.disconnect()
|
||||
}
|
||||
// Additional handling for specific types of nodes, if necessary
|
||||
// For example, stopping an OscillatorNode
|
||||
if (node instanceof OscillatorNode) {
|
||||
node.stop()
|
||||
}
|
||||
})
|
||||
this.playState = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.rnboplayer {
|
||||
position: fixed;
|
||||
width: 220px; /* Or specify a fixed width like 220px if you prefer */
|
||||
max-width: 220px; /* This line might be redundant depending on your width strategy */
|
||||
height: 100px;
|
||||
display: inline-grid;
|
||||
z-index: 2;
|
||||
bottom: 11%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,395 @@
|
||||
<template>
|
||||
<div>
|
||||
<KeyboardPlayHandler />
|
||||
<h2>You will hear the arifacts right after the music begins</h2>
|
||||
<h2>
|
||||
These file contain both patches, use Gainsliders of AudioElement to control volume and allow to skip. AudioStore used for shared AudioContext.
|
||||
Music is connected directlly to the noise patch,
|
||||
way less glitches
|
||||
</h2>
|
||||
<div class="rnboplayer">
|
||||
<div v-if="selectedAudioTitle">
|
||||
{{ selectedAudio.title }}
|
||||
<AudioElement
|
||||
:ref="selectedAudio.title"
|
||||
:key="selectedAudio.id"
|
||||
:src="selectedAudio.src"
|
||||
:title="selectedAudio.title"
|
||||
@update:playing="handlePlayingUpdate"
|
||||
@update:volume="changeMusicVolume"
|
||||
>
|
||||
<template #default="{}" />
|
||||
<!-- Slot content for AudioElement, if needed -->
|
||||
</AudioElement>
|
||||
</div>
|
||||
<AudioElement
|
||||
:ref="noise.title"
|
||||
:key="noise.id"
|
||||
:src="noise.src"
|
||||
:title="noise.title"
|
||||
@update:volume="changeNoiseVolume"
|
||||
>
|
||||
<template #default="{}" />
|
||||
<!-- Slot content for AudioElement, if needed -->
|
||||
</AudioElement>
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<button @click="skip">
|
||||
Skip
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button @click="resumeContext">
|
||||
Resume Context
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button @click="stopContext">
|
||||
Suspend Context
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AudioElement from '@/components/experiments/AudioElement.vue'
|
||||
import importedMusicPatcher from '@/assets/patch/music_patch.export.json'
|
||||
import importedNoisePatcher from '@/assets/patch/noise_patch.export.json'
|
||||
import { createRNBODevice } from '@/lib/AudioFunctions'
|
||||
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
|
||||
|
||||
// import setupNodes from '@/components/Player/Nodes'
|
||||
import { useAudioStore } from '~/stores/audio'
|
||||
export default {
|
||||
components: {
|
||||
AudioElement,
|
||||
KeyboardPlayHandler
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
audioList: [
|
||||
{ id: 1, title: 'Lagoon', src: window.location.origin + '/sounds/lagoon.m4a' },
|
||||
{ id: 2, title: 'Forest', src: window.location.origin + '/sounds/forest.m4a' },
|
||||
{ id: 3, title: 'Meadow', src: window.location.origin + '/sounds/meadow.m4a' },
|
||||
{ id: 4, title: 'Tropics', src: window.location.origin + '/sounds/tropics.m4a' }
|
||||
],
|
||||
noise: { id: 5, title: 'Noise', src: window.location.origin + '/masking/noise.flac' },
|
||||
selectedAudioTitle: '',
|
||||
currentElement: {},
|
||||
createdNodes: {},
|
||||
musicPatch: importedMusicPatcher,
|
||||
noisePatch: importedNoisePatcher,
|
||||
audioContext: null,
|
||||
playState: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedAudio () {
|
||||
return this.audioList.find(audio => audio.title === this.selectedAudioTitle) || null
|
||||
},
|
||||
checkContextCompatibility () {
|
||||
// Assume createdNodes is an object where each property is a node
|
||||
const nodes = Object.values(this.createdNodes)
|
||||
const incompatible = nodes.filter(node => node.context !== this.audioContext)
|
||||
if (nodes.length === nodes.length - incompatible.length) { return true }
|
||||
// // useNuxtApp().$logger.log(incompatible.length + '/' + nodes.length + ' sind inkompatibel')
|
||||
this.handleIncompatibeError(incompatible)
|
||||
return false
|
||||
},
|
||||
isPlaying () {
|
||||
return this.playState
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
this.disconnectNodes()
|
||||
if (this.audioContext) { this.audioContext.close() }
|
||||
this.audioContext = null
|
||||
},
|
||||
mounted () {
|
||||
this.audioContext = useAudioStore().getContext()
|
||||
// Example: Select 'Song Three' by default
|
||||
this.selectAudioByTitle(this.audioList[0].title)
|
||||
this.addUserNavigationHandling()
|
||||
this.musicPatch = importedMusicPatcher
|
||||
this.noisePatch = importedNoisePatcher
|
||||
},
|
||||
methods: {
|
||||
|
||||
changeNoiseVolume (volume) {
|
||||
if (this.createdNodes.noiseGain) {
|
||||
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(volume, this.createdNodes.noiseGain.context.currentTime + 0.30)
|
||||
this.noiseGain = this.createdNodes.noiseGain.gain.value
|
||||
}
|
||||
},
|
||||
changeMusicVolume (volume) {
|
||||
if (this.createdNodes.musicGain) {
|
||||
this.createdNodes.musicGain.gain.linearRampToValueAtTime(volume, this.createdNodes.musicGain.context.currentTime + 0.30)
|
||||
this.musicGain = this.createdNodes.musicGain.gain.value
|
||||
}
|
||||
},
|
||||
addUserNavigationHandling () {
|
||||
if ('mediaSession' in navigator) {
|
||||
// Set the handler for the next track action
|
||||
navigator.mediaSession.setActionHandler('nexttrack', () => {
|
||||
this.skip('next')
|
||||
})
|
||||
|
||||
// Set the handler for the previous track action
|
||||
navigator.mediaSession.setActionHandler('previoustrack', () => {
|
||||
this.skip('previous')
|
||||
})
|
||||
}
|
||||
},
|
||||
getSong (currentTitle, direction) {
|
||||
const index = this.audioList.findIndex(song => song.title === currentTitle)
|
||||
let adjacentIndex = index + (direction === 'next' ? 1 : -1)
|
||||
// Loop back to the first song if 'next' goes beyond the last index
|
||||
if (adjacentIndex >= this.audioList.length) {
|
||||
adjacentIndex = 0
|
||||
} else if (adjacentIndex < 0) {
|
||||
adjacentIndex = this.audioList.length - 1
|
||||
}
|
||||
return this.audioList[adjacentIndex]
|
||||
},
|
||||
skip (direction) {
|
||||
const nextSong = this.getSong(this.selectedAudioTitle, direction)
|
||||
this.selectAudioByTitle(nextSong.title)
|
||||
},
|
||||
resumeContext () {
|
||||
if (this.audioContext.state === 'suspended') { this.audioContext.resume() } else {
|
||||
// useNuxtApp().$logger.log('already resumed?')
|
||||
}
|
||||
},
|
||||
stopContext () {
|
||||
if (this.audioContext.state === 'running') { this.audioContext.suspend() } else {
|
||||
// useNuxtApp().$logger.log('already suspended')
|
||||
}
|
||||
},
|
||||
updateCurrentElement (title) {
|
||||
this.currentElement = this.audioList.find(e => e.title === title)
|
||||
},
|
||||
selectAudioByTitle (title) {
|
||||
this.selectedAudioTitle = title
|
||||
// const audioElement = this.audioList.find(e => e.title === this.selectedAudioTitle)
|
||||
this.currentElement = this.audioList[this.audioList.findIndex(e => e.title === title)]
|
||||
// // useNuxtApp().$logger.log('currentElement: ', this.selectedAudioTitle)
|
||||
},
|
||||
areAllNodesAvailable () {
|
||||
// List of required node keys as you've named them in this.createdNodes
|
||||
const requiredNodes = [
|
||||
'musicSource', 'musicSplitter', 'microphoneSource',
|
||||
'musicDevice', 'outputSplitter', 'musicGain',
|
||||
'noiseSource', 'noiseSplitter', 'noiseDevice',
|
||||
'noiseGain', 'merger'
|
||||
]
|
||||
|
||||
// Check if each required node exists and is not undefined in this.createdNodes
|
||||
return requiredNodes.every(nodeKey => this.createdNodes[nodeKey] !== undefined)
|
||||
},
|
||||
handleIncompatibeError (incompatible) {
|
||||
if (incompatible) {
|
||||
const node = incompatible.pop()
|
||||
if (node.context !== this.createdNodes.musicSplitter.context) {
|
||||
const audioContext = this.createdNodes.musicSplitter.context
|
||||
this.createdNodes.musicSource ||= audioContext.createMediaElementSource(musicAudioElement)
|
||||
}
|
||||
} else {
|
||||
// useNuxtApp().$logger.log('no error to solve')
|
||||
}
|
||||
},
|
||||
connectNodes () {
|
||||
try {
|
||||
// Destructure for easier access
|
||||
const {
|
||||
musicSource, microphoneSource,
|
||||
musicGain,
|
||||
noiseSource, noiseSplitter, noiseDevice,
|
||||
noiseGain, merger
|
||||
} = this.createdNodes
|
||||
|
||||
// Check context compatibility
|
||||
if (!this.checkContextCompatibility) {
|
||||
if (!this.checkContextCompatibility) { throw new Error('Incompatible audio context among nodes.') }
|
||||
}
|
||||
|
||||
// Connect music source to splitter (Stereo to 2 Channels)
|
||||
musicSource.connect(musicGain)
|
||||
// Connect microphone to musicDevice input 0 (Mono)
|
||||
// Connect musicDevice to outputSplitter (Stereo out)
|
||||
// musicDevice.connect(outputSplitter)
|
||||
// Optionally connect musicDevice to musicGain for additional gain control (Stereo)
|
||||
// musicDevice.connect(musicGain)
|
||||
// Connect noise source to noiseSplitter (Stereo to 2 Channels)
|
||||
noiseSource.connect(noiseSplitter)
|
||||
// Connect microphone to noiseDevice input 0 (Mono)
|
||||
microphoneSource.connect(noiseDevice, 0, 0)
|
||||
// Connect noiseSplitter to noiseDevice inputs 1 and 2 (Stereo)
|
||||
noiseSplitter.connect(noiseDevice, 0, 1)
|
||||
noiseSplitter.connect(noiseDevice, 1, 2)
|
||||
// Connect outputSplitter to noiseDevice inputs 3 and 4 (Stereo from musicDevice)
|
||||
// const rndSrc = noiseDevice.context.createConstantSource()
|
||||
// const rndSrc2 = noiseDevice.context.createConstantSource()
|
||||
// rndSrc.connect(noiseDevice, 0, 3)
|
||||
// rndSrc2.connect(noiseDevice, 0, 4)
|
||||
// Assuming noiseDevice outputs stereo, connect to noiseGain (Stereo)
|
||||
noiseDevice.connect(noiseGain)
|
||||
// Assuming you want to merge and output both processed signals (Stereo from both musicGain and noiseGain)
|
||||
// Connect noiseGain to the first two inputs of the merger (Stereo)
|
||||
noiseGain.connect(merger, 0, 0)
|
||||
noiseGain.connect(merger, 0, 1)
|
||||
// Connect musicGain to the last two inputs of the merger (Stereo)
|
||||
musicGain.connect(merger, 0, 2)
|
||||
musicGain.connect(merger, 0, 3)
|
||||
// Finally, connect the merger to the audio context's destination (Stereo to output)
|
||||
merger.connect(merger.context.destination)
|
||||
} catch (e) {
|
||||
// useNuxtApp().$logger.error('Failed to connect nodes: ', e.message)
|
||||
// Additional error handling can be implemented here if necessary
|
||||
}
|
||||
},
|
||||
handlePlayingUpdate (isPlaying) {
|
||||
const audioContext = this.audioContext ? this.audioContext : useAudioStore().getContext()
|
||||
let microphoneSource = null
|
||||
// // useNuxtApp().$logger.log('Handling playing update = ' + isPlaying) // true when playing
|
||||
if (isPlaying) {
|
||||
// Web Audio API is available.
|
||||
// const musicAudioComponent = this.$refs[this.currentElement.title].audioElement
|
||||
if (this.areAllNodesAvailable()) {
|
||||
this.disconnectNodes()
|
||||
const musicAudioElement = this.$refs[this.currentElement.title].$refs.audioElement
|
||||
const musicGainValue = this.createdNodes.musicGain.gain.value
|
||||
const noiseGainValue = this.createdNodes.noiseGain.gain.value
|
||||
// Prepare the audio elements (unmute)
|
||||
this.$refs[this.currentElement.title].$refs.audioElement.muted = false
|
||||
this.$refs.Noise.$refs.audioElement.muted = false
|
||||
// replace music node because all Nodes are there
|
||||
this.createdNodes.musicSource = null
|
||||
this.createdNodes.musicSource = audioContext.createMediaElementSource(musicAudioElement)
|
||||
this.connectNodes()
|
||||
|
||||
if (musicGainValue > 0 && musicGainValue <= 1.0) {
|
||||
this.createdNodes.musicGain.gain.linearRampToValueAtTime(musicGainValue || 1.0, audioContext.currentTime + 2)
|
||||
}
|
||||
if (noiseGainValue > 0 && noiseGainValue <= 1.0) {
|
||||
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(noiseGainValue || 1.0, audioContext.currentTime + 3)
|
||||
}
|
||||
this.audioContext.resume()
|
||||
} else {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false,
|
||||
autoGainControl: false
|
||||
},
|
||||
video: false
|
||||
})
|
||||
.then((micStream) => {
|
||||
microphoneSource = this.audioContext.createMediaStreamSource(micStream)
|
||||
this.createdNodes.microphoneSource ||= microphoneSource
|
||||
// Return both audioContext and microphoneSource to the next link in the chain
|
||||
return { audioContext, microphoneSource }
|
||||
})
|
||||
.then(({ audioContext, microphoneSource }) => {
|
||||
// First RNBO device creation
|
||||
return createRNBODevice(audioContext, this.noisePatch).then((noiseRNBODevice) => {
|
||||
this.createdNodes.noiseRNBODevice ||= noiseRNBODevice
|
||||
// Return al.then(() => {hen(() => necessary objects for the next step
|
||||
return { audioContext, microphoneSource, noiseRNBODevice }
|
||||
})
|
||||
})
|
||||
.then(({ audioContext, microphoneSource, noiseRNBODevice }) => {
|
||||
// Second RNBO device creation
|
||||
return createRNBODevice(audioContext, this.musicPatch).then((musicRNBODevice) => {
|
||||
// Return all necessary objects for the final setup
|
||||
this.createdNodes.musicRNBODevice ||= musicRNBODevice
|
||||
return { audioContext, microphoneSource, noiseRNBODevice, musicRNBODevice }
|
||||
})
|
||||
}).then(({ audioContext, microphoneSource, noiseRNBODevice, musicRNBODevice }) => {
|
||||
const musicElement = this.$refs[this.currentElement.title]
|
||||
const musicAudioElement = musicElement.$refs.audioElement
|
||||
|
||||
// Ensure this.createdNodes is initialized
|
||||
this.createdNodes ||= {}
|
||||
// Assign nodes if they don't exist
|
||||
this.createdNodes.microphoneSource ||= microphoneSource
|
||||
this.createdNodes.musicDevice ||= musicRNBODevice.node
|
||||
this.createdNodes.noiseDevice ||= noiseRNBODevice.node
|
||||
this.createdNodes.noiseSource ||= audioContext.createMediaElementSource(this.$refs.Noise.$refs.audioElement)
|
||||
this.createdNodes.musicSource ||= audioContext.createMediaElementSource(musicAudioElement)
|
||||
this.createdNodes.musicSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.noiseSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.outputSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.musicGain ||= audioContext.createGain()
|
||||
this.createdNodes.noiseGain ||= audioContext.createGain()
|
||||
this.createdNodes.merger ||= audioContext.createChannelMerger(4)
|
||||
|
||||
musicAudioElement.muted = false
|
||||
this.$refs.Noise.$refs.audioElement.muted = false
|
||||
this.createdNodes.musicGain.gain.value = 0.0001
|
||||
this.createdNodes.noiseGain.gain.value = 0.0001 // macht nichts
|
||||
this.connectNodes()
|
||||
|
||||
setTimeout(() => {
|
||||
this.playState = true
|
||||
this.createdNodes.musicGain.gain.exponentialRampToValueAtTime(1, audioContext.currentTime + 3)
|
||||
this.createdNodes.noiseGain.gain.exponentialRampToValueAtTime(1, audioContext.currentTime + 4)
|
||||
if (audioContext.state === 'suspended') { this.resumeContext() }
|
||||
}, 1000)
|
||||
})
|
||||
.catch((_error) => {
|
||||
this.disconnectNodes()
|
||||
|
||||
if (this.audioContext) {
|
||||
this.audioContext.destination.disconnect()
|
||||
// audioContext.destination = null
|
||||
}
|
||||
this.$toast.error('Oouh sorry! Error while setting up audio, please reload.')
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// this.audioContext.destination.disconnect()
|
||||
this.audioContext.suspend()
|
||||
// this.disconnectNodes()
|
||||
// const newAudioContext = useAudioStore().getContext()
|
||||
|
||||
// this.audioContext.close()
|
||||
// this.audioContext = newAudioContext
|
||||
}
|
||||
},
|
||||
disconnectNodes () {
|
||||
// Ensure this.createdNodes is defined and is an object
|
||||
if (typeof this.createdNodes === 'object' && this.createdNodes !== null) {
|
||||
Object.values(this.createdNodes).forEach((node) => {
|
||||
// Check if the node exists and has a disconnect method
|
||||
if (node && typeof node.disconnect === 'function') {
|
||||
node.disconnect()
|
||||
}
|
||||
// Additional handling for specific types of nodes, if necessary
|
||||
// For example, stopping an OscillatorNode
|
||||
if (node instanceof OscillatorNode) {
|
||||
node.stop()
|
||||
}
|
||||
})
|
||||
this.playState = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.rnboplayer {
|
||||
position: fixed;
|
||||
width: 220px; /* Or specify a fixed width like 220px if you prefer */
|
||||
max-width: 220px; /* This line might be redundant depending on your width strategy */
|
||||
height: 100px;
|
||||
display: inline-grid;
|
||||
z-index: 2;
|
||||
bottom: 11%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
455
archive/pages/experiments/noiseManager.vue
Normal file
455
archive/pages/experiments/noiseManager.vue
Normal file
@@ -0,0 +1,455 @@
|
||||
<template>
|
||||
<div class="rnboplayer">
|
||||
<label class="switch">
|
||||
<input type="checkbox" :v-model="deviceUsed">
|
||||
<span class="slider" />
|
||||
</label>
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="adaptive pb-3">
|
||||
<div class="col-12 ">
|
||||
<div class="d-none d-md-block mx-auto pb-1" style="width: 225px">
|
||||
<div class="progress " style="height: 10px">
|
||||
<div
|
||||
class="progress-bar bar"
|
||||
role="progressbar"
|
||||
aria-label="Basic example"
|
||||
:style="{width:bar_width+'%'}"
|
||||
style="background-color: #e9c046;transition: 0.1s"
|
||||
aria-valuenow="75"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center mb-1">
|
||||
<nuxt-link to="#adaptive-modal" data-bs-target="#adaptive-modal" data-bs-toggle="modal" class="text-muted text-decoration-none fw-bold fs-6 ">
|
||||
{{ t('Adaptive soundscape') }} : <span class="" style="color: #e9c046">{{ t('On') }}</span>
|
||||
</nuxt-link><span class="ps-3"><i style="padding: 5px 0px;" class="fa-solid text-muted d-flex fa-chevron-right" /></span>
|
||||
</div>
|
||||
<div class="d-block d-md-none mx-auto pb-1" style="width: 225px">
|
||||
<div class="progress " style="height: 10px">
|
||||
<div
|
||||
class="progress-bar bar"
|
||||
role="progressbar"
|
||||
aria-label="Basic example"
|
||||
:style="{width:bar_width+'%'}"
|
||||
style="background-color: #e9c046;transition: 0.1s"
|
||||
aria-valuenow="75"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BootomBar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedAudioTitle">
|
||||
{{ selectedAudio.title }}
|
||||
<AudioElement
|
||||
:ref="selectedAudio.title"
|
||||
:key="selectedAudio.id"
|
||||
:src="selectedAudio.src"
|
||||
:title="selectedAudio.title"
|
||||
@update:playing="handlePlayingUpdate"
|
||||
@volume="changeMusicVolume"
|
||||
>
|
||||
<template #default="{ }" />
|
||||
<!-- Slot content for AudioElement, if needed -->
|
||||
</AudioElement>
|
||||
</div>
|
||||
<AudioElement
|
||||
:ref="noise.title"
|
||||
:key="noise.id"
|
||||
:src="noise.src"
|
||||
:title="noise.title"
|
||||
@update:playing="handlePlayingUpdate"
|
||||
@volume="changeNoiseVolume"
|
||||
>
|
||||
<template #default="{}" />
|
||||
<!-- Slot content for AudioElement, if needed -->
|
||||
</AudioElement>
|
||||
|
||||
<button @click="skip">
|
||||
Skip
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AudioElement from '@/components/experiments/AudioElement.vue'
|
||||
import importedMusicPatcher from '@/assets/patch/music_patch.export.json'
|
||||
import importedNoisePatcher from '@/assets/patch/noise_patch.export.json'
|
||||
import { createRNBODevice } from '@/lib/AudioFunctions'
|
||||
|
||||
// import setupNodes from '@/components/Player/Nodes'
|
||||
// import { useAudioStore } from '~/stores/audio'
|
||||
export default {
|
||||
components: {
|
||||
AudioElement
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
t: useI18n,
|
||||
audioList: [
|
||||
{ id: 1, title: 'Lagoon', src: window.location.origin + '/sounds/lagoon.m4a' },
|
||||
{ id: 2, title: 'Forest', src: window.location.origin + '/sounds/forest.m4a' },
|
||||
{ id: 3, title: 'Meadow', src: window.location.origin + '/sounds/meadow.m4a' },
|
||||
{ id: 4, title: 'Tropics', src: window.location.origin + '/sounds/tropics.m4a' }
|
||||
],
|
||||
noise: { id: 5, title: 'Noise', src: window.location.origin + '/masking/noise.flac' },
|
||||
selectedAudioTitle: '',
|
||||
currentElement: {},
|
||||
createdNodes: {},
|
||||
musicPatch: importedMusicPatcher,
|
||||
noisePatch: importedNoisePatcher,
|
||||
audioContext: null,
|
||||
isPlayerRunning: false,
|
||||
checked: false,
|
||||
// meter
|
||||
meter: null,
|
||||
bar_width: 0,
|
||||
analyser: null,
|
||||
dataArray: null,
|
||||
bar_val: 25
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedAudio () {
|
||||
return this.audioList.find(audio => audio.title === this.selectedAudioTitle) || null
|
||||
},
|
||||
deviceUsed () {
|
||||
return this.isPlayerRunning
|
||||
}
|
||||
|
||||
},
|
||||
beforeUnmount () {
|
||||
this.disconnectNodes()
|
||||
if (this.audioContext) { this.audioContext.close() }
|
||||
this.audioContext = null
|
||||
},
|
||||
mounted () {
|
||||
this.audioContext = new AudioContext()
|
||||
this.currentElement = this.audioList[0]
|
||||
// Example: Select 'Song Three' by default
|
||||
this.selectAudioByTitle(this.audioList[0].title)
|
||||
this.addUserNavigationHandling()
|
||||
this.musicPatch = importedMusicPatcher
|
||||
this.noisePatch = importedNoisePatcher
|
||||
},
|
||||
methods: {
|
||||
changeMusicVolume (newValue) {
|
||||
if (!this.createdNodes.musicGain.gain) { return }
|
||||
// useNuxtApp().$logger.log(this.createdNodes.musicGain.gain)
|
||||
this.createdNodes.musicGain.gain.cancelScheduledValues(this.createdNodes.musicGain.context.currentTime)
|
||||
this.createdNodes.musicGain.gain.setValueAtTime(newValue / 100, this.createdNodes.musicGain.context.currentTime + 0.01)
|
||||
},
|
||||
changeNoiseVolume (newValue) {
|
||||
if (!this.createdNodes.noiseGain.gain) { return }
|
||||
this.createdNodes.noiseGain.gain.setValueAtTime(newValue / 100, this.createdNodes.noiseGain.context.currentTime + 0.01)
|
||||
// this.createdNodes.noiseGain.gain.value = newValue / 100
|
||||
},
|
||||
addUserNavigationHandling () {
|
||||
if ('mediaSession' in navigator) {
|
||||
// Set the handler for the next track action
|
||||
navigator.mediaSession.setActionHandler('nexttrack', () => {
|
||||
this.skip('next')
|
||||
})
|
||||
|
||||
// Set the handler for the previous track action
|
||||
navigator.mediaSession.setActionHandler('previoustrack', () => {
|
||||
this.skip('previous')
|
||||
})
|
||||
}
|
||||
},
|
||||
getSong (currentTitle, direction) {
|
||||
const index = this.audioList.findIndex(song => song.title === currentTitle)
|
||||
let adjacentIndex = index + (direction === 'next' ? 1 : -1)
|
||||
// Loop back to the first song if 'next' goes beyond the last index
|
||||
if (adjacentIndex >= this.audioList.length) {
|
||||
adjacentIndex = 0
|
||||
} else if (adjacentIndex < 0) {
|
||||
adjacentIndex = this.audioList.length - 1
|
||||
}
|
||||
return this.audioList[adjacentIndex]
|
||||
},
|
||||
skip (direction) {
|
||||
const nextSong = this.getSong(this.selectedAudioTitle, direction)
|
||||
this.selectAudioByTitle(nextSong.title)
|
||||
},
|
||||
updateCurrentElement (title) {
|
||||
this.currentElement = this.audioList.find(e => e.title === title)
|
||||
},
|
||||
selectAudioByTitle (title) {
|
||||
this.selectedAudioTitle = title
|
||||
// const audioElement = this.audioList.find(e => e.title === this.selectedAudioTitle)
|
||||
this.currentElement = this.audioList[this.audioList.findIndex(e => e.title === title)]
|
||||
// useNuxtApp().$logger.log('currentElement: ', this.selectedAudioTitle)
|
||||
},
|
||||
areAllNodesAvailable () {
|
||||
// List of required node keys as you've named them in this.createdNodes
|
||||
const requiredNodes = [
|
||||
'musicSource', 'musicSplitter', 'microphoneSource',
|
||||
'musicDevice', 'outputSplitter', 'musicGain',
|
||||
'noiseSource', 'noiseSplitter', 'noiseDevice',
|
||||
'noiseGain', 'merger'
|
||||
]
|
||||
|
||||
// Check if each required node exists and is not undefined in this.createdNodes
|
||||
return requiredNodes.every(nodeKey => this.createdNodes[nodeKey] !== undefined)
|
||||
},
|
||||
updateMeter () {
|
||||
requestAnimationFrame(this.updateMeter)
|
||||
this.analyser.getByteFrequencyData(this.dataArray)
|
||||
const rms = this.getRMS(this.dataArray)
|
||||
let level = 20 * Math.log10(rms / 128)
|
||||
level = Math.max(0, Math.min(100, level + 100))
|
||||
// bar.style.width = level + '%';
|
||||
|
||||
this.bar_width = level
|
||||
},
|
||||
getRMS (dataArray) {
|
||||
let rms = 0
|
||||
for (let i = 0; i < dataArray.length; i++) {
|
||||
rms += dataArray[i] * dataArray[i]
|
||||
}
|
||||
rms /= dataArray.length
|
||||
rms = Math.sqrt(rms)
|
||||
return rms
|
||||
},
|
||||
connectNodes () {
|
||||
// Destructure for easier access
|
||||
const audioCtx = this.createdNodes.microphoneSource.context
|
||||
this.analyser = audioCtx.createAnalyser()
|
||||
this.createdNodes.microphoneSource.connect(this.analyser)
|
||||
this.analyser.fftSize = 2048
|
||||
const bufferLength = this.analyser.frequencyBinCount
|
||||
this.dataArray = new Uint8Array(bufferLength)
|
||||
this.updateMeter()
|
||||
|
||||
const {
|
||||
musicSource, musicSplitter, microphoneSource,
|
||||
musicDevice, outputSplitter, musicGain,
|
||||
noiseSource, noiseSplitter, noiseDevice,
|
||||
noiseGain, merger
|
||||
} = this.createdNodes
|
||||
// Assuming all nodes are created and references to them are correct
|
||||
|
||||
// Connect music source to splitter (Stereo to 2 Channels)
|
||||
musicSource.connect(musicSplitter) // 2 channels: L, R
|
||||
|
||||
// Connect microphone to musicDevice input 0 (Mono)
|
||||
microphoneSource.connect(musicDevice, 0, 0) // 1 channel: Microphone
|
||||
|
||||
// Connect musicSplitter to musicDevice inputs 1 and 2 (Stereo)
|
||||
musicSplitter.connect(musicDevice, 0, 1) // 1 channel: Left
|
||||
musicSplitter.connect(musicDevice, 1, 2) // 1 channel: Right
|
||||
|
||||
// Connect musicDevice to outputSplitter (Stereo out)
|
||||
musicDevice.connect(outputSplitter) // Assuming musicDevice outputs stereo
|
||||
|
||||
// Optionally connect musicDevice to musicGain for additional gain control (Stereo)
|
||||
musicDevice.connect(musicGain) // Assuming musicDevice outputs stereo, connected to both channels of musicGain
|
||||
|
||||
// Connect noise source to noiseSplitter (Stereo to 2 Channels)
|
||||
noiseSource.connect(noiseSplitter) // 2 channels: L, R
|
||||
|
||||
// Connect microphone to noiseDevice input 0 (Mono)
|
||||
microphoneSource.connect(noiseDevice, 0, 0) // 1 channel: Microphone
|
||||
|
||||
// Connect noiseSplitter to noiseDevice inputs 1 and 2 (Stereo)
|
||||
noiseSplitter.connect(noiseDevice, 0, 1) // 1 channel: Left
|
||||
noiseSplitter.connect(noiseDevice, 1, 2) // 1 channel: Right
|
||||
|
||||
// Connect outputSplitter to noiseDevice inputs 3 and 4 (Stereo from musicDevice)
|
||||
outputSplitter.connect(noiseDevice, 0, 3) // 1 channel: Left from musicDevice
|
||||
outputSplitter.connect(noiseDevice, 1, 4) // 1 channel: Right from musicDevice
|
||||
|
||||
// Assuming noiseDevice outputs stereo, connect to noiseGain (Stereo)
|
||||
noiseDevice.connect(noiseGain) // Assuming noiseDevice outputs stereo, connected to both channels of noiseGain
|
||||
|
||||
// Assuming you want to merge and output both processed signals (Stereo from both musicGain and noiseGain)
|
||||
// Connect noiseGain to the first two inputs of the merger (Stereo)
|
||||
noiseGain.connect(merger, 0, 0) // 1 channel: Left
|
||||
noiseGain.connect(merger, 0, 1) // 1 channel: Right
|
||||
|
||||
// Connect musicGain to the last two inputs of the merger (Stereo)
|
||||
musicGain.connect(merger, 0, 2) // 1 channel: Left
|
||||
musicGain.connect(merger, 0, 3) // 1 channel: Right
|
||||
|
||||
// Finally, connect the merger to the audio context's destination (Stereo to output)
|
||||
merger.connect(merger.context.destination)
|
||||
},
|
||||
handlePlayingUpdate (isPlaying) {
|
||||
const ContextClass = (window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext || window.msAudioContext)
|
||||
let audioContext = this.audioContext ? this.audioContext : new ContextClass()
|
||||
let microphoneSource = null
|
||||
// useNuxtApp().$logger.log('Handling playing update = ' + isPlaying) // true when playing
|
||||
if (isPlaying) {
|
||||
// Web Audio API is available.
|
||||
// const musicAudioComponent = this.$refs[this.currentElement.title].audioElement
|
||||
if (this.areAllNodesAvailable()) {
|
||||
this.disconnectNodes()
|
||||
// reconnecting because all Nodes are there
|
||||
this.connectNodes()
|
||||
this.createdNodes.noiseGain.gain.exponentialRampToValueAtTime(1.0, audioContext.currentTime + 2)
|
||||
this.createdNodes.noiseGain.gain.exponentialRampToValueAtTime(1.0, audioContext.currentTime + 3)
|
||||
// useNuxtApp().$logger.log('Connected everything because it was there already')
|
||||
} else {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false,
|
||||
autoGainControl: false
|
||||
},
|
||||
video: false
|
||||
})
|
||||
.then((micStream) => {
|
||||
audioContext = new ContextClass()
|
||||
microphoneSource = audioContext.createMediaStreamSource(micStream)
|
||||
this.createdNodes.microphoneSource ||= microphoneSource
|
||||
// Return both audioContext and microphoneSource to the next link in the chain
|
||||
// useNuxtApp().$logger.log('Step 1 Micstream created')
|
||||
return { audioContext, microphoneSource }
|
||||
})
|
||||
.then(({ audioContext, microphoneSource }) => {
|
||||
// First RNBO device creation
|
||||
return createRNBODevice(audioContext, this.noisePatch).then((noiseRNBODevice) => {
|
||||
this.createdNodes.noiseRNBODevice ||= noiseRNBODevice
|
||||
// Return al.then(() => {hen(() => necessary objects for the next step
|
||||
// useNuxtApp().$logger.log('Step 2 AudioContext, NoiseDevice und MicSource created')
|
||||
return { audioContext, microphoneSource, noiseRNBODevice }
|
||||
})
|
||||
})
|
||||
.then(({ audioContext, microphoneSource, noiseRNBODevice }) => {
|
||||
// Second RNBO device creation
|
||||
return createRNBODevice(audioContext, this.musicPatch).then((musicRNBODevice) => {
|
||||
// Return all necessary objects for the final setup
|
||||
this.createdNodes.musicRNBODevice ||= musicRNBODevice
|
||||
// useNuxtApp().$logger.log('Step 3 AudioContext, NoiseDevice, musicDevice und MicSource created')
|
||||
return { audioContext, microphoneSource, noiseRNBODevice, musicRNBODevice }
|
||||
})
|
||||
}).then(({ audioContext, microphoneSource, noiseRNBODevice, musicRNBODevice }) => {
|
||||
// Ensure this.createdNodes is initialized
|
||||
this.createdNodes ||= {}
|
||||
// Assign nodes if they don't exist
|
||||
this.createdNodes.microphoneSource ||= microphoneSource
|
||||
this.createdNodes.musicDevice ||= musicRNBODevice.node
|
||||
this.createdNodes.noiseDevice ||= noiseRNBODevice.node
|
||||
this.createdNodes.noiseSource ||= audioContext.createMediaElementSource(this.$refs.Noise.$refs.audioElement)
|
||||
this.createdNodes.musicSource ||= audioContext.createMediaElementSource(this.$refs[this.currentElement.title].$refs.audioElement)
|
||||
this.createdNodes.musicSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.noiseSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.outputSplitter ||= audioContext.createChannelSplitter(2)
|
||||
this.createdNodes.musicGain ||= audioContext.createGain()
|
||||
this.createdNodes.noiseGain ||= audioContext.createGain()
|
||||
this.createdNodes.merger ||= audioContext.createChannelMerger(4)
|
||||
|
||||
this.createdNodes.musicGain.gain.value = 0.001
|
||||
this.createdNodes.noiseGain.gain.value = 0.001 // macht nichts
|
||||
this.connectNodes()
|
||||
this.createdNodes.musicGain.gain.exponentialRampToValueAtTime(0.5, audioContext.currentTime + 3)
|
||||
this.createdNodes.noiseGain.gain.exponentialRampToValueAtTime(0.5, audioContext.currentTime + 4)
|
||||
})
|
||||
.catch((_error) => {
|
||||
// this.disconnectNodes()
|
||||
if (this.audioContext) {
|
||||
this.audioContext.destination.disconnect()
|
||||
// audioContext.destination = null
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.disconnectNodes()
|
||||
}
|
||||
},
|
||||
disconnectNodes () {
|
||||
// Ensure this.createdNodes is defined and is an object
|
||||
if (this.createdNodes !== null) {
|
||||
Object.values(this.createdNodes).forEach((node) => {
|
||||
// Check if the node exists and has a disconnect method
|
||||
if (node && typeof node.disconnect === 'function') {
|
||||
node.disconnect()
|
||||
}
|
||||
// Additional handling for specific types of nodes, if necessary
|
||||
// For example, stopping an OscillatorNode
|
||||
if (node instanceof OscillatorNode) {
|
||||
node.stop()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.rnboplayer {
|
||||
position: fixed;
|
||||
width: 220px; /* Or specify a fixed width like 220px if you prefer */
|
||||
max-width: 220px; /* This line might be redundant depending on your width strategy */
|
||||
height: 100px;
|
||||
display: inline-grid;
|
||||
z-index: 2;
|
||||
bottom: 11%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
/* The switch - the box around the slider */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
/* Hide default HTML checkbox */
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* The slider */
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px #2196F3;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(26px);
|
||||
-ms-transform: translateX(26px);
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
</style>
|
Reference in New Issue
Block a user