AudioEngineV2 - How to attach sound to mesh in a new way

Sure! The last version where everything worked fine was 7.51.3. After upgrading to 7.52.2, I noticed that sounds were no longer audible. There were no errors in the console, so I suspect the issue might be on my side.

I apologize for not providing a Playground example. My project is quite large, and it will take some time to extract a minimal reproducible case. However, I can show you the class I use for sound playback, which stopped working after the update. Again, it’s entirely possible that the issue is due to something on my end.

Here is a standalone class I wrote as an abstraction layer for handling sounds:

import { AbstractMesh, AssetsManager, Scene, Sound } from 'babylonjs'

class SoundPlayer {
  private readonly baseUrl: string = '/sounds/loader/'
  private readonly scene: Scene
  private assetsManager: AssetsManager
  private mesh: AbstractMesh | null = null
  sounds: { [key: string]: Sound[] } = {}
  volume: number

  constructor(scene: Scene, baseUrl: string, mesh: AbstractMesh | null, volume: number = 1) {
    this.scene = scene
    this.assetsManager = new AssetsManager(this.scene)
    this.baseUrl = baseUrl
    this.mesh = mesh
    this.volume = volume
  }

  setVolume(volume: number) {
    Object.values(this.sounds).forEach((soundArray) => {
      soundArray.forEach((sound) => {
        if (sound.isPlaying) {
          if (this.volume > 0) sound.setVolume(this.volume)
        }
      })
    })
    this.volume = volume
  }

  setMesh(mesh: AbstractMesh | null) {
    this.mesh = mesh
  }

  playSound(name: string, volume: number = 1.0, loop: boolean = false, fade: boolean = false, playbackRate: number = 1): Sound | undefined {
    if (name in this.sounds) {
      const randomIndex = Math.floor(Math.random() * this.sounds[name].length)
      return this.playSoundFile(this.sounds[name][randomIndex], volume, loop, fade, playbackRate)
    } else {
      console.error(`Sound play '${name}' is not defined.`)
    }
  }

  stopSoundFadeOut(name: string, fadeout: boolean = true): void {
    if (name in this.sounds) {
      this.sounds[name].forEach((sound: Sound) => {
        if (fadeout) {
          const fadeDuration = 3000 // Adjust fade duration as needed
          const intervalDuration = 50 // Adjust interval duration as needed

          const initialVolume = sound.getVolume()
          let currentVolume = initialVolume

          const interval = setInterval(() => {
            currentVolume -= initialVolume / (fadeDuration / intervalDuration)
            sound.setVolume(Math.max(currentVolume, 0))

            if (currentVolume <= 0) {
              clearInterval(interval)
              sound.stop()
            }
          }, intervalDuration)
        } else {
          sound.stop()
        }
      })
    } else {
      console.error(`Sound '${name}' is not defined.`)
    }
  }

  getSound(name: string): Sound | undefined {
    if (name in this.sounds) {
      const sounds = this.sounds[name]
      if (sounds.length > 0) {
        return sounds[0]
      } else {
        console.error(`No sounds available for '${name}'.`)
        return undefined
      }
    } else {
      console.error(`Sound '${name}' is not defined.`)
      return undefined
    }
  }

  stopSound(name: string) {
    if (name in this.sounds) {
      this.sounds[name].forEach((sound: Sound) => {
        sound.stop()
      })
    } else {
      console.error(`Sound '${name}' is not defined.`)
    }
  }

  playSoundFile(
    sound: Sound,
    volume: number = 1.0,
    loop: boolean = false,
    fade: boolean = false,
    playbackRate: number = 1,
  ): Sound | undefined {
    sound.setVolume(0)
    sound.loop = loop
    sound.setPlaybackRate(playbackRate)
    sound.autoplay = true
    if (this.mesh) {
      sound.spatialSound = true
      sound.setPosition(this.mesh.position)
      if (this.scene.activeCamera) {
        sound.setLocalDirectionToMesh(this.scene.activeCamera.position)
      }
    } else {
      sound.spatialSound = false
    }

    if (fade) {
      const fadeDuration = 1 // Adjust fade duration as needed
      sound.play()
      sound.setVolume(0, fadeDuration / 2) // Start with silence and gradually increase volume
      setTimeout(() => {
        sound.setVolume(volume * this.volume, fadeDuration / 2) // Gradually increase volume to target
      }, fadeDuration / 2)
      return
    }
    sound.setVolume(volume * this.volume)
    sound.play()

    return sound
  }

  async fetchUrls(urls: Record<string, string[]>) {
    try {
      for (const action in urls) {
        if (Object.prototype.hasOwnProperty.call(urls, action)) {
          const actionKey = action as keyof typeof urls
          const soundTasks: Promise<Sound>[] = []
          urls[actionKey].forEach((url) => {
            const soundTask = this.assetsManager.addBinaryFileTask(`${actionKey}-${url}`, this.baseUrl + url)
            soundTasks.push(
              new Promise((resolve, reject) => {
                soundTask.onSuccess = (task) => {
                  const sound = new Sound(url, task.data as ArrayBuffer, this.scene)
                  resolve(sound)
                }
                soundTask.onError = (_, e) => {
                  reject(`Failed to load sound '${url}': ${e}`)
                }
              }),
            )
          })
          Promise.all(soundTasks)
            .then((sounds) => {
              this.sounds[actionKey] = sounds
            })
            .catch((error) => {
              console.error(error)
            })
        }
      }
      return await this.assetsManager.loadAsync()
    } catch (error) {
      console.error('Failed to load sounds:', error)
      throw error
    }
  }
}

export default SoundPlayer

I will dig it more.