diff --git a/packages/api/src/nitro/sound/ISoundManager.ts b/packages/api/src/nitro/sound/ISoundManager.ts index 87714b5..8db15ba 100644 --- a/packages/api/src/nitro/sound/ISoundManager.ts +++ b/packages/api/src/nitro/sound/ISoundManager.ts @@ -1,8 +1,22 @@ import { IMusicController } from './IMusicController'; +import { ISoundVolumesSnapshot } from './ISoundVolumesSnapshot'; export interface ISoundManager { init(): Promise; musicController: IMusicController; traxVolume: number; + systemVolume: number; + furniVolume: number; + + /** + * Returns a referentially-stable snapshot of the three volume + * levels (system / furni / trax). The same reference is returned + * across reads until a volume changes; mutations dispatch + * `NitroEventType.SOUND_VOLUMES_UPDATED` to signal invalidation. + * + * Pairs with `useSyncExternalStore` on the React client for + * volume-slider widgets. + */ + getVolumesSnapshot(): Readonly; } diff --git a/packages/api/src/nitro/sound/ISoundVolumesSnapshot.ts b/packages/api/src/nitro/sound/ISoundVolumesSnapshot.ts new file mode 100644 index 0000000..f61c1e1 --- /dev/null +++ b/packages/api/src/nitro/sound/ISoundVolumesSnapshot.ts @@ -0,0 +1,6 @@ +export interface ISoundVolumesSnapshot +{ + system: number; + furni: number; + trax: number; +} diff --git a/packages/api/src/nitro/sound/index.ts b/packages/api/src/nitro/sound/index.ts index be4bfe4..f1e1c21 100644 --- a/packages/api/src/nitro/sound/index.ts +++ b/packages/api/src/nitro/sound/index.ts @@ -2,3 +2,4 @@ export * from './IMusicController'; export * from './IPlaylistController'; export * from './ISongInfo'; export * from './ISoundManager'; +export * from './ISoundVolumesSnapshot'; diff --git a/packages/events/src/NitroEventType.ts b/packages/events/src/NitroEventType.ts index 414ecfa..b7a1ea0 100644 --- a/packages/events/src/NitroEventType.ts +++ b/packages/events/src/NitroEventType.ts @@ -22,4 +22,5 @@ export class NitroEventType public static readonly IGNORED_USERS_UPDATED = 'IGNORED_USERS_UPDATED'; public static readonly GROUP_BADGES_UPDATED = 'GROUP_BADGES_UPDATED'; public static readonly ROOM_USER_LIST_UPDATED = 'ROOM_USER_LIST_UPDATED'; + public static readonly SOUND_VOLUMES_UPDATED = 'SOUND_VOLUMES_UPDATED'; } diff --git a/packages/sound/src/SoundManager.ts b/packages/sound/src/SoundManager.ts index e736fd4..db73f2c 100644 --- a/packages/sound/src/SoundManager.ts +++ b/packages/sound/src/SoundManager.ts @@ -1,6 +1,6 @@ -import { IAdvancedMap, IMusicController, INitroEvent, ISoundManager } from '@nitrots/api'; +import { IAdvancedMap, IMusicController, INitroEvent, ISoundManager, ISoundVolumesSnapshot } from '@nitrots/api'; import { GetConfiguration } from '@nitrots/configuration'; -import { GetEventDispatcher, NitroSettingsEvent, NitroSoundEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomEngineSamplePlaybackEvent } from '@nitrots/events'; +import { GetEventDispatcher, NitroEvent, NitroEventType, NitroSettingsEvent, NitroSoundEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomEngineSamplePlaybackEvent } from '@nitrots/events'; import { AdvancedMap, NitroLogger } from '@nitrots/utils'; import { MusicController } from './music/MusicController'; @@ -9,6 +9,7 @@ export class SoundManager implements ISoundManager private _volumeSystem: number = 0.5; private _volumeFurni: number = 0.5; private _volumeTrax: number = 0.5; + private _volumesSnapshot: Readonly | null = null; private _internalSamples: IAdvancedMap = new AdvancedMap(); private _furniSamples: IAdvancedMap = new AdvancedMap(); @@ -81,17 +82,24 @@ export class SoundManager implements ISoundManager case NitroSettingsEvent.SETTINGS_UPDATED: { const castedEvent = (event as NitroSettingsEvent); - const volumeFurniUpdated = castedEvent.volumeFurni !== this._volumeFurni; - const volumeTraxUpdated = castedEvent.volumeTrax !== this._volumeTrax; + const nextSystem = (castedEvent.volumeSystem / 100); + const nextFurni = (castedEvent.volumeFurni / 100); + const nextTrax = (castedEvent.volumeTrax / 100); - this._volumeSystem = (castedEvent.volumeSystem / 100); - this._volumeFurni = (castedEvent.volumeFurni / 100); - this._volumeTrax = (castedEvent.volumeTrax / 100); + const volumeSystemUpdated = nextSystem !== this._volumeSystem; + const volumeFurniUpdated = nextFurni !== this._volumeFurni; + const volumeTraxUpdated = nextTrax !== this._volumeTrax; + + this._volumeSystem = nextSystem; + this._volumeFurni = nextFurni; + this._volumeTrax = nextTrax; if(volumeFurniUpdated) this.updateFurniSamplesVolume(this._volumeFurni); if(volumeTraxUpdated) this._musicController?.updateVolume(this._volumeTrax); + if(volumeSystemUpdated || volumeFurniUpdated || volumeTraxUpdated) this.invalidateVolumesSnapshot(); + return; } case NitroSoundEvent.PLAY_SOUND: { @@ -215,8 +223,38 @@ export class SoundManager implements ISoundManager return this._volumeTrax; } + public get systemVolume(): number + { + return this._volumeSystem; + } + + public get furniVolume(): number + { + return this._volumeFurni; + } + public get musicController(): IMusicController { return this._musicController; } + + private invalidateVolumesSnapshot(): void + { + this._volumesSnapshot = null; + + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOUND_VOLUMES_UPDATED)); + } + + public getVolumesSnapshot(): Readonly + { + if(this._volumesSnapshot) return this._volumesSnapshot; + + this._volumesSnapshot = Object.freeze({ + system: this._volumeSystem, + furni: this._volumeFurni, + trax: this._volumeTrax + }); + + return this._volumesSnapshot; + } }