Initial commit

This commit is contained in:
Mindboost
2025-07-01 10:53:26 +00:00
commit 38050e5c69
416 changed files with 48708 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
export function createMockMediaStream (): MediaStream {
// Create an empty MediaStream
const mockStream = new MediaStream()
// Create a mock audio track using the Web Audio API
const audioContext = new AudioContext()
const oscillator = audioContext.createOscillator()
const dst = audioContext.createMediaStreamDestination()
oscillator.connect(dst)
oscillator.start()
// Add the audio track from the destination to the stream
dst.stream.getAudioTracks().forEach((track) => {
mockStream.addTrack(track)
})
// You can now return this mockStream as a simulated microphone MediaStream
return mockStream
}

View File

@@ -0,0 +1,117 @@
<template>
<div class="noise-controlled-band">
<AudioTag
:ref="el => audioElement"
:src="audioSrc"
:volume="volume"
:play="playing"
@canplay="onCanPlay"
/>
<RNBOControlValue
:center-frequency="centerFrequency"
:status="playing"
:q-factor="$props.qFactor"
:attack="masterAttack"
:release="masterRelease"
@control-value-change="handleValueChange"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
import AudioTag from '../../AudioTag.vue'
import RNBOControlValue from '../..//tests/ControlValues/RNBOControlValue.vue'
import tracksConfig from '~/tracks.config'
import { useAudioStore } from '~/stores/audio'
import { calculateNormalizedVolume } from '~/lib/AudioFunctions'
export default defineComponent({
name: 'NoiseControlledBand',
components: {
AudioTag,
RNBOControlValue
},
props: {
centerFrequency: {
type: Number,
required: true
},
qFactor: {
type: Number,
required: true
},
masterAttack: {
type: Number,
default: 120000,
required: false
},
masterRelease: {
type: Number,
default: 144000,
required: false
}
},
emits: ['ready', 'update:mid-volume'],
setup (props, { emit }) {
const audioElement = ref<InstanceType<typeof HTMLElement> | null>(null)
const playing = computed(() => { return useAudioStore().playing }) // the playing state is bind to the audioStore
const gainValueDB = ref(0)
const volume = ref(0)
const audioSrc = computed(() => {
try {
const frequency = props.centerFrequency
let band = ''
if (frequency < 500) {
band = 'low_band'
} else if (frequency >= 500 && frequency < 4000) {
band = 'mid_band'
} else {
band = 'high_band'
}
const path = `/masking/3bands/${band}_256kbps.webm`
const fullPath = `${window.location.origin}${encodeURI(path)}`
useNuxtApp().$logger.info('Loading audio track:', fullPath)
return fullPath
} catch (error) {
useNuxtApp().$logger.error('Error loading audio track:', error)
return ''
}
})
const handleValueChange = (data: { frequency: number; value: number }) => {
// Convert dB to linear scale
gainValueDB.value = calculateNormalizedVolume(data.value)
volume.value = gainValueDB.value
emit('update:mid-volume', volume.value)
}
const onCanPlay = () => {
emit('ready', props.centerFrequency)
}
return {
audioElement,
audioSrc,
gainValueDB,
volume,
handleValueChange,
onCanPlay,
playing
}
}
})
</script>
<style scoped>
.noise-controlled-band {
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
}
</style>

View File

@@ -0,0 +1,105 @@
<template>
<div class="noise-controlled-band">
<AudioTag
:ref="el => audioElement"
:src="audioSrc"
:volume="volume"
:play="playing"
@canplay="onCanPlay"
/>
<RNBOControlValue
:center-frequency="centerFrequency"
:status="playing"
:attack="masterAttack"
:release="masterRelease"
@control-value-change="handleValueChange"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
import AudioTag from '../../AudioTag.vue'
import RNBOControlValue from '../../tests/ControlValues/RNBOControlValue.vue'
import tracksConfig from '~/tracks.config'
import { useAudioStore } from '~/stores/audio'
import { calculateNormalizedVolume } from '~/lib/AudioFunctions'
export default defineComponent({
name: 'NoiseControlledBand',
components: {
AudioTag,
RNBOControlValue
},
props: {
centerFrequency: {
type: Number,
required: true
},
masterAttack: {
type: Number,
default: 120000,
required: false
},
masterRelease: {
type: Number,
default: 144000,
required: false
}
},
emits: ['ready'],
setup (props, { emit }) {
const audioElement = ref<InstanceType<typeof HTMLElement> | null>(null)
const playing = computed(() => { return useAudioStore().playing }) // the playing state is bind to the audioStore
const gainValueDB = ref(0)
const volume = ref(0)
const audioSrc = computed(() => {
try {
const frequencyKey = `${props.centerFrequency}_src`
useNuxtApp().$logger.log('Loading audio track:', frequencyKey)
const trackPath = tracksConfig[frequencyKey as keyof typeof tracksConfig]
if (!trackPath) {
throw new Error(`No track found for frequency ${props.centerFrequency}`)
}
const returnValue = `${window.location.origin}${encodeURI(trackPath)}`
// Check if the audio file is available
return returnValue
} catch (error) {
useNuxtApp().$logger.error('Error loading audio track:', error)
return ''
}
})
const handleValueChange = (data: { frequency: number; value: number }) => {
// Convert dB to linear scale
gainValueDB.value = calculateNormalizedVolume(data.value)
volume.value = gainValueDB.value
}
const onCanPlay = () => {
useNuxtApp().$logger.log(`Audio for frequency ${props.centerFrequency} is ready to play`)
emit('ready', props.centerFrequency)
}
return {
audioElement,
audioSrc,
gainValueDB,
volume,
handleValueChange,
onCanPlay,
playing
}
}
})
</script>
<style scoped>
.noise-controlled-band {
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
}
</style>

View File

@@ -0,0 +1,121 @@
<template>
<div v-show="false" id="hiddenAudio">
<AudioTagWebAudio
ref="audioElement"
:src="audioSrc"
:volume="volume"
:play="playing"
:master-gain="masterGain"
@canplay="onCanPlay"
/>
<div class="noise-controlled-band">
Gain: {{ gainValueDB }}
<RNBOControlValue
:center-frequency="centerFrequency"
:status="playing"
:attack="masterAttack"
:release="masterRelease"
:q-factor="qFactor"
@control-value-change="handleValueChange"
/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
import type { Logger } from 'pino'
import RNBOControlValue from '../../tests/ControlValues/RNBOControlValue.vue'
import AudioTagWebAudio from '../../AudioTagWebAudio.vue'
import { calculateNormalizedVolume } from '~/lib/AudioFunctions'
import { useAudioStore } from '~/stores/audio'
export default defineComponent({
name: 'NoiseControlledWebAudio3Band',
components: {
AudioTagWebAudio,
RNBOControlValue
},
props: {
centerFrequency: {
type: Number,
required: true
},
masterGain: {
type: GainNode,
required: true
},
masterAttack: {
type: Number,
default: 120000 * 2,
required: false
},
masterRelease: {
type: Number,
default: 144000 / 1000,
required: false
},
qFactor: {
type: Number,
required: true
}
},
emits: ['ready'],
setup (props, { emit }) {
const audioElement = ref<InstanceType<typeof HTMLElement> | null>(null)
const gainValueDB = ref(0)
const volume = ref(1)
const playing = computed(() => { return useAudioStore().getPlaying }) // the playing state is bind to the audioStore
const logger = useNuxtApp().$logger as Logger
const audioSrc = computed(() => {
try {
const frequency = props.centerFrequency
let band = ''
if (frequency < 500) {
band = 'low_band'
} else if (frequency >= 500 && frequency < 4000) {
band = 'mid_band'
} else {
band = 'high_band'
}
const path = `/masking/3bands/${band}_256kbps.webm`
const fullPath = `${window.location.origin}${encodeURI(path)}`
return fullPath
} catch (error) {
return ''
}
})
const handleValueChange = (data: { frequency: number; value: number }) => {
// Convert dB to linear scale
gainValueDB.value = calculateNormalizedVolume(data.value)
volume.value = gainValueDB.value
}
const onCanPlay = () => {
logger.info(`Audio for frequency ${props.centerFrequency} is ready to play`)
emit('ready', props.centerFrequency)
}
return {
audioElement,
audioSrc,
gainValueDB,
volume,
handleValueChange,
onCanPlay,
playing
}
}
})
</script>
<style scoped>
.noise-controlled-band {
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
}
</style>

View File

@@ -0,0 +1,111 @@
<template>
<div class="noise-controlled-band">
<AudioTagWebAudio
ref="audioElement"
:src="audioSrc"
:volume="volume"
:play="playing"
:master-gain="masterGain"
@canplay="onCanPlay"
/>
Gain: {{ gainValueDB }}
<RNBOControlValue
:center-frequency="centerFrequency"
:status="playing"
:attack="masterAttack"
:release="masterRelease"
@control-value-change="handleValueChange"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
import type { Logger } from 'pino'
import RNBOControlValue from '../..//tests/ControlValues/RNBOControlValue.vue'
import AudioTagWebAudio from '../../AudioTagWebAudio.vue'
import tracksConfig from '~/tracks.config'
import { calculateNormalizedVolume } from '~/lib/AudioFunctions'
import { useAudioStore } from '~/stores/audio'
export default defineComponent({
name: 'NoiseControlledWebAudioBand',
components: {
AudioTagWebAudio,
RNBOControlValue
},
props: {
centerFrequency: {
type: Number,
required: true
},
masterGain: {
type: GainNode,
required: true
},
masterAttack: {
type: Number,
default: 120000,
required: false
},
masterRelease: {
type: Number,
default: 144000,
required: false
}
},
emits: ['ready'],
setup (props, { emit }) {
const audioElement = ref<InstanceType<typeof HTMLElement> | null>(null)
const gainValueDB = ref(0)
const volume = ref(1)
const playing = computed(() => { return useAudioStore().playing }) // the playing state is bind to the audioStore
const logger = useNuxtApp().$logger as Logger
const audioSrc = computed(() => {
try {
const frequencyKey = `${props.centerFrequency}_src`
logger.info('Loading audio track:', frequencyKey)
const trackPath = tracksConfig[frequencyKey as keyof typeof tracksConfig]
if (!trackPath) {
throw new Error(`No track found for frequency ${props.centerFrequency}`)
}
const returnValue = `${window.location.origin}${encodeURI(trackPath)}`
// Check if the audio file is available
return returnValue
} catch (error) {
logger.error('Error loading audio track:', error)
return ''
}
})
const handleValueChange = (data: { frequency: number; value: number }) => {
// Convert dB to linear scale
gainValueDB.value = calculateNormalizedVolume(data.value)
volume.value = gainValueDB.value
}
const onCanPlay = () => {
logger.info(`Audio for frequency ${props.centerFrequency} is ready to play`)
emit('ready', props.centerFrequency)
}
return {
audioElement,
audioSrc,
gainValueDB,
volume,
handleValueChange,
onCanPlay,
playing
}
}
})
</script>
<style scoped>
.noise-controlled-band {
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
}
</style>

View File

@@ -0,0 +1,316 @@
<!--
RNBOControlValue ist eine Komponente zur Steuerung und Überwachung von Audio-Parametern,
insbesondere für die Kontrolle von Frequenzbändern. Sie ermöglicht das Testen von
Audio-Geräten, die Anpassung von Parametern und die Anzeige von Kontrollwerten in Echtzeit.
-->
<template>
<div v-if="true">
<h2>Control Values Device Test -- {{ centerFrequency }}Hz</h2>
<button @click="testControlValuesDevice">Test Control Values Device</button>
<p v-if="testResult">{{ testResult }}</p>
<div class="microphone-info">
<h3>Current Microphone:</h3>
<p>{{ currentMicrophone || 'No microphone selected' }}</p>
</div>
<table v-if="parameters.length > 0" class="parameter-table">
<thead>
<tr>
<th>Parameter Name</th>
<th>Value</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-for="param in parameters" :key="param.name">
<td>{{ param.name }}</td>
<td>
<input
:value="param.value"
type="number"
@input="updateParameter(param.name, $event.target?.value)"
>
</td>
<td>
<input
:value="param.value"
type="range"
:min="param.min"
:max="param.max"
:step="param.step"
@input="updateParameter(param.name, $event.target?.value)"
>
</td>
</tr>
</tbody>
</table>
<!-- Neue Keynote für den Outlet-Wert -->
<div class="outlet-keynote">
<h3>Control Value</h3>
<div class="outlet-value">{{ formatValue(outletValue) }}</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { mapActions, mapState } from 'pinia'
import { useDevicesStore } from '~/stores/device'
import { useMicStore } from '~/stores/microphone'
import { useAudioStore } from '~/stores/audio'
interface ParameterData {
name: string;
paramId: string;
value: number;
min: number;
max: number;
step: number;
}
const minSamples = 48
const maxSamples = 1920000
export default defineComponent({
name: 'RNBOControlValue',
props: {
centerFrequency: {
type: Number,
required: true,
validator: (value: number) => [63, 125, 250, 500, 1000, 1500, 2000, 4000, 8000, 16000, 150].includes(value)
},
qFactor: {
type: Number,
default: 1.414,
validator: (value: number) => value > 0.1 && value < 1.5
},
attack: {
type: Number,
default: 120000,
validator: (value:number) => value >= minSamples && value <= maxSamples
},
release: {
type: Number,
default: 144000,
validator: (value:number) => value >= minSamples && value <= maxSamples
},
status: {
type: Boolean,
default: false
}
},
emits: ['control-value-change'],
data () {
return {
testResult: '',
parameters: [] as ParameterData[],
device: null as any,
outletValue: 0,
currentMicrophone: ''
}
},
computed: {
...mapState(useMicStore, ['microphone'])
},
watch: {
release (value) {
this.updateParameter('release', '' + value)
},
attack (value) {
if (!isNaN(value)) {
this.updateParameter('attack', '' + value)
}
},
status (newStatus) {
const microphoneStore = useMicStore()
// status is bind to playing status in audio store
if (newStatus) {
microphoneStore.attachMicrophone()
this.testControlValuesDevice()
} else {
microphoneStore.detachMicrophone()
}
}
},
mounted () {
this.updateParameter('centerFrequency', '' + this.centerFrequency)
this.updateParameter('release', '' + this.release)
this.updateParameter('attack', '' + this.attack)
this.updateParameter('qFactor', String(this.qFactor))
},
methods: {
async getCurrentMicrophone () {
const micStore = useMicStore()
try {
const micro = await micStore.getMicrophone()
if (micro?.microphoneNode.label) {
this.currentMicrophone = micro.microphoneNode.label
} else {
this.currentMicrophone = 'No microphone detected'
}
} catch (error) {
useNuxtApp().$logger.error('Error getting microphone:', error)
this.currentMicrophone = 'Error detecting microphone'
}
},
async testControlValuesDevice () {
const deviceStore = useDevicesStore()
const micStore = useMicStore()
const audioStore = useAudioStore()
await audioStore.getContext()
if (!audioStore) { return }
if (audioStore.audioContext?.state !== 'running') {
await audioStore.audioContext?.resume()
}
this.device = await deviceStore.createControlValuesDevice(`testControlValues_${this.centerFrequency}Hz`, this.centerFrequency)
if (!this.device) {
this.testResult = `Failed to create control values device for ${this.centerFrequency}Hz`
// this.$logger.error('Device creation failed')
}
const device = this.device
// this.$logger.info(`Created device for ${this.centerFrequency}Hz`, { device })
const microphone = await micStore.getMicrophone()
try {
const micSource = microphone.microphoneNode
if (micSource && this.device.node && audioStore.audioContext) {
micSource.connect(this.device.node)
await this.updateParameterList()
await new Promise(resolve => setTimeout(resolve, 100))
this.setupOutletListener()
this.testResult = `Control values device for ${this.centerFrequency}Hz created successfully`
} else {
this.testResult = `Failed to connect microphone to device for ${this.centerFrequency}Hz`
// this.$logger.error('Connection failed:', { micSource, deviceNode: this.device.node, audioContext: audioStore.audioContext })
}
} catch (error) {
// this.$logger.error(`Test failed for ${this.centerFrequency}Hz:`, error)
this.testResult = `Test failed for ${this.centerFrequency}Hz: ${error instanceof Error ? error.message : String(error)}`
// this.$logger.error(this.testResult)
}
},
setupOutletListener () {
try {
if (!this.device) {
// this.$logger.warn('Device is not available')
return
}
this.device.messageEvent.subscribe((ev: any) => {
if (ev.tag === 'out1') {
const newValue = this.ensureNumber(ev.payload)
this.outletValue = newValue
if (!(newValue > -13 && newValue < -12.98)) {
this.$emit('control-value-change', { frequency: this.centerFrequency, value: newValue })
}
}
})
} catch (error: unknown) {
// this.$logger.error(`Test failed for ${this.centerFrequency}Hz:`, error)
this.testResult = `Test failed for ${this.centerFrequency}Hz: ${error instanceof Error ? error.message : String(error)}`
}
},
updateParameterList () {
// this.$logger.info('Updating parameter list', this.device)
if (this.device && this.device.parameters) {
const params = this.device.parameters
// this.$logger.info('Parameters:', { params })
this.parameters = Array.from(params.entries()).map(([name, param]: [string, any]) => {
return {
name: param.name,
value: this.ensureNumber(param.value),
min: this.ensureNumber(param.min),
max: this.ensureNumber(param.max),
step: this.ensureNumber(param.step) || 0.01
}
})
// this.$logger.info('Updated parameters:', this.parameters)
} else {
// this.$logger.info('No parameters found')
}
},
updateParameter (name: string, value: string) {
if (this.device && this.device.parameters) {
const numValue = this.ensureNumber(value)
const param = this.parameters.find(p => p.name === name)
if (param) {
param.value = numValue
if (typeof this.device.parameters.set === 'function') {
this.device.parameters.set(name, numValue)
} else if (Array.isArray(this.device.parameters)) {
const deviceParam = this.device.parameters.find((p: any) => p.name === name)
if (deviceParam) {
deviceParam.value = numValue
}
} else if (typeof this.device.parameters === 'object') {
if (this.device.parameters[name]) {
this.device.parameters[name].value = numValue
}
}
this.$forceUpdate()
}
} else {
// Device noch nicht da → update nur in local parameters
const param = this.parameters.find(p => p.name === name)
if (param) {
param.value = this.ensureNumber(value)
} else {
this.parameters.push({ name, value: this.ensureNumber(value) })
}
this.$forceUpdate()
}
},
ensureNumber (value: any): number {
const num = Number(value)
return isNaN(num) ? 0 : num
},
formatValue (value: number): string {
return value.toFixed(2)
}
}
})
</script>
<style scoped>
.parameter-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.parameter-table th, .parameter-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
.parameter-table th {
background-color: #f2f2f2;
}
.parameter-table input[type="range"] {
width: 100%;
}
.outlet-keynote {
text-align: center;
margin-top: 30px;
}
.outlet-keynote h3 {
font-size: 1.2em;
margin-bottom: 10px;
}
.outlet-value {
font-size: 2em;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,75 @@
<template>
<div v-show="false">
<h1>Microphone</h1>
<button @click="attach">
{{ microphoneActive ? 'Mikrofon trennen' : 'Mikrofon aktivieren' }}
</button>
</div>
</template>
<script lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { useAudioStore } from '~/stores/audio'
import { useMicStore } from '~/stores/microphone'
export default {
name: 'MicrophoneHandler',
emits: ['update:attach'],
setup (_props, { emit }) {
const audioStore = useAudioStore()
const microphone = ref<Promise<MediaStream> | null>(null)
const microphoneActive = ref(false)
const attach = () => {
if (!microphone.value) {
microphone.value = navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: false,
noiseSuppression: false,
autoGainControl: false
},
video: false
})
microphoneActive.value = true
}
return microphone.value.then((stream) => {
// useNuxtApp().$logger.log('stream there' + stream)
emit('update:attach', stream)
return stream
})
}
const detach = async () => {
if (microphone.value) {
try {
const stream = await microphone.value
stream.getTracks().forEach(track => track.stop())
} catch (error) {
}
microphone.value = null
microphoneActive.value = false
}
}
watch(() => audioStore.playing, (newValue) => {
if (newValue) {
attach()
} else {
detach()
}
})
onUnmounted(() => {
// Clean up by detaching the microphone when the component is unmounted
detach()
})
// Return the public properties and methods
return {
attach,
detach,
isPlaying: () => audioStore.playing,
microphoneActive
}
}
}
</script>

View File

@@ -0,0 +1,83 @@
<template>
<h1>Test Version NoiseMusicGain: mit WebAudio & Gain, ohne Noise-Patch & ohne Music-Patch</h1>
<AudioElement
ref="Noise"
key="5"
:src="noise_src"
title="Noise"
@update:volume="updateNoiseGain"
>
<template #default="{}">
<div class="icon">
<img style="width: 25px" src="~/assets/image/noiseicon.svg">
</div>
</template>
</AudioElement>
<AudioElement
ref="Music"
key="1"
:src="forest_src"
title="Forest"
@update:volume="updateMusicGain"
@update:playing="handlePlayingUpdate"
>
<template #default="{ }">
<div class="icon">
<!-- tropic icon -->
<img style="width: 25px" src="~/assets/image/musicicon.svg">
</div>
</template>
<!-- Slot content for AudioElement, if needed -->
</AudioElement>
</template>
<script lang="ts">
import AudioElement from '../AudioElement.vue'
import { useAudioStore } from '../../../stores/audio'
export default {
name: 'NoiseMusicGain',
components: { AudioElement },
data () {
return {
audioContext: useAudioStore().getContext(),
playing: false,
paused: false,
createdNodes: {} as any,
noise_src: window.location.origin + useRuntimeConfig().public.noise_src,
forest_src: window.location.origin + useRuntimeConfig().public.tracks.forest_src
}
},
methods: {
handlePlayingUpdate () {
const noiseElement = this.$refs.Noise as typeof AudioElement
const noiseAudioElement = noiseElement.$refs.audioElement as HTMLMediaElement
const musicElement = this.$refs.Music as typeof AudioElement
const musicAudioElement = musicElement.$refs.audioElement as HTMLMediaElement
const audioContext = this.audioContext
const destination = this.audioContext.destination
this.createdNodes.musicGain ||= audioContext.createGain()
this.createdNodes.noiseGain ||= audioContext.createGain()
this.createdNodes.noiseSource = audioContext.createMediaElementSource(noiseAudioElement)
this.createdNodes.musicSource = audioContext.createMediaElementSource(musicAudioElement)
this.createdNodes.noiseSource.connect(this.createdNodes.noiseGain)
this.createdNodes.musicSource.connect(this.createdNodes.musicGain)
this.createdNodes.noiseGain.connect(destination)
this.createdNodes.musicGain.connect(destination)
musicAudioElement.muted = false
noiseAudioElement.muted = false
},
updateNoiseGain (volume: number) {
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(volume, this.createdNodes.noiseGain.context.currentTime + 0.30)
},
updateMusicGain (volume: number) {
this.createdNodes.musicGain.gain.linearRampToValueAtTime(volume, this.createdNodes.musicGain.context.currentTime + 0.30)
}
}
}
</script>

View File

@@ -0,0 +1,99 @@
<template>
<h1>Test Version NoiseMusicGain and FadeIn: mit WebAudio & Gain, ohne Noise-Patch & ohne Music-Patch</h1>
<h2> Obwohl die Methode linearRampToValueAtTime verwendet wird, startet das audio einfach nach der eingestellten Zeit ohne fade</h2>
<button @click="fadeInGains">
Trigger FadeIn
</button>
<button @click="fadeOutGains">
Trigger FadeOut
</button>
<AudioElement
ref="Noise"
key="5"
:src="noise_src"
title="Noise"
@update:volume="updateNoiseGain"
>
<template #default="{}">
<img style="width: 25px" src="~/assets/image/noiseicon.svg">
</template>
</AudioElement>
<AudioElement
ref="Music"
key="1"
:src="forest_src"
title="Forest"
@update:volume="updateMusicGain"
@update:playing="handlePlayingUpdate"
>
<template #default="{ }">
<img style="width: 25px" src="~/assets/image/musicicon.svg">
</template>
</AudioElement>
</template>
<script lang="ts">
import AudioElement from '../AudioElement.vue'
import { useAudioStore } from '../../../stores/audio'
export default {
name: 'NoiseMusicGain',
components: { AudioElement },
data () {
return {
audioContext: useAudioStore().getContext(),
playing: false,
paused: false,
createdNodes: {} as any,
forest_src: window.location.origin + useRuntimeConfig().public.tracks.forest_src as string,
noise_src: window.location.origin + useRuntimeConfig().public.noise_src as string
}
},
methods: {
handlePlayingUpdate () {
const noiseElement = this.$refs.Noise as typeof AudioElement
const noiseAudioElement = noiseElement.$refs.audioElement as HTMLMediaElement
const musicElement = this.$refs.Music as typeof AudioElement
const musicAudioElement = musicElement.$refs.audioElement as HTMLMediaElement
const audioContext = this.audioContext
const destination = this.audioContext.destination
this.createdNodes.musicGain ||= audioContext.createGain()
this.createdNodes.noiseGain ||= audioContext.createGain()
this.createdNodes.musicGain.gain.setValueAtTime(0, audioContext.currentTime)
this.createdNodes.noiseGain.gain.setValueAtTime(0, audioContext.currentTime)
musicAudioElement.muted = false
noiseAudioElement.muted = false
this.createdNodes.noiseSource = audioContext.createMediaElementSource(noiseAudioElement)
this.createdNodes.musicSource = audioContext.createMediaElementSource(musicAudioElement)
this.createdNodes.noiseSource.connect(this.createdNodes.noiseGain)
this.createdNodes.musicSource.connect(this.createdNodes.musicGain)
this.createdNodes.noiseGain.connect(destination)
this.createdNodes.musicGain.connect(destination)
this.fadeInGains()
},
fadeInGains () {
const noiseGain = this.createdNodes.noiseGain
const musicGain = this.createdNodes.musicGain
noiseGain.gain.linearRampToValueAtTime(1.0, noiseGain.context.currentTime + 7)
musicGain.gain.linearRampToValueAtTime(1.0, musicGain.context.currentTime + 5)
},
fadeOutGains () {
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(0, this.createdNodes.noiseGain.context.currentTime + 3)
this.createdNodes.musicGain.gain.linearRampToValueAtTime(0, this.createdNodes.musicGain.context.currentTime + 3)
},
updateNoiseGain (volume: number) {
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(volume, this.createdNodes.noiseGain.context.currentTime + 0.30)
},
updateMusicGain (volume: number) {
this.createdNodes.musicGain.gain.linearRampToValueAtTime(volume, this.createdNodes.musicGain.context.currentTime + 0.30)
}
}
}
</script>

View File

@@ -0,0 +1,90 @@
<template>
<h1>Test Version NoiseMusicGain: mit WebAudio & Gain, ohne Noise-Patch & ohne Music-Patch</h1>
<MicrophoneHandler ref="Microphone" @update:attach="setupMicrophone" />
<AudioElement
ref="Noise"
key="5"
:src="noise_src"
title="Noise"
@update:volume="updateNoiseGain"
>
<template #default="{}">
<div class="icon">
<img style="width: 25px" src="~/assets/image/noiseicon.svg">
</div>
</template>
</AudioElement>
<AudioElement
ref="Music"
key="1"
:src="forest_src"
title="Forest"
@update:volume="updateMusicGain"
@update:playing="handlePlayingUpdate"
>
<template #default="{ }">
<div class="icon">
<!-- tropic icon -->
<img style="width: 25px" src="~/assets/image/musicicon.svg">
</div>
</template>
<!-- Slot content for AudioElement, if needed -->
</AudioElement>
</template>
<script lang="ts">
import AudioElement from '../AudioElement.vue'
import { useAudioStore } from '../../../stores/audio'
import MicrophoneHandler from '../tests/Microphone.vue'
export default {
name: 'NoiseMusicGain',
components: { AudioElement, MicrophoneHandler },
data () {
return {
audioContext: useAudioStore().getContext(),
playing: false,
paused: false,
createdNodes: {} as any,
forest_src: window.location.origin + useRuntimeConfig().public.tracks.forest_src as string,
noise_src: window.location.origin + useRuntimeConfig().public.noise_src as string
}
},
methods: {
setupMicrophone (stream:MediaStream) {
// useNuxtApp().$logger.log('setupMicrophone got this stream', { stream })
this.createdNodes.microphone = this.audioContext.createMediaStreamSource(stream)
this.createdNodes.microphone.connect(this.audioContext.destination)
},
handlePlayingUpdate () {
const noiseElement = this.$refs.Noise as typeof AudioElement
const noiseAudioElement = noiseElement.$refs.audioElement as HTMLMediaElement
const musicElement = this.$refs.Music as typeof AudioElement
const musicAudioElement = musicElement.$refs.audioElement as HTMLMediaElement
const audioContext = this.audioContext
const destination = this.audioContext.destination
this.createdNodes.musicGain ||= audioContext.createGain()
this.createdNodes.noiseGain ||= audioContext.createGain()
this.createdNodes.noiseSource = audioContext.createMediaElementSource(noiseAudioElement)
this.createdNodes.musicSource = audioContext.createMediaElementSource(musicAudioElement)
this.createdNodes.noiseSource.connect(this.createdNodes.noiseGain)
this.createdNodes.musicSource.connect(this.createdNodes.musicGain)
this.createdNodes.noiseGain.connect(destination)
this.createdNodes.musicGain.connect(destination)
musicAudioElement.muted = false
noiseAudioElement.muted = false
},
updateNoiseGain (volume: number) {
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(volume, this.createdNodes.noiseGain.context.currentTime + 0.30)
},
updateMusicGain (volume: number) {
this.createdNodes.musicGain.gain.linearRampToValueAtTime(volume, this.createdNodes.musicGain.context.currentTime + 0.30)
}
}
}
</script>

View File

@@ -0,0 +1,220 @@
<template>
<h1>Test Version NoiseMusicGain: mit WebAudio & Gain und PlayPause, ohne Noise-Patch & ohne Music-Patch</h1>
<h2>Play State: {{ playing }} </h2>
<p>
The method refreshAudioContext helps to get the ressources free when we stop playing the audio
// without it would be louder each time we start playing
</p>
<p>
Whenever I view this page the audio starts playing, when I hit 'space' it fades out within 2seconds
when i start playing again nothing happens... I would expect playing.
</p>
<div v-if="createdNodes.musicGain">
{{ musicGain }}
</div>
<div v-else>
No MusicGain
</div>
<div v-if="createdNodes.noiseGain">
{{ noiseGain }}
</div>
<div v-else>
No noiseGain
</div>
<AudioElement
ref="Noise"
key="5"
:src="noise_src"
title="Noise"
@update:volume="updateNoiseGain"
@update:loaded="noiseReady=true"
>
<template #default="{}">
<div class="icon">
<img style="width: 25px" src="~/assets/image/noiseicon.svg">
</div>
</template>
</AudioElement>
<AudioElement
ref="Music"
key="1"
:src="forest_src"
title="Forest"
@update:volume="updateMusicGain"
@update:playing="handlePlayingUpdate"
@update:fadeout="fadeOutGains"
@update:loaded="musicReady=true"
>
<template #default="{ }">
<div class="icon">
<!-- tropic icon -->
<img style="width: 25px" src="~/assets/image/musicicon.svg">
</div>
</template>
<!-- Slot content for AudioElement, if needed -->
</AudioElement>
</template>
<script lang="ts">
import AudioElement from '../AudioElement2.vue'
import { useAudioStore } from '../../../stores/audio'
export default {
name: 'NoiseMusicGain',
components: { AudioElement },
data () {
return {
audioContext: useAudioStore().getContext(),
playing: false,
paused: false,
createdNodes: {} as any,
fading: false,
noiseReady: false,
musicReady: false,
musicGain: 0,
noiseGain: 0,
forest_src: window.location.origin + useRuntimeConfig().public.tracks.forest_src as string,
noise_src: window.location.origin + useRuntimeConfig().public.noise_src as string
}
},
mounted () {
this.monitorGainNodes()
},
methods: {
monitorGainNodes () {
// This could be an interval or a direct method call in your gain changing methods
setInterval(() => {
if (this.createdNodes.musicGain) {
this.musicGain = this.createdNodes.musicGain.gain.value
}
if (this.createdNodes.noiseGain) {
this.noiseGain = this.createdNodes.noiseGain.gain.value
}
}, 100) // Update every 100 ms, adjust interval as necessary
},
// This method helps to get the ressources free when we stop playing the audio
// without it would be louder each time we start playing
refreshAudioContext () {
const newAudioContext = new AudioContext()
this.audioContext.close()
useAudioStore().audioContext = newAudioContext
this.audioContext = useAudioStore().getContext()
},
fadeOutGains () {
// useNuxtApp().$logger.log('Fade OUT Gains')
// Define the duration of the fade out
const fadeDuration = 2.0 // 2 seconds for fade out
const currentTime = this.audioContext.currentTime
const fadeEndTime = currentTime + fadeDuration
this.fading = true
if (this.createdNodes.noiseGain) {
// Cancel scheduled values to clear any previous scheduled changes
this.createdNodes.noiseGain.gain.cancelScheduledValues(currentTime)
// Set the current value
this.createdNodes.noiseGain.gain.setValueAtTime(this.createdNodes.noiseGain.gain.value, currentTime)
// Schedule the fade out
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(0, fadeEndTime)
this.noiseGain = 0
}
if (this.createdNodes.musicGain) {
// Cancel scheduled values to clear any previous scheduled changes
this.createdNodes.musicGain.gain.cancelScheduledValues(currentTime)
// Set the current value
this.createdNodes.musicGain.gain.setValueAtTime(this.createdNodes.musicGain.gain.value, currentTime)
// Schedule the fade out
this.createdNodes.musicGain.gain.linearRampToValueAtTime(0, fadeEndTime)
this.playing = false
}
setTimeout(() => {
this.fading = false
}, fadeDuration * 1000)
},
fadeInGains () {
// useNuxtApp().$logger.log('Fade In Gains')
const fadeTime = this.audioContext.currentTime + 6.0
this.fading = true
const noiseGain = this.createdNodes.noiseGain
const musicGain = this.createdNodes.musicGain
this.createdNodes.noiseSource.muted = false
this.createdNodes.musicSource.muted = false
noiseGain.gain.linearRampToValueAtTime(1.0, fadeTime)
musicGain.gain.linearRampToValueAtTime(1.0, fadeTime)
this.noiseGain = noiseGain.gain.value
this.musicGain = musicGain.gain.value
this.playing = true
setTimeout(() => {
this.fading = false
}, fadeTime * 1000)
},
handlePlayingUpdate (state: boolean) {
// useNuxtApp().$logger.log(`Audio Element is ${state ? 'Playing' : 'Not Playing'}`)
if (!this.musicReady && !this.noiseReady) {
// useNuxtApp().$logger.log('not yet ready' + this.musicReady + ' ready noise' + this.noiseReady)
return
}
if (state && useAudioStore().isPlaying()) {
// Everytime a AudioTag starts playing it is muted, because we want to control everything in web audio
// Music has just started react on it.
const noiseElement = this.$refs.Noise as typeof AudioElement
const noiseAudioElement = noiseElement.$refs.audioElement as HTMLMediaElement
const musicElement = this.$refs.Music as typeof AudioElement
const musicAudioElement = musicElement.$refs.audioElement as HTMLMediaElement
const audioContext = this.audioContext
const destination = this.audioContext.destination
this.createdNodes.musicGain ||= audioContext.createGain()
this.createdNodes.noiseGain ||= audioContext.createGain()
this.createdNodes.musicGain.gain.setValueAtTime(0, audioContext.currentTime)
this.createdNodes.noiseGain.gain.setValueAtTime(0, audioContext.currentTime)
musicAudioElement.muted = false
noiseAudioElement.muted = false
musicAudioElement.volume = 1.0
noiseAudioElement.volume = 1.0
this.createdNodes.noiseSource = audioContext.createMediaElementSource(noiseAudioElement)
this.createdNodes.musicSource = audioContext.createMediaElementSource(musicAudioElement)
this.createdNodes.noiseSource.connect(this.createdNodes.noiseGain)
this.createdNodes.musicSource.connect(this.createdNodes.musicGain)
this.createdNodes.noiseGain.connect(destination)
this.createdNodes.musicGain.connect(destination)
this.fadeInGains()
} else {
if (this.fading && this.createdNodes.noiseGain && this.createdNodes.musicGain) {
this.createdNodes.noiseGain.gain.cancelScheduledValues(this.createdNodes.noiseGain.currentTime)
this.createdNodes.musicGain.gain.cancelScheduledValues(this.createdNodes.musicGain.currentTime)
}
// Music has just stopped react on it.
setTimeout(() => {
this.playing = false
this.createdNodes = []
this.refreshAudioContext()
}, 2000)
}
},
updateNoiseGain (volume: number) {
if (this.createdNodes.noiseGain) {
this.createdNodes.noiseGain.gain.linearRampToValueAtTime(volume, this.createdNodes.noiseGain.context.currentTime + 0.30)
this.noiseGain = this.createdNodes.noiseGain.gain.value
}
},
updateMusicGain (volume: number) {
if (this.createdNodes.musicGain) {
this.createdNodes.musicGain.gain.linearRampToValueAtTime(volume, this.createdNodes.musicGain.context.currentTime + 0.30)
this.musicGain = this.createdNodes.musicGain.gain.value
}
}
}
}
</script>

View File

@@ -0,0 +1,73 @@
<template>
<h1>Test Version NoiseMusicWebAudio: mit WebAudio, ohne Gain, ohne Noise-Patch & ohne Music-Patch</h1>
<AudioElement
ref="Noise"
key="5"
:src="noise_src"
title="Noise"
>
<template #default="{}">
<div class="icon">
<img style="width: 25px" src="~/assets/image/noiseicon.svg">
</div>
</template>
</AudioElement>
<AudioElement
ref="Music"
key="1"
:src="forest_src"
title="Forest"
@update:playing="handlePlayingUpdate"
>
<template #default="{ }">
<div class="icon">
<!-- tropic icon -->
<img style="width: 25px" src="~/assets/image/musicicon.svg">
</div>
</template>
<!-- Slot content for AudioElement, if needed -->
</AudioElement>
</template>
<script lang="ts">
import AudioElement from '../AudioElement.vue'
import { useAudioStore } from '../../../stores/audio'
export default {
name: 'NoiseMusicGain',
components: { AudioElement },
data () {
return {
audioContext: useAudioStore().getContext(),
createdNodes: {} as any,
playing: false,
paused: false,
noise_src: window.location.origin + useRuntimeConfig().public.noise_src,
forest_src: window.location.origin + useRuntimeConfig().public.tracks.forest_src
}
},
beforeUnmount () {
this.audioContext.close()
this.audioContext = null
},
methods: {
handlePlayingUpdate () {
const noiseElement = this.$refs.Noise as typeof AudioElement
const noiseAudioElement = noiseElement.$refs.audioElement as HTMLMediaElement
const musicElement = this.$refs.Music as typeof AudioElement
const musicAudioElement = musicElement.$refs.audioElement as HTMLMediaElement
const audioContext = this.audioContext
const destination = this.audioContext.destination
this.createdNodes.noiseSource ||= audioContext.createMediaElementSource(noiseAudioElement)
this.createdNodes.musicSource ||= audioContext.createMediaElementSource(musicAudioElement)
musicAudioElement.muted = false
noiseAudioElement.muted = false
this.createdNodes.noiseSource.connect(destination)
this.createdNodes.musicSource.connect(destination)
}
}
}
</script>

View File

@@ -0,0 +1,354 @@
<template>
<div>
<h4>{{ currentScene.file }}</h4>
<h4>Node {{ currentScene.node ? true : false }}</h4>
<h4>{{ currentScene.title }}</h4>
<h4>Howl {{ currentScene.howl ? true : false }}</h4>
</div>
<div class="rnboplayer">
<button class="play yellow" @click="play" />
<button v-if="playing" class="pause yellow" @click="pause" />
<button v-if="playing" @click="useNuxtApp().$logger.log('hit play')">
Pause
</button>
<div class="row">
<div class="slider">
<div class="icon">
<!-- tropic icon -->
<img style="width: 25px" src="~/assets/image/noiseicon.svg">
</div>
<input
id="gain-control"
v-model="outputNoiseGain"
type="range"
min="0"
max="100"
step="1"
@wheel="changeNoiseGain"
>
</div>
</div>
<div class="row">
<div class="slider">
<div class="icon">
<!-- tropic icon -->
<img style="width: 25px" src="~/assets/image/musicicon.svg">
</div>
<input
id="gain-control"
v-model="outputMusicGain"
type="range"
min="0"
max="100"
default="50"
step="1"
@wheel="changeMusicGain"
>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref, computed, watch } from 'vue'
import Player from '~/components/Player/Player.js'
import setupNodes from '~/components/Player/Nodes'
import { useUserStore } from '~/stores/user.js'
import { usePlayerStore } from '~/stores/player.js'
import tracksConfig from '~/tracks.config'
// Stores
const userStore = useUserStore()
const playerStore = usePlayerStore()
const playing = computed(() => usePlayerStore().playing as boolean)
// Refs
const scenePlayer = ref<Player | null>(null)
const noisePlayer = ref<Player | null>(null)
const playlistIndex = ref(scenePlayer.value?.index || 0)
// Sliders
const outputMusicGain = ref(0)
const outputNoiseGain = ref(0)
// Playlists
const scenesPlaylist = ref([
{ title: 'Lagoon', file: 'lagoon', howl: null, node: null },
{ title: 'Meadow', file: 'meadow', howl: null, node: null },
{ title: 'Tropics', file: 'tropics', howl: null, node: null },
{ title: 'Forest', file: 'forest', howl: null, node: null }
])
const howlsReadyCount = computed(() =>
scenesPlaylist.value.filter(item => item.howl !== null).length
)
const nodesReadyCount = computed(() =>
scenesPlaylist.value.filter(item => item.node !== null).length
)
const noisePlaylist = ref([
{ title: 'noise', file: 'noise', howl: null, node: null }
])
const currentScene = computed(() => scenesPlaylist.value[0])
// Gain Nodes
const noiseGainNode = ref<GainNode | null>(null)
const musicGainNode = ref<GainNode | null>(null)
function play () {
playerStore.playing = true
useNuxtApp().$logger.log('current playing = ', playerStore.playing)
}
function pause () {
playerStore.playing = false
useNuxtApp().$logger.log('current playing = ', playerStore.playing)
}
function changeNoiseGain (event: WheelEvent) {
event.preventDefault()
const delta = Math.sign(event.deltaY)
if (noiseGainNode.value && isWithinRange(outputNoiseGain.value - delta)) {
outputNoiseGain.value -= delta
}
}
function changeMusicGain (event: WheelEvent) {
event.preventDefault()
const delta = Math.sign(event.deltaY)
if (musicGainNode.value && isWithinRange(outputMusicGain.value - delta)) {
scenePlayer.value?.setVolume()
outputMusicGain.value -= delta
}
}
function isWithinRange (val: number) {
return val >= 0 && val <= 100
}
// Lifecycle
onMounted(async () => {
const logger = useNuxtApp().$logger
scenePlayer.value = new Player(scenesPlaylist, 'sounds')
noisePlayer.value = new Player(noisePlaylist, 'masking')
scenePlayer.value.initializeHowl(scenePlayer.value.index)
noisePlayer.value.initializeHowl(noisePlayer.value.index)
const userScenery = userStore.getUserScenery
const filteredScenes = scenePlayer.value.playlist.filter(
(playItem: { title: string }) => playItem.title.toLowerCase() === userScenery.toLowerCase()
)
const noiseNode = noisePlayer.value.playlist[0].node
const response = await setupNodes(noiseNode, filteredScenes[0].node)
if (response === null) {
logger.info('Got no gain nodes from setupNodes, continue with HTML5')
// noisePlayer.value.playlist[0].howl.play()
// scenePlayer.value.playlist[0].howl.play()
} else {
try {
// filteredScenes[0].howl.play()
// noisePlayer.value.playlist[0].howl.play()
if (response.length > 1) {
noiseGainNode.value = response[0]
musicGainNode.value = response[1]
}
if (musicGainNode.value && noiseGainNode.value) {
musicGainNode.value.gain.value = 0.5
noiseGainNode.value.gain.value = 0.5
}
} catch (error) {
useNuxtApp().$logger.error(error)
}
}
})
onBeforeUnmount(() => {
noisePlayer.value = null
scenePlayer.value = null
playerStore.resetAudioContext()
})
// Watches
watch(playlistIndex, (val) => {
const player = scenePlayer.value as Player
player.index = val
// musicGainNode.value?.gain.linearRampToValueAtTime(val / 100, musicGainNode.value.context.currentTime + 0.2)
})
watch(outputMusicGain, (val) => {
musicGainNode.value?.gain.linearRampToValueAtTime(val / 100, musicGainNode.value.context.currentTime + 0.2)
})
watch(outputNoiseGain, (val) => {
noiseGainNode.value?.gain.linearRampToValueAtTime(val / 100, noiseGainNode.value.context.currentTime + 0.2)
})
watch(
() => noisePlayer.value?.playlist[0]?.node,
(node) => {
if (node) { useNuxtApp().$logger.log('🎧 Node ist jetzt da:', node) }
}
)
watch(
() => playerStore.playing,
(state) => {
if (state && noisePlayer && scenePlayer) {
noisePlayer.value?.play()
scenePlayer.value?.play()
useNuxtApp().$logger.log('🎧 Player spielt')
} else {
noisePlayer.value?.pause()
scenePlayer.value?.pause()
useNuxtApp().$logger.log('🎧 Player spielt nicht')
}
}
)
</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;
}
.player {
background-color: #fff;
border-radius: 12px;
position:sticky;
width: 225px;
height: inherit;
display:flex;
float: bottom;
}
.player button {
border-radius: 10px;
padding: 10px;
}
.container {
display: flex;
flex-wrap: wrap;
gap: 20px; /* Spacing between items */
width: 225px;
margin-bottom: 20px;
}
.icon, .slider {
flex: 1 1 100px; /* Flex-grow, flex-shrink, flex-basis */
display: flex;
align-items: center; /* Center items vertically */
justify-content: center; /* Center items horizontally */
}
.icon {
/* Add padding around the icon for margin */
margin-right: 15px; /* Adjust this value as needed */
/* Align items if using flexbox */
display: flex;
align-items: center;
justify-content: center;
}
.icon img {
/* Adjust width and height as needed or keep them auto to maintain aspect ratio */
width: auto;
height: 100%; /* Example height, adjust based on your icon size */
}
.slider input[type=range] {
width: 100%; /* Full width of its parent */
background-color: transparent !important;
}
@media (min-width: 600px) {
.row {
display: flex;
width: 100%;
}
.icon, .slider {
flex: 1; /* Take up equal space */
}
}
/* Styles the track */
input[type="range"]::-webkit-slider-runnable-track {
background: #e9c046; /* yellow track */
height: 8px;
border-radius: 5px;
}
input[type="range"]::-moz-range-track {
background: #e9c046; /* yellow track */
height: 8px;
border-radius: 5px;
}
input[type="range"]::-ms-track {
background: #e9c046; /* yellow track */
border-color: transparent;
color: transparent;
height: 8px;
}
.play.yellow {
background: rgba(255, 176, 66, 0.008);
border-radius: 50%;
box-shadow: 0 0 0 0 rgba(255, 177, 66, 1);
animation: pulse-yellow 4s infinite;
position: fixed;
bottom: 40%;
width: 200px;
height: 200px;
background-image: url('/images/playbtn.svg');
background-repeat:no-repeat;
background-attachment:fixed;
background-position: 58% 55%;
}
.pause.yellow {
background: rgba(255, 176, 66, 0.008);
border-radius: 50%;
box-shadow: 0 0 0 0 rgba(255, 177, 66, 1);
opacity: 0.05;
position: fixed;
bottom: 40%;
width: 200px;
height: 200px;
background-image: url('/images/pausebtn.svg');
background-size: 130px 100px;
background-repeat:no-repeat;
background-attachment:fixed;
background-position: center;
}
.pause.yellow:hover{
opacity: 0.5;
}
@keyframes pulse-yellow {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(255, 177, 66, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(255, 177, 66, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(255, 177, 66, 0);
}
}
</style>