From 775ec7d03b687857f86a8797fb576fbb5cf3a604 Mon Sep 17 00:00:00 2001 From: skeletor Date: Tue, 8 Nov 2022 22:44:30 +0000 Subject: [PATCH] Feature/jukebox --- package.json | 2 + src/api/nitro/sound/IMusicController.ts | 25 + src/api/nitro/sound/IMusicManager.ts | 6 - src/api/nitro/sound/IPlaylistController.ts | 15 + src/api/nitro/sound/ISoundManager.ts | 5 +- src/api/nitro/sound/common/ISongInfo.ts | 12 + src/api/nitro/sound/common/index.ts | 1 + src/api/nitro/sound/index.ts | 4 +- .../sound/JukeboxSongDisksMessageParser.ts | 11 +- src/nitro/index.ts | 1 + src/nitro/sound/SoundManager.ts | 30 +- src/nitro/sound/common/SongDataEntry.ts | 54 ++ .../sound/common/SongStartRequestData.ts | 7 +- src/nitro/sound/common/TraxSample.ts | 194 ------ src/nitro/sound/common/TraxSequencer.ts | 166 ----- src/nitro/sound/common/index.ts | 6 +- .../sound/events/NotifyPlayedSongEvent.ts | 27 + src/nitro/sound/events/NowPlayingEvent.ts | 35 + src/nitro/sound/events/PlayListStatusEvent.ts | 12 + .../events/SongDiskInventoryReceivedEvent.ts | 11 + .../sound/events/SongInfoReceivedEvent.ts | 19 + src/nitro/sound/events/SoundManagerEvent.ts | 10 +- src/nitro/sound/events/index.ts | 5 + src/nitro/sound/index.ts | 1 + .../sound/music/JukeboxPlaylistController.ts | 198 ++++++ src/nitro/sound/music/MusicController.ts | 599 ++++++++++++++++++ src/nitro/sound/music/MusicManager.ts | 183 ------ src/nitro/sound/music/MusicPlayer.ts | 226 +++++++ src/nitro/sound/music/index.ts | 3 +- .../sound/{common => trax}/TraxChannel.ts | 1 + .../sound/{common => trax}/TraxChannelItem.ts | 0 src/nitro/sound/{common => trax}/TraxData.ts | 11 +- src/nitro/sound/trax/index.ts | 3 + yarn.lock | 10 + 34 files changed, 1310 insertions(+), 583 deletions(-) create mode 100644 src/api/nitro/sound/IMusicController.ts delete mode 100644 src/api/nitro/sound/IMusicManager.ts create mode 100644 src/api/nitro/sound/IPlaylistController.ts create mode 100644 src/api/nitro/sound/common/ISongInfo.ts create mode 100644 src/api/nitro/sound/common/index.ts create mode 100644 src/nitro/sound/common/SongDataEntry.ts delete mode 100644 src/nitro/sound/common/TraxSample.ts delete mode 100644 src/nitro/sound/common/TraxSequencer.ts create mode 100644 src/nitro/sound/events/NotifyPlayedSongEvent.ts create mode 100644 src/nitro/sound/events/NowPlayingEvent.ts create mode 100644 src/nitro/sound/events/PlayListStatusEvent.ts create mode 100644 src/nitro/sound/events/SongDiskInventoryReceivedEvent.ts create mode 100644 src/nitro/sound/events/SongInfoReceivedEvent.ts create mode 100644 src/nitro/sound/music/JukeboxPlaylistController.ts create mode 100644 src/nitro/sound/music/MusicController.ts delete mode 100644 src/nitro/sound/music/MusicManager.ts create mode 100644 src/nitro/sound/music/MusicPlayer.ts rename src/nitro/sound/{common => trax}/TraxChannel.ts (99%) rename src/nitro/sound/{common => trax}/TraxChannelItem.ts (100%) rename src/nitro/sound/{common => trax}/TraxData.ts (90%) create mode 100644 src/nitro/sound/trax/index.ts diff --git a/package.json b/package.json index 9bbaa7ef..af261ad8 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "postinstall": "node ./post-install.js" }, "dependencies": { + "howler": "^2.2.3", "@pixi/app": "~6.4.2", "@pixi/basis": "~6.4.2", "@pixi/canvas-display": "~6.4.2", @@ -58,6 +59,7 @@ }, "devDependencies": { "@types/pako": "^1.0.3", + "@types/howler": "^2.2.7", "@typescript-eslint/eslint-plugin": "^5.30.7", "@typescript-eslint/parser": "^5.30.7", "eslint": "^8.20.0", diff --git a/src/api/nitro/sound/IMusicController.ts b/src/api/nitro/sound/IMusicController.ts new file mode 100644 index 00000000..169ef2b4 --- /dev/null +++ b/src/api/nitro/sound/IMusicController.ts @@ -0,0 +1,25 @@ +import { IAdvancedMap } from '../../IAdvancedMap'; +import { ISongInfo } from './common/ISongInfo'; +import { IPlaylistController } from './IPlaylistController'; + +export interface IMusicController +{ + getRoomItemPlaylist(_arg_1?:number):IPlaylistController; + getSongDiskInventorySize():number; + getSongDiskInventoryDiskId(_arg_1:number):number; + getSongDiskInventorySongId(_arg_1:number):number; + getSongInfo(_arg_1:number):ISongInfo; + getSongIdPlayingAtPriority(_arg_1:number):number; + playSong(songId:number, priority:number, startPos?:number, playLength?:number, fadeInSeconds?:number, fadeOutSeconds?:number):boolean; + stop(_arg_1:number):void; + addSongInfoRequest(_arg_1:number):void; + requestSongInfoWithoutSamples(_arg_1:number):void; + requestUserSongDisks():void; + onSongLoaded(_arg_1:number):void; + updateVolume(_arg_1:number):void; + samplesUnloaded(_arg_1:number[]):void; + get samplesIdsInUse():number[]; + get songDiskInventory(): IAdvancedMap + init(): void; + dispose():void; +} diff --git a/src/api/nitro/sound/IMusicManager.ts b/src/api/nitro/sound/IMusicManager.ts deleted file mode 100644 index 59e5cb24..00000000 --- a/src/api/nitro/sound/IMusicManager.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { INitroManager } from '../../common'; - -export interface IMusicManager extends INitroManager -{ - playPosition: number; -} diff --git a/src/api/nitro/sound/IPlaylistController.ts b/src/api/nitro/sound/IPlaylistController.ts new file mode 100644 index 00000000..a8feea33 --- /dev/null +++ b/src/api/nitro/sound/IPlaylistController.ts @@ -0,0 +1,15 @@ +import { ISongInfo } from './common/ISongInfo'; + +export interface IPlaylistController +{ + get priority():number; + get length():number; + get playPosition():number; + get nowPlayingSongId():number; + get isPlaying():boolean; + get entries(): ISongInfo[]; + getEntry(_arg_1: number):ISongInfo; + requestPlayList():void; + init(): void; + dispose():void; +} diff --git a/src/api/nitro/sound/ISoundManager.ts b/src/api/nitro/sound/ISoundManager.ts index ed89ee0f..e0bbb0f7 100644 --- a/src/api/nitro/sound/ISoundManager.ts +++ b/src/api/nitro/sound/ISoundManager.ts @@ -1,7 +1,8 @@ import { INitroManager } from '../../common'; -import { IMusicManager } from './IMusicManager'; +import { IMusicController } from './IMusicController'; export interface ISoundManager extends INitroManager { - musicManager: IMusicManager; + get musicController(): IMusicController; + get traxVolume(): number; } diff --git a/src/api/nitro/sound/common/ISongInfo.ts b/src/api/nitro/sound/common/ISongInfo.ts new file mode 100644 index 00000000..475b75dc --- /dev/null +++ b/src/api/nitro/sound/common/ISongInfo.ts @@ -0,0 +1,12 @@ +export interface ISongInfo +{ + //get loaded():boolean; + get id():number; + get diskId():number; + set diskId(id: number); + get length():number; + get name():string; + get creator():string; + get songData():string; + //get soundObject():IHabboSound; +} diff --git a/src/api/nitro/sound/common/index.ts b/src/api/nitro/sound/common/index.ts new file mode 100644 index 00000000..c6c8783f --- /dev/null +++ b/src/api/nitro/sound/common/index.ts @@ -0,0 +1 @@ +export * from './ISongInfo'; diff --git a/src/api/nitro/sound/index.ts b/src/api/nitro/sound/index.ts index 2939b351..37eb921f 100644 --- a/src/api/nitro/sound/index.ts +++ b/src/api/nitro/sound/index.ts @@ -1,2 +1,4 @@ -export * from './IMusicManager'; +export * from './common'; +export * from './IMusicController'; +export * from './IPlaylistController'; export * from './ISoundManager'; diff --git a/src/nitro/communication/messages/parser/sound/JukeboxSongDisksMessageParser.ts b/src/nitro/communication/messages/parser/sound/JukeboxSongDisksMessageParser.ts index d730833d..5212ca8d 100644 --- a/src/nitro/communication/messages/parser/sound/JukeboxSongDisksMessageParser.ts +++ b/src/nitro/communication/messages/parser/sound/JukeboxSongDisksMessageParser.ts @@ -1,13 +1,14 @@ -import { IMessageDataWrapper, IMessageParser } from '../../../../../api'; +import { IAdvancedMap, IMessageDataWrapper, IMessageParser } from '../../../../../api'; +import { AdvancedMap } from '../../../../../core'; export class JukeboxSongDisksMessageParser implements IMessageParser { - private _songDisks: Map = new Map(); + private _songDisks: IAdvancedMap = new AdvancedMap(); private _maxLength: number; flush(): boolean { - this._songDisks.clear(); + this._songDisks.reset(); this._maxLength = 0; return true; } @@ -19,13 +20,13 @@ export class JukeboxSongDisksMessageParser implements IMessageParser for(let i = 0; i < count; i++) { - this._songDisks.set(wrapper.readInt(), wrapper.readInt()); + this._songDisks.add(wrapper.readInt(), wrapper.readInt()); } return true; } - public get songDisks(): Map + public get songDisks(): IAdvancedMap { return this._songDisks; } diff --git a/src/nitro/index.ts b/src/nitro/index.ts index 970528f3..bd99803b 100644 --- a/src/nitro/index.ts +++ b/src/nitro/index.ts @@ -8,5 +8,6 @@ export * from './localization'; export * from './Nitro'; export * from './room'; export * from './session'; +export * from './sound'; export * from './utils'; export * from './window'; diff --git a/src/nitro/sound/SoundManager.ts b/src/nitro/sound/SoundManager.ts index dbb61aff..375f8b5d 100644 --- a/src/nitro/sound/SoundManager.ts +++ b/src/nitro/sound/SoundManager.ts @@ -1,8 +1,8 @@ -import { IAdvancedMap, IMusicManager, INitroEvent, ISoundManager, NitroConfiguration } from '../../api'; +import { IAdvancedMap, IMusicController, INitroEvent, ISoundManager, NitroConfiguration } from '../../api'; import { AdvancedMap, NitroManager } from '../../core'; import { NitroSettingsEvent, NitroSoundEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomEngineSamplePlaybackEvent } from '../../events'; import { Nitro } from '../Nitro'; -import { MusicManager } from './music'; +import { MusicController } from './music/MusicController'; export class SoundManager extends NitroManager implements ISoundManager { @@ -14,7 +14,7 @@ export class SoundManager extends NitroManager implements ISoundManager private _furniSamples: IAdvancedMap; private _furnitureBeingPlayed: IAdvancedMap; - private _musicManager: MusicManager; + private _musicController: IMusicController; constructor() { @@ -27,15 +27,14 @@ export class SoundManager extends NitroManager implements ISoundManager this._internalSamples = new AdvancedMap(); this._furniSamples = new AdvancedMap(); this._furnitureBeingPlayed = new AdvancedMap(); - - this._musicManager = new MusicManager(); + this._musicController = new MusicController(); this.onEvent = this.onEvent.bind(this); } public onInit(): void { - this._musicManager.init(); + this._musicController.init(); Nitro.instance.roomEngine.events.addEventListener(RoomEngineSamplePlaybackEvent.PLAY_SAMPLE, this.onEvent); Nitro.instance.roomEngine.events.addEventListener(RoomEngineObjectEvent.REMOVED, this.onEvent); @@ -46,10 +45,10 @@ export class SoundManager extends NitroManager implements ISoundManager public onDispose(): void { - if(this._musicManager) + if(this._musicController) { - this._musicManager.dispose(); - this._musicManager = null; + this._musicController.dispose(); + this._musicController = null; } Nitro.instance.roomEngine.events.removeEventListener(RoomEngineSamplePlaybackEvent.PLAY_SAMPLE, this.onEvent); @@ -86,12 +85,16 @@ export class SoundManager extends NitroManager implements ISoundManager const castedEvent = (event as NitroSettingsEvent); const volumeFurniUpdated = castedEvent.volumeFurni !== this._volumeFurni; + const volumeTraxUpdated = castedEvent.volumeTrax !== this._volumeTrax; this._volumeSystem = (castedEvent.volumeSystem / 100); this._volumeFurni = (castedEvent.volumeFurni / 100); this._volumeTrax = (castedEvent.volumeTrax / 100); if(volumeFurniUpdated) this.updateFurniSamplesVolume(this._volumeFurni); + + if(volumeTraxUpdated) this._musicController?.updateVolume(this._volumeTrax); + return; } case NitroSoundEvent.PLAY_SOUND: { @@ -219,8 +222,13 @@ export class SoundManager extends NitroManager implements ISoundManager }); } - public get musicManager(): IMusicManager + public get traxVolume(): number { - return this._musicManager; + return this._volumeTrax; + } + + public get musicController(): IMusicController + { + return this._musicController; } } diff --git a/src/nitro/sound/common/SongDataEntry.ts b/src/nitro/sound/common/SongDataEntry.ts new file mode 100644 index 00000000..21ff4799 --- /dev/null +++ b/src/nitro/sound/common/SongDataEntry.ts @@ -0,0 +1,54 @@ +import { ISongInfo } from '../../../api'; +import { PlayListEntry } from '../../communication/messages/parser/sound/PlayListEntry'; + +export class SongDataEntry extends PlayListEntry implements ISongInfo +{ + private _songData:string; + private _jukeboxDiskId:number = -1; + + constructor(id:number, length:number, name:string, creator:string, songData: string = '') + { + super(id, length, name, creator); + this._songData = songData; + } + + public override get id():number + { + return this._id; + } + + public override get length():number + { + return this._length; + } + + public override get name():string + { + return this._name; + } + + public override get creator():string + { + return this._creator; + } + + public get songData():string + { + return this._songData; + } + + public set songData(k:string) + { + this._songData = k; + } + + public get diskId():number + { + return this._jukeboxDiskId; + } + + public set diskId(k:number) + { + this._jukeboxDiskId = k; + } +} diff --git a/src/nitro/sound/common/SongStartRequestData.ts b/src/nitro/sound/common/SongStartRequestData.ts index fd6827d9..bbe07294 100644 --- a/src/nitro/sound/common/SongStartRequestData.ts +++ b/src/nitro/sound/common/SongStartRequestData.ts @@ -1,4 +1,3 @@ -import { GetTickerTime } from '../../../pixi-proxy'; export class SongStartRequestData { @@ -9,14 +8,14 @@ export class SongStartRequestData private _fadeInSeconds: number; private _fadeOutSeconds: number; - constructor(songId: number, startPos: number, playLength: number, playRequestTime: number, fadeInSeconds: number, fadeOutSeconds: number) + constructor(songId: number, startPos: number, playLength: number, fadeInSeconds: number, fadeOutSeconds: number) { this._songId = songId; this._startPos = startPos; this._playLength = playLength; - this._playRequestTime = playRequestTime; this._fadeInSeconds = fadeInSeconds; this._fadeOutSeconds = fadeOutSeconds; + this._playRequestTime = Date.now(); } public get songId(): number @@ -28,7 +27,7 @@ export class SongStartRequestData { if(this._startPos < 0) return 0; - return this._startPos + ((GetTickerTime() - this._playRequestTime) / 1000); + return this._startPos + ((Date.now() - this._playRequestTime) / 1000); } public get playLength(): number diff --git a/src/nitro/sound/common/TraxSample.ts b/src/nitro/sound/common/TraxSample.ts deleted file mode 100644 index a093d8d4..00000000 --- a/src/nitro/sound/common/TraxSample.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { BinaryReader, BinaryWriter } from '../../../core'; - -export class TraxSample -{ - public static readonly SAMPLE_FREQUENCY_44KHZ: string = 'sample_44khz'; - public static readonly SAMPLE_FREQUENCY_22KHZ: string = 'sample_22khz'; - public static readonly SAMPLE_FREQUENCY_11KHZ: string = 'sample_11khz'; - public static readonly SAMPLE_SCALE_16BIT: string = 'sample_16bit'; - public static readonly SAMPLE_SCALE_8BIT: string = 'sample_8bit'; - public static readonly _Str_11575: number = (1 / 0x8000); - private static readonly _Str_14308: number = 32; - private static readonly MASK_8BIT: number = 0xFF; - private static readonly MASK_16BIT: number = 0xFFFF; - private static readonly OFFSET_8BIT: number = 127; - private static readonly OFFSET_16BIT: number = 32767; - - private _sampleData: number[]; - private _id: number; - private _samplesPerValue: number; - private _sampleRepeats: number; - private _usageList: any[]; - private _usageTimestamp: number; - - constructor(k: BinaryWriter, sampleId: number, sampleFrequency: string = TraxSample.SAMPLE_FREQUENCY_44KHZ, sampleScale: string = TraxSample.SAMPLE_SCALE_16BIT) - { - this._id = sampleId; - this._samplesPerValue = 2; - this._sampleRepeats = 1; - - let local5 = 65536; - - switch(sampleFrequency) - { - case TraxSample.SAMPLE_FREQUENCY_22KHZ: - this._sampleRepeats = 2; - break; - case TraxSample.SAMPLE_FREQUENCY_11KHZ: - this._sampleRepeats = 4; - break; - default: - this._sampleRepeats = 1; - } - - if(sampleScale === TraxSample.SAMPLE_SCALE_8BIT) - { - this._samplesPerValue = 4; - local5 = 0x0100; - } - - const local6: number = this._samplesPerValue * this._sampleRepeats; - const local7: number = (Math.trunc(k.getBuffer().byteLength / 8) / local6) * local6; - - this._sampleData = new Array(local7 / local6); - - const local8: number = 1 / (local5 / 2); - - k.position = 0; - - const reader: BinaryReader = new BinaryReader(k.getBuffer()); - - let local9: number; - const local10: number = (local7 / this._sampleRepeats); - let local12: number; - let local15: number; - - for(let i = 0; i < local10; i++) - { - local12 = reader.readFloat(); - reader.readFloat(); - - for(let j = 2; j <= this._sampleRepeats; j++) - { - local15 = reader.readFloat(); - reader.readFloat(); - - local12 = (((j * (j - 1)) / j) + (local15 / j)); - } - - if(i >= ((local10 - 1) - TraxSample._Str_14308)) local12 = (local12 * (((local10 - 1) - 1) / TraxSample._Str_14308)); - - let local14 = ((local12 + 1) / local8); - - if(local14 < 0) - { - local14 = 0; - } - else if(local14 >= local5) - { - local14 = local5 - 1; - } - - local9 = (local9 * local5) + local14; - - if((i % this._samplesPerValue) === this._samplesPerValue - 1) - { - this._sampleData[Math.trunc(i / this._samplesPerValue)] = local9; - } - } - } - - public setSample(k: number[], arg2: number, arg3: number, arg4: number): number - { - let local8: number; - let local9: number; - let local10: number; - - if(k === null || this._sampleData === null) return arg4; - - const local5 = this._samplesPerValue * this._sampleRepeats; - arg4 = arg4 / local5; - - if(arg2 < 0) arg3 = arg3 + arg2; - - if(arg3 > k.length - arg2) arg3 = k.length - arg2; - - let local6 = arg3 / local5; - let local7: number; - - if(local6 > this._sampleData.length - arg4) - { - local7 = (local6 - this._sampleData.length - arg4) * local5; - local6 = this._sampleData.length - arg4; - - if(local7 > (k.length - arg2)) local7 = k.length - arg2; - } - - if(this._sampleRepeats === 1) - { - if(this._samplesPerValue === 2) - { - while(local6-- > 0) - { - local8 = this._sampleData[arg4++]; - - k[arg2++] = (((local8 >> 16) & TraxSample.MASK_16BIT) - TraxSample.OFFSET_16BIT); - k[arg2++] = ((local8 & TraxSample.MASK_16BIT) - TraxSample.OFFSET_16BIT); - } - } - else if(this._samplesPerValue === 4) - { - while(local6-- > 0) - { - local8 = this._sampleData[arg4++]; - - k[arg2++] = ((((local8 >> 24) & TraxSample.MASK_8BIT) - TraxSample.OFFSET_8BIT) << 8); - k[arg2++] = ((((local8 >> 16) & TraxSample.MASK_8BIT) - TraxSample.OFFSET_8BIT) << 8); - k[arg2++] = ((((local8 >> 8) & TraxSample.MASK_8BIT) - TraxSample.OFFSET_8BIT) << 8); - k[arg2++] = (((local8 & TraxSample.MASK_8BIT) - TraxSample.OFFSET_8BIT) << 8); - } - } - } - else if(this._sampleRepeats >= 2) - { - local9 = 0; - local10 = 0; - - if(this._samplesPerValue === 2) - { - while(local6-- > 0) - { - local8 = this._sampleData[arg4++]; - local10 = (((local8 >> 16) & TraxSample.MASK_16BIT) - TraxSample.OFFSET_16BIT); - local9 = this._sampleRepeats; - - while(local9 > 0) - // eslint-disable-next-line no-empty - { - - } - } - } - } - } - - public get id(): number - { - return this._id; - } - - public get length(): number - { - return this._sampleData.length * this._samplesPerValue * this._sampleRepeats; - } - - public get usageCount(): number - { - return this._usageList.length; - } - - public get usageTimestamp(): number - { - return this._usageTimestamp; - } -} diff --git a/src/nitro/sound/common/TraxSequencer.ts b/src/nitro/sound/common/TraxSequencer.ts deleted file mode 100644 index 7f32e7b5..00000000 --- a/src/nitro/sound/common/TraxSequencer.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { TraxData } from './TraxData'; -export class TraxSequencer -{ - private static readonly SAMPLES_PER_SECOND: number = 44100; - private static readonly BUFFER_LENGTH: number = 0x2000; - private static readonly SAMPLES_BAR_LENGTH: number = 88000; - private static readonly BAR_LENGTH: number = 88000; - private static readonly MIXING_BUFFER: number[] = new Array(this.BUFFER_LENGTH); - private static readonly INTERPOLATION_BUFFER: number[] = new Array(this.BUFFER_LENGTH); - - private _volume: number; - //sound - //soundChannel - private _traxData: TraxData; - private _samples: Map; - private _ready: boolean; - private _songId: number; - private _playLengthSamples: number = 0; - private _playHead: number; - //sequence array - private _prepared: boolean; - private _finished: boolean = true; - private _lengthSamples: number; - private _latencyMs: number; - private _fadeInActive: boolean; - private _fadeOutActive: boolean; - private _fadeInLengthSamples: number; - private _fadeOutLengthSamples: number; - private _fadeInSampleCounter: number; - private _fadeOutSampleCounter: number; - //fadeOutStopTimer - //stopTimer - private _useCutMode: boolean; - private _expectedStreamPosition: number = 0; - private _bufferUnderRunCount: number = 0; - - constructor(songId: number, traxData: TraxData, samples: any) - { - this._latencyMs = 30; - //set events - this._songId = songId; - this._volume = 1; - //set sound new Sound() - //set soundchannel null - this._samples = new Map(); - this._traxData = traxData; - this._ready = true; - this._playHead = 0; - //set sequence [] - this._prepared = false; - this._lengthSamples = 0; - this._finished = false; - this._fadeInActive = false; - this._fadeOutActive = false; - this._fadeInLengthSamples = 0; - this._fadeOutLengthSamples = 0; - this._fadeInSampleCounter = 0; - this._fadeOutSampleCounter = 0; - } - - public prepare(): boolean - { - if(!this._ready) return false; - - if(this._prepared) return true; - - if(this._traxData != null) - { - this._useCutMode = false; - - if(this._traxData.hasMetaData) - { - this._useCutMode = this._traxData.metaCutMode; - } - - if(this._useCutMode) - // eslint-disable-next-line no-empty - { - - } - } - } - - public prepareSequence(): void - { - for(const channel of this._traxData.channels) - { - for(const item of channel.items) - { - const sample = this._samples.get(item.id); - } - } - } - - public get position(): number - { - return this._playHead / TraxSequencer.SAMPLES_PER_SECOND; - } - - private set position(value: number) - { - this._playHead = value * TraxSequencer.SAMPLES_PER_SECOND; - } - - public get volume(): number - { - return this._volume; - } - - public set volume(value: number) - { - this._volume = value; - - //soundTransform for channel - } - - public get ready(): boolean - { - return this._ready; - } - - public set ready(value: boolean) - { - this._ready = value; - } - - public get finished(): boolean - { - return this._finished; - } - - public set finished(value: boolean) - { - this._finished = value; - } - - public get fadeOutSeconds(): number - { - return this._fadeOutLengthSamples / TraxSequencer.SAMPLES_PER_SECOND; - } - - public set fadeOutSeconds(value: number) - { - this._fadeOutLengthSamples = value * TraxSequencer.SAMPLES_PER_SECOND; - } - - public get fadeInSetconds(): number - { - return this._fadeInLengthSamples / TraxSequencer.SAMPLES_PER_SECOND; - } - - public set fadeInSeconds(value: number) - { - this._fadeInLengthSamples = value * TraxSequencer.SAMPLES_PER_SECOND; - } - - public get traxData(): TraxData - { - return this._traxData; - } - - public get length(): number - { - return this._lengthSamples / TraxSequencer.SAMPLES_PER_SECOND; - } -} diff --git a/src/nitro/sound/common/index.ts b/src/nitro/sound/common/index.ts index 58a0347e..c33b01f6 100644 --- a/src/nitro/sound/common/index.ts +++ b/src/nitro/sound/common/index.ts @@ -1,6 +1,2 @@ +export * from './SongDataEntry'; export * from './SongStartRequestData'; -export * from './TraxChannel'; -export * from './TraxChannelItem'; -export * from './TraxData'; -export * from './TraxSample'; -export * from './TraxSequencer'; diff --git a/src/nitro/sound/events/NotifyPlayedSongEvent.ts b/src/nitro/sound/events/NotifyPlayedSongEvent.ts new file mode 100644 index 00000000..678be477 --- /dev/null +++ b/src/nitro/sound/events/NotifyPlayedSongEvent.ts @@ -0,0 +1,27 @@ +import { NitroEvent } from '../../../events'; + +export class NotifyPlayedSongEvent extends NitroEvent +{ + public static readonly NOTIFY_PLAYED_SONG = 'UIEW_NOTIFY_PLAYED_SONG'; + + private _name: string; + private _creator: string; + + constructor(name:string, creator:string) + { + super(NotifyPlayedSongEvent.NOTIFY_PLAYED_SONG); + + this._name = name; + this._creator = creator; + } + + public get name(): string + { + return this._name; + } + + public get creator(): string + { + return this._creator; + } +} diff --git a/src/nitro/sound/events/NowPlayingEvent.ts b/src/nitro/sound/events/NowPlayingEvent.ts new file mode 100644 index 00000000..967dc8e3 --- /dev/null +++ b/src/nitro/sound/events/NowPlayingEvent.ts @@ -0,0 +1,35 @@ +import { NitroEvent } from '../../../events'; + +export class NowPlayingEvent extends NitroEvent +{ + public static readonly NPE_USER_PLAY_SONG = 'NPE_USER_PLAY_SONG'; + public static readonly NPW_USER_STOP_SONG = 'NPW_USER_STOP_SONG'; + public static readonly NPE_SONG_CHANGED = 'NPE_SONG_CHANGED'; + + private _id:number; + private _position:number; + private _priority:number; + + constructor(k:string, priority:number, id:number, position:number) + { + super(k); + this._id = id; + this._position = position; + this._priority = priority; + } + + public get id():number + { + return this._id; + } + + public get position():number + { + return this._position; + } + + public get priority():number + { + return this._priority; + } +} diff --git a/src/nitro/sound/events/PlayListStatusEvent.ts b/src/nitro/sound/events/PlayListStatusEvent.ts new file mode 100644 index 00000000..19837173 --- /dev/null +++ b/src/nitro/sound/events/PlayListStatusEvent.ts @@ -0,0 +1,12 @@ +import { NitroEvent } from '../../../events'; + +export class PlayListStatusEvent extends NitroEvent +{ + public static readonly PLUE_PLAY_LIST_UPDATED = 'PLUE_PLAY_LIST_UPDATED'; + public static readonly PLUE_PLAY_LIST_FULL = 'PLUE_PLAY_LIST_FULL'; + + constructor(k:string) + { + super(k); + } +} diff --git a/src/nitro/sound/events/SongDiskInventoryReceivedEvent.ts b/src/nitro/sound/events/SongDiskInventoryReceivedEvent.ts new file mode 100644 index 00000000..cc61c9d0 --- /dev/null +++ b/src/nitro/sound/events/SongDiskInventoryReceivedEvent.ts @@ -0,0 +1,11 @@ +import { NitroEvent } from '../../../events'; + +export class SongDiskInventoryReceivedEvent extends NitroEvent +{ + public static readonly SDIR_SONG_DISK_INVENTORY_RECEIVENT_EVENT = 'SDIR_SONG_DISK_INVENTORY_RECEIVENT_EVENT'; + + constructor(k:string) + { + super(k); + } +} diff --git a/src/nitro/sound/events/SongInfoReceivedEvent.ts b/src/nitro/sound/events/SongInfoReceivedEvent.ts new file mode 100644 index 00000000..3536a333 --- /dev/null +++ b/src/nitro/sound/events/SongInfoReceivedEvent.ts @@ -0,0 +1,19 @@ +import { NitroEvent } from '../../../events'; + +export class SongInfoReceivedEvent extends NitroEvent +{ + public static readonly SIR_TRAX_SONG_INFO_RECEIVED = 'SIR_TRAX_SONG_INFO_RECEIVED'; + + private _id:number; + + constructor(k:string, _arg_2:number) + { + super(k); + this._id = _arg_2; + } + + public get id():number + { + return this._id; + } +} diff --git a/src/nitro/sound/events/SoundManagerEvent.ts b/src/nitro/sound/events/SoundManagerEvent.ts index 117bdf3d..d317b114 100644 --- a/src/nitro/sound/events/SoundManagerEvent.ts +++ b/src/nitro/sound/events/SoundManagerEvent.ts @@ -4,8 +4,16 @@ export class SoundManagerEvent extends NitroEvent { public static TRAX_SONG_COMPLETE: string = 'SME_TRAX_SONG_COMPLETE'; - constructor(type: string) + private _id: number; + + constructor(type: string, id: number) { super(type); + this._id = id; + } + + public get id(): number + { + return this._id; } } diff --git a/src/nitro/sound/events/index.ts b/src/nitro/sound/events/index.ts index a1ffd069..6ed30e60 100644 --- a/src/nitro/sound/events/index.ts +++ b/src/nitro/sound/events/index.ts @@ -1 +1,6 @@ +export * from './NotifyPlayedSongEvent'; +export * from './NowPlayingEvent'; +export * from './PlayListStatusEvent'; +export * from './SongDiskInventoryReceivedEvent'; +export * from './SongInfoReceivedEvent'; export * from './SoundManagerEvent'; diff --git a/src/nitro/sound/index.ts b/src/nitro/sound/index.ts index 283166d8..024ab71a 100644 --- a/src/nitro/sound/index.ts +++ b/src/nitro/sound/index.ts @@ -2,3 +2,4 @@ export * from './common'; export * from './events'; export * from './music'; export * from './SoundManager'; +export * from './trax'; diff --git a/src/nitro/sound/music/JukeboxPlaylistController.ts b/src/nitro/sound/music/JukeboxPlaylistController.ts new file mode 100644 index 00000000..4d310dfd --- /dev/null +++ b/src/nitro/sound/music/JukeboxPlaylistController.ts @@ -0,0 +1,198 @@ +import { IMessageEvent, IPlaylistController, ISongInfo } from '../../../api'; +import { GetJukeboxPlayListMessageComposer, JukeboxPlayListFullMessageEvent, JukeboxSongDisksMessageEvent, NowPlayingMessageEvent } from '../../communication'; +import { Nitro } from '../../Nitro'; +import { SongDataEntry } from '../common/SongDataEntry'; +import { NowPlayingEvent, PlayListStatusEvent, SongInfoReceivedEvent } from '../events'; +import { MusicPriorities } from './MusicPriorities'; + +export class JukeboxPlaylistController implements IPlaylistController +{ + private _isPlaying = false; + private _entries:ISongInfo[]; + private _currentEntryId: number; + private _missingSongInfo:number[]; + private _playPosition: number; + private _disposed: boolean = false; + + private _messageEvents: IMessageEvent[]; + + constructor() + { + this._entries = []; + this._missingSongInfo = []; + + this._messageEvents = [ + new NowPlayingMessageEvent(this.onNowPlayingMessageEvent.bind(this)), + new JukeboxSongDisksMessageEvent(this.onJukeboxSongDisksMessageEvent.bind(this)), + new JukeboxPlayListFullMessageEvent(this.onJukeboxPlayListFullMessageEvent.bind(this)) + ]; + + this.onSongInfoReceivedEvent = this.onSongInfoReceivedEvent.bind(this); + } + + public init(): void + { + this._messageEvents.forEach(event => Nitro.instance.communication.connection.addMessageEvent(event)); + //this._events.addEventListener(SoundManagerEvent.TRAX_SONG_COMPLETE, this.onSongFinishedPlayingEvent); + Nitro.instance.soundManager.events.addEventListener(SongInfoReceivedEvent.SIR_TRAX_SONG_INFO_RECEIVED, this.onSongInfoReceivedEvent); + } + + public get priority(): number + { + return MusicPriorities.PRIORITY_ROOM_PLAYLIST; + } + + private onNowPlayingMessageEvent(event: NowPlayingMessageEvent): void + { + const parser = event.getParser(); + + console.log(((((('Received Now Playing message with: ' + parser.currentSongId) + ', ') + parser.nextSongId) + ', ') + parser.syncCount)); + this._isPlaying = (parser.currentSongId !== -1); + + if(parser.currentSongId >= 0) + { + Nitro.instance.soundManager.musicController.playSong(parser.currentSongId, MusicPriorities.PRIORITY_ROOM_PLAYLIST, (parser.syncCount / 1000), 0, 1, 1); + this._currentEntryId = parser.currentSongId; + } + else + { + this.stopPlaying(); + } + + if(parser.nextSongId >= 0) Nitro.instance.soundManager.musicController.addSongInfoRequest(parser.nextSongId); + + this._playPosition = parser.currentPosition; + //Dispatch local event NowPlayingEvent + Nitro.instance.soundManager.events.dispatchEvent(new NowPlayingEvent(NowPlayingEvent.NPE_SONG_CHANGED, MusicPriorities.PRIORITY_ROOM_PLAYLIST, parser.currentSongId, parser.currentPosition)); + } + + private onJukeboxSongDisksMessageEvent(event: JukeboxSongDisksMessageEvent): void + { + const parser = event.getParser(); + + console.log(('Received Jukebox song disks (=playlist) message, length of playlist: ' + parser.songDisks.length)); + this._entries = []; + for(let i = 0; i < parser.songDisks.length; i++) + { + const songId = parser.songDisks.getWithIndex(i); + const diskId = parser.songDisks.getKey(i); + let songInfo = (Nitro.instance.soundManager.musicController.getSongInfo(songId) as SongDataEntry); + if(songInfo == null) + { + songInfo = new SongDataEntry(songId, -1, null, null, null); + if(this._missingSongInfo.indexOf(songId) < 0) + { + this._missingSongInfo.push(songId); + Nitro.instance.soundManager.musicController.requestSongInfoWithoutSamples(songId); + } + } + songInfo.diskId = diskId; + this._entries.push(songInfo); + } + if(this._missingSongInfo.length == 0) + { + Nitro.instance.soundManager.events.dispatchEvent(new PlayListStatusEvent(PlayListStatusEvent.PLUE_PLAY_LIST_UPDATED)); + } + } + + private onJukeboxPlayListFullMessageEvent(event: JukeboxPlayListFullMessageEvent): void + { + console.log('Received jukebox playlist full message.'); + Nitro.instance.soundManager.events.dispatchEvent(new PlayListStatusEvent(PlayListStatusEvent.PLUE_PLAY_LIST_FULL)); + } + + private onSongInfoReceivedEvent(songInfoEvent:SongInfoReceivedEvent):void + { + for(let i = 0; i < this.length; i++) + { + const songData = this._entries[i]; + if(songData.id == songInfoEvent.id) + { + const diskId = songData.diskId; + const updatedSongData = Nitro.instance.soundManager.musicController.getSongInfo(songInfoEvent.id); + if(updatedSongData != null) + { + updatedSongData.diskId = diskId; + this._entries[i] = updatedSongData; + } + break; + } + } + const _local_3 = this._missingSongInfo.indexOf(songInfoEvent.id); + if(_local_3 >= 0) + { + this._missingSongInfo.splice(_local_3, 1); + } + if(this._missingSongInfo.length == 0) + { + Nitro.instance.soundManager.events.dispatchEvent(new PlayListStatusEvent(PlayListStatusEvent.PLUE_PLAY_LIST_UPDATED)); + } + } + + public stopPlaying():void + { + Nitro.instance.soundManager.musicController.stop(this.priority); + this._currentEntryId = -1; + this._playPosition = -1; + this._isPlaying = false; + } + + public get length(): number + { + if(!this._entries) + { + return 0; + } + return this._entries.length; + } + + public get playPosition(): number + { + return this._playPosition; + } + + public get nowPlayingSongId(): number + { + return this._currentEntryId; + } + + public get isPlaying(): boolean + { + return this._isPlaying; + } + + public get entries(): ISongInfo[] + { + return this._entries; + } + + public getEntry(k: number): ISongInfo + { + if(((k < 0) || (k >= this._entries.length))) + { + return null; + } + return this._entries[k]; + } + + public requestPlayList(): void + { + Nitro.instance.communication.connection.send(new GetJukeboxPlayListMessageComposer()); + } + + public dispose(): void + { + if(!this._disposed) + { + this._disposed = true; + this.stopPlaying(); + if(Nitro.instance.soundManager.events) + { + Nitro.instance.soundManager.events.removeEventListener(SongInfoReceivedEvent.SIR_TRAX_SONG_INFO_RECEIVED, this.onSongInfoReceivedEvent); + } + this._messageEvents.forEach(event => Nitro.instance.communication.connection.removeMessageEvent(event)); + this._messageEvents = null; + //this._events.removeEventListener(SoundControllerEvent.TRAX_SONG_COMPLETE, this.onSongFinishedPlayingEvent); + } + } +} diff --git a/src/nitro/sound/music/MusicController.ts b/src/nitro/sound/music/MusicController.ts new file mode 100644 index 00000000..8a93bd73 --- /dev/null +++ b/src/nitro/sound/music/MusicController.ts @@ -0,0 +1,599 @@ +import { IAdvancedMap, IMessageEvent, IMusicController, IPlaylistController } from '../../../api'; +import { ISongInfo } from '../../../api/nitro/sound/common/ISongInfo'; +import { AdvancedMap } from '../../../core'; +import { RoomObjectSoundMachineEvent } from '../../../events'; +import { GetNowPlayingMessageComposer, GetSongInfoMessageComposer, GetUserSongDisksMessageComposer, TraxSongInfoMessageEvent, UserSongDisksInventoryMessageEvent } from '../../communication'; +import { Nitro } from '../../Nitro'; +import { SongDataEntry } from '../common/SongDataEntry'; +import { SongStartRequestData } from '../common/SongStartRequestData'; +import { NotifyPlayedSongEvent, NowPlayingEvent, SongDiskInventoryReceivedEvent, SongInfoReceivedEvent, SoundManagerEvent } from '../events'; +import { TraxData } from '../trax/TraxData'; +import { JukeboxPlaylistController } from './JukeboxPlaylistController'; +import { MusicPlayer } from './MusicPlayer'; +import { MusicPriorities } from './MusicPriorities'; + +export class MusicController implements IMusicController +{ + public static readonly SKIP_POSITION_SET: number = -1; + private static readonly MAXIMUM_NOTIFY_PRIORITY: number = MusicPriorities.PRIORITY_ROOM_PLAYLIST; + + private _timerInstance: number; + private _songRequestList: number[]; + private _requestedSongs: Map; + private _availableSongs: Map; + private _songRequestsPerPriority: SongStartRequestData[]; + private _songRequestCountsPerPriority:number[]; + private _diskInventoryMissingData: number[]; + private _songDiskInventory: IAdvancedMap; + private _priorityPlaying:number = -1; + private _requestNumberPlaying: number = -1; + private _messageEvents: IMessageEvent[]; + private _roomItemPlaylist: IPlaylistController; + private _musicPlayer: MusicPlayer; + + private _songIdPlaying: number; + private _previousNotifiedSongId: number; + private _previousNotificationTime: number = -1; + + constructor() + { + this._timerInstance = -1; + this._songRequestList = []; + this._requestedSongs = new Map(); + this._availableSongs = new Map(); + this._songDiskInventory = new AdvancedMap(); + this._songRequestsPerPriority = []; + this._songRequestCountsPerPriority = []; + this._diskInventoryMissingData = []; + this._songIdPlaying = -1; + this._previousNotifiedSongId = -1; + + + this._messageEvents = [ + new TraxSongInfoMessageEvent(this.onTraxSongInfoMessageEvent.bind(this)), + new UserSongDisksInventoryMessageEvent(this.onSongDiskInventoryMessage.bind(this)) + ]; + + this.onJukeboxInit = this.onJukeboxInit.bind(this); + this.onJukeboxDispose = this.onJukeboxDispose.bind(this); + this.onSoundMachineInit = this.onSoundMachineInit.bind(this); + this.onSoundMachineDispose = this.onSoundMachineDispose.bind(this); + + this.onTraxSongComplete = this.onTraxSongComplete.bind(this); + } + + public init(): void + { + this._timerInstance = window.setInterval(this.onTick.bind(this), 1000); + this._musicPlayer = new MusicPlayer(Nitro.instance.getConfiguration('external.samples.url')); + + this._messageEvents.forEach(event => Nitro.instance.communication.connection.addMessageEvent(event)); + + Nitro.instance.roomEngine.events.addEventListener(RoomObjectSoundMachineEvent.JUKEBOX_INIT, this.onJukeboxInit); + Nitro.instance.roomEngine.events.addEventListener(RoomObjectSoundMachineEvent.JUKEBOX_DISPOSE, this.onJukeboxDispose); + Nitro.instance.roomEngine.events.addEventListener(RoomObjectSoundMachineEvent.SOUND_MACHINE_INIT, this.onSoundMachineInit); + Nitro.instance.roomEngine.events.addEventListener(RoomObjectSoundMachineEvent.SOUND_MACHINE_DISPOSE, this.onSoundMachineDispose); + + Nitro.instance.soundManager.events.addEventListener(SoundManagerEvent.TRAX_SONG_COMPLETE, this.onTraxSongComplete); + } + + public getRoomItemPlaylist(_arg_1?: number): IPlaylistController + { + return this._roomItemPlaylist; + } + + public get songDiskInventory(): IAdvancedMap + { + return this._songDiskInventory; + } + + public getSongDiskInventorySize(): number + { + return this._songDiskInventory.length; + } + + public getSongDiskInventoryDiskId(k: number): number + { + if(((k >= 0) && (k < this._songDiskInventory.length))) + { + return this._songDiskInventory.getKey(k); + } + return -1; + } + + public getSongDiskInventorySongId(k: number): number + { + if(((k >= 0) && (k < this._songDiskInventory.length))) + { + return this._songDiskInventory.getWithIndex(k); + } + return -1; + } + + public getSongInfo(k: number): ISongInfo + { + const _local_2:SongDataEntry = this.getSongDataEntry(k); + if(_local_2 == null) + { + this.requestSongInfoWithoutSamples(k); + } + return _local_2; + } + + public getSongIdPlayingAtPriority(k: number): number + { + if(k != this._priorityPlaying) + { + return -1; + } + return this._songIdPlaying; + } + + public stop(priority: number): void + { + const isCurrentPlayingPriority = (priority === this._priorityPlaying); + const isTopRequestPriority = (this.getTopRequestPriority() === priority); + if(isCurrentPlayingPriority) + { + this.resetSongStartRequest(priority); + this.stopSongAtPriority(priority); + } + else + { + this.resetSongStartRequest(priority); + if(isTopRequestPriority) + { + this.reRequestSongAtPriority(this._priorityPlaying); + } + } + } + + public addSongInfoRequest(k: number): void + { + this.requestSong(k, true); + } + + public requestSongInfoWithoutSamples(k: number): void + { + this.requestSong(k, false); + } + + public requestUserSongDisks(): void + { + Nitro.instance.communication.connection.send(new GetUserSongDisksMessageComposer()); + } + + public updateVolume(_arg_1: number): void + { + this._musicPlayer.setVolume(_arg_1); + } + + public dispose(): void + { + if(this._timerInstance) + { + clearInterval(this._timerInstance); + this._timerInstance = null; + } + + this._messageEvents.forEach(event => Nitro.instance.communication.connection.removeMessageEvent(event)); + + Nitro.instance.roomEngine.events.removeEventListener(RoomObjectSoundMachineEvent.JUKEBOX_INIT, this.onJukeboxInit); + Nitro.instance.roomEngine.events.removeEventListener(RoomObjectSoundMachineEvent.JUKEBOX_DISPOSE, this.onJukeboxDispose); + Nitro.instance.roomEngine.events.removeEventListener(RoomObjectSoundMachineEvent.SOUND_MACHINE_INIT, this.onSoundMachineInit); + Nitro.instance.roomEngine.events.removeEventListener(RoomObjectSoundMachineEvent.SOUND_MACHINE_DISPOSE, this.onSoundMachineDispose); + Nitro.instance.soundManager.events.removeEventListener(SoundManagerEvent.TRAX_SONG_COMPLETE, this.onTraxSongComplete); + } + + public get samplesIdsInUse(): number[] + { + let _local_3:SongStartRequestData; + let _local_4:SongDataEntry; + let k = []; + for(let i = 0; i < this._songRequestsPerPriority.length; i++) + { + if(this._songRequestsPerPriority[i] != null) + { + _local_3 = this._songRequestsPerPriority[i]; + _local_4 = this._availableSongs.get(_local_3.songId); + if(_local_4) + { + const songData = _local_4.songData; + if(songData.length > 0) + { + const traxData = new TraxData(songData); + k = k.concat(traxData.getSampleIds()); + } + } + } + } + return k; + } + + public onSongLoaded(songId: number): void + { + console.log(('Song loaded : ' + songId)); + const priority = this.getTopRequestPriority(); + if(priority >= 0) + { + const songIdAtTopPriority = this.getSongIdRequestedAtPriority(priority); + if(songId === songIdAtTopPriority) + { + this.playSongObject(priority, songId); + } + } + } + + public samplesUnloaded(_arg_1: number[]): void + { + throw new Error('Method not implemented.'); + } + + protected onTraxSongComplete(k:SoundManagerEvent):void + { + console.log((('Song ' + k.id) + ' finished playing')); + if(this.getSongIdPlayingAtPriority(this._priorityPlaying) == k.id) + { + if(((this.getTopRequestPriority() == this._priorityPlaying) && (this.getSongRequestCountAtPriority(this._priorityPlaying) == this._requestNumberPlaying))) + { + this.resetSongStartRequest(this._priorityPlaying); + } + const priorityPlaying = this._priorityPlaying; + this.playSongWithHighestPriority(); + if(priorityPlaying >= MusicPriorities.PRIORITY_SONG_PLAY) + { + Nitro.instance.soundManager.events.dispatchEvent(new NowPlayingEvent(NowPlayingEvent.NPW_USER_STOP_SONG, priorityPlaying, k.id, -1)); + } + } + } + + private onTraxSongInfoMessageEvent(event: TraxSongInfoMessageEvent): void + { + const parser = event.getParser(); + + for(const song of parser.songs) + { + const songAvailable = !!this.getSongDataEntry(song.id); + const areSamplesRequested = !!this.areSamplesRequested(song.id); + + if(!songAvailable) + { + if(!areSamplesRequested) + { + //_local_9 = this._soundManager.loadTraxSong(_local_6.id, _local_6.data); + } + + const songInfoEntry: SongDataEntry = new SongDataEntry(song.id, song.length, song.name, song.creator, song.data); + this._availableSongs.set(song.id, songInfoEntry); + + const topRequestPriotityIndex: number = this.getTopRequestPriority(); + const songId: number = this.getSongIdRequestedAtPriority(topRequestPriotityIndex); + if(song.id === songId) + { + this._musicPlayer.preloadSamplesForSong(song.data).then(() => this.onSongLoaded(song.id)); + //this.playSongObject(topRequestPriotityIndex, songId); + } + Nitro.instance.soundManager.events.dispatchEvent(new SongInfoReceivedEvent(SongInfoReceivedEvent.SIR_TRAX_SONG_INFO_RECEIVED, song.id)); + while(this._diskInventoryMissingData.indexOf(song.id) != -1) + { + this._diskInventoryMissingData.splice(this._diskInventoryMissingData.indexOf(song.id), 1); + if(this._diskInventoryMissingData.length === 0) + { + Nitro.instance.soundManager.events.dispatchEvent(new SongDiskInventoryReceivedEvent(SongDiskInventoryReceivedEvent.SDIR_SONG_DISK_INVENTORY_RECEIVENT_EVENT)); + } + } + console.log(('Received song info : ' + song.id)); + + } + } + } + + private onSongDiskInventoryMessage(event: UserSongDisksInventoryMessageEvent): void + { + const parser = event.getParser(); + + this._songDiskInventory.reset(); + for(let i = 0; i < parser.songDiskCount; i++) + { + const diskId = parser.getDiskId(i); + const songId = parser.getSongId(i); + this._songDiskInventory.add(diskId, songId); + + if(!this._availableSongs.get(songId)) + { + this._diskInventoryMissingData.push(songId); + this.requestSongInfoWithoutSamples(songId); + } + } + if(this._diskInventoryMissingData.length === 0) + { + Nitro.instance.soundManager.events.dispatchEvent(new SongDiskInventoryReceivedEvent(SongDiskInventoryReceivedEvent.SDIR_SONG_DISK_INVENTORY_RECEIVENT_EVENT)); + } + } + + private onTick(): void + { + if(this._songRequestList.length === 0) return; + + Nitro.instance.communication.connection.send(new GetSongInfoMessageComposer(...this._songRequestList)); + console.log(('Requested song info\'s : ' + this._songRequestList)); + this._songRequestList = []; + } + + private requestSong(songId: number, arg2: boolean): void + { + if(!this._requestedSongs.get(songId)) + { + this._requestedSongs.set(songId, arg2); + this._songRequestList.push(songId); + } + } + + private areSamplesRequested(k:number):boolean + { + if(!this._requestedSongs.get(k)) + { + return false; + } + return this._requestedSongs.get(k); + } + + private processSongEntryForPlaying(k:number, _arg_2:boolean=true):boolean + { + const songData:SongDataEntry = this.getSongDataEntry(k); + if(!songData) + { + this.addSongInfoRequest(k); + return false; + } + /* if(_local_3.soundObject == null) + { + _local_3.soundObject = this._soundManager.loadTraxSong(_local_3.id, _local_3.songData); + } + const _local_4:IHabboSound = _local_3.soundObject; + if(!_local_4.ready) + { + return false; + } */ + return true; + } + + public playSong(songId: number, priority: number, startPos: number = 0, playLength: number = 0, fadeInSeconds: number = 0.5, fadeOutSeconds: number = 0.5): boolean + { + console.log(('Requesting ' + songId + ' for playing')); + if(!this.addSongStartRequest(priority, songId, startPos, playLength, fadeInSeconds, fadeOutSeconds)) + { + return false; + } + if(!this.processSongEntryForPlaying(songId)) + { + return false; + } + if(priority >= this._priorityPlaying) + { + this.playSongObject(priority, songId); + } + else + { + console.log(((('Higher priority song blocked playing. Stored song ' + songId) + ' for priority ') + priority)); + } + return true; + } + + private playSongObject(priority:number, songId:number):boolean + { + if((((songId == -1) || (priority < 0)) || (priority >= MusicPriorities.PRIORITY_COUNT))) + { + return false; + } + const _local_3 = false; + // if(this.stopSongAtPriority(this._priorityPlaying)) + // { + // _local_3 = true; + // } + const songData:SongDataEntry = this.getSongDataEntry(songId); + if(!songData) + { + console.log((('WARNING: Unable to find song entry id ' + songId) + ' that was supposed to be loaded.')); + return false; + } + if(_local_3) + { + console.log(('Waiting previous song to stop before playing song ' + songId)); + return true; + } + this._musicPlayer.setVolume(Nitro.instance.soundManager.traxVolume); + let startPos = MusicController.SKIP_POSITION_SET; + let playLength = 0; + let fadeInSeconds = 2; + let fadeOutSeconds = 1; + + const songRequestData:SongStartRequestData = this.getSongStartRequest(priority); + + console.log(songRequestData); + + if(songRequestData) + { + startPos = songRequestData.startPos; + playLength = songRequestData.playLength; + fadeInSeconds = songRequestData.fadeInSeconds; + fadeOutSeconds = songRequestData.fadeOutSeconds; + } + if(startPos >= (songData.length / 1000)) + { + console.log('start position is too far'); + return false; + } + if(startPos <= MusicController.SKIP_POSITION_SET) + { + startPos = 0; + } + + startPos = Math.trunc(startPos); + /* + _local_5.fadeInSeconds = _local_8; + _local_5.fadeOutSeconds = _local_9; + _local_5.position = _local_6; + _local_5.play(_local_7); + */ + + this._priorityPlaying = priority; + this._requestNumberPlaying = this.getSongRequestCountAtPriority(priority); + this._songIdPlaying = songId; + if(this._priorityPlaying <= MusicController.MAXIMUM_NOTIFY_PRIORITY) + { + this.notifySongPlaying(songData); + } + this._musicPlayer.play(songData.songData, startPos, playLength); + if(priority > MusicPriorities.PRIORITY_ROOM_PLAYLIST) + { + Nitro.instance.soundManager.events.dispatchEvent(new NowPlayingEvent(NowPlayingEvent.NPE_USER_PLAY_SONG, priority, songData.id, -1)); + } + console.log(((((((((('Started playing song ' + songId) + ' at position ') + startPos) + ' for ') + playLength) + ' seconds (length ') + (songData.length / 1000)) + ') with priority ') + priority)); + return true; + } + + private notifySongPlaying(k:SongDataEntry):void + { + const _local_2 = 8000; + const timeNow = Date.now(); + if(((k.length >= _local_2) && ((!(this._previousNotifiedSongId == k.id)) || (timeNow > (this._previousNotificationTime + _local_2))))) + { + Nitro.instance.soundManager.events.dispatchEvent(new NotifyPlayedSongEvent(k.name, k.creator)); + this._previousNotifiedSongId = k.id; + this._previousNotificationTime = timeNow; + } + } + + private addSongStartRequest(priority:number, songId:number, startPos:number, playLength:number, fadeInSeconds:number, fadeOutSeconds:number):boolean + { + if(((priority < 0) || (priority >= MusicPriorities.PRIORITY_COUNT))) + { + return false; + } + const songStartRequest = new SongStartRequestData(songId, startPos, playLength, fadeInSeconds, fadeOutSeconds); + this._songRequestsPerPriority[priority] = songStartRequest; + this._songRequestCountsPerPriority[priority] = (this._songRequestCountsPerPriority[priority] + 1); + return true; + } + + private getSongDataEntry(k:number):SongDataEntry + { + let entry:SongDataEntry; + if(this._availableSongs != null) + { + entry = (this._availableSongs.get(k)); + } + return entry; + } + + private getSongStartRequest(k:number):SongStartRequestData + { + return this._songRequestsPerPriority[k]; + } + + private getTopRequestPriority(): number + { + return this._songRequestsPerPriority.length - 1; + } + + private getSongIdRequestedAtPriority(priorityIndex: number): number + { + if(priorityIndex < 0 || priorityIndex >= MusicPriorities.PRIORITY_COUNT) return -1; + + if(!this._songRequestsPerPriority[priorityIndex]) return -1; + + return this._songRequestsPerPriority[priorityIndex].songId; + } + + private getSongRequestCountAtPriority(k:number):number + { + if(((k < 0) || (k >= MusicPriorities.PRIORITY_COUNT))) + { + return -1; + } + return this._songRequestCountsPerPriority[k]; + } + + private playSongWithHighestPriority():void + { + let _local_3:number; + this._priorityPlaying = -1; + this._songIdPlaying = -1; + this._requestNumberPlaying = -1; + const k = this.getTopRequestPriority(); + let _local_2 = k; + while(_local_2 >= 0) + { + _local_3 = this.getSongIdRequestedAtPriority(_local_2); + if(((_local_3 >= 0) && (this.playSongObject(_local_2, _local_3)))) + { + return; + } + _local_2--; + } + } + + private resetSongStartRequest(k:number):void + { + if(((k >= 0) && (k < MusicPriorities.PRIORITY_COUNT))) + { + this._songRequestsPerPriority[k] = null; + } + } + + private reRequestSongAtPriority(k:number):void + { + this._songRequestCountsPerPriority[k] = (this._songRequestCountsPerPriority[k] + 1); + } + + private stopSongAtPriority(priority:number):boolean + { + if(((priority == this._priorityPlaying) && (this._priorityPlaying >= 0))) + { + const songIdAtPriority = this.getSongIdPlayingAtPriority(priority); + if(songIdAtPriority >= 0) + { + const songData = this.getSongDataEntry(songIdAtPriority); + //this.stopSongDataEntry(_local_3); + console.log('stopping song ' + songIdAtPriority); + this._musicPlayer.stop(); + return true; + } + } + return false; + } + + private onSoundMachineInit(k:Event):void + { + this.disposeRoomPlaylist(); + //this._roomItemPlaylist = (new SoundMachinePlayListController(this._soundManager, this, this._events) as IPlaylistController); + } + + private onSoundMachineDispose(k:Event):void + { + this.disposeRoomPlaylist(); + } + + private onJukeboxInit(k:Event):void + { + this.disposeRoomPlaylist(); + this._roomItemPlaylist = (new JukeboxPlaylistController() as IPlaylistController); + this._roomItemPlaylist.init(); + Nitro.instance.communication.connection.send(new GetNowPlayingMessageComposer()); + } + + private onJukeboxDispose(k:Event):void + { + this.disposeRoomPlaylist(); + } + + private disposeRoomPlaylist():void + { + if(this._roomItemPlaylist != null) + { + this._roomItemPlaylist.dispose(); + this._roomItemPlaylist = null; + } + } +} diff --git a/src/nitro/sound/music/MusicManager.ts b/src/nitro/sound/music/MusicManager.ts deleted file mode 100644 index bd3c64dc..00000000 --- a/src/nitro/sound/music/MusicManager.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { IMusicManager, INitroEvent } from '../../../api'; -import { NitroManager } from '../../../core'; -import { RoomObjectSoundMachineEvent } from '../../../events'; -import { GetSongInfoMessageComposer, JukeboxPlayListFullMessageEvent, JukeboxSongDisksMessageEvent, NowPlayingMessageEvent, SongInfoEntry, TraxSongInfoMessageEvent, UserSongDisksInventoryMessageEvent } from '../../communication'; -import { Nitro } from '../../Nitro'; -import { SongStartRequestData } from '../common'; -import { SoundManagerEvent } from '../events'; -import { MusicPriorities } from './MusicPriorities'; - -export class MusicManager extends NitroManager implements IMusicManager -{ - public static readonly SKIP_POSITION_SET: number = -1; - private static readonly MAXIMUM_NOTIFY_PRIORITY: number = MusicPriorities.PRIORITY_ROOM_PLAYLIST; - - private _timerInstance: any; - private _songRequestList: number[]; - private _requestedSongs: Map; - private _availableSongs: Map; - private _songRequestsPerPriority: SongStartRequestData[]; - - private _currentEntryId: number; - private _playPosition: number; - private _isPlaying: boolean; - - constructor() - { - super(); - - this._timerInstance = null; - this._songRequestList = []; - this._requestedSongs = new Map(); - this._availableSongs = new Map(); - this._songRequestsPerPriority = []; - - this._currentEntryId = -1; - this._playPosition = -1; - this._isPlaying = false; - - this.onEvent = this.onEvent.bind(this); - - this._timerInstance = setInterval(this.onTick.bind(this), 1000); - } - - public onInit(): void - { - Nitro.instance.communication.connection.addMessageEvent(new TraxSongInfoMessageEvent(this.onEvent)); - Nitro.instance.communication.connection.addMessageEvent(new UserSongDisksInventoryMessageEvent(this.onEvent)); - Nitro.instance.communication.connection.addMessageEvent(new NowPlayingMessageEvent(this.onNowPlayingMessageEvent.bind(this))); - Nitro.instance.communication.connection.addMessageEvent(new JukeboxSongDisksMessageEvent(this.onEvent)); - Nitro.instance.communication.connection.addMessageEvent(new JukeboxPlayListFullMessageEvent(this.onEvent)); - Nitro.instance.roomEngine.events.addEventListener(RoomObjectSoundMachineEvent.JUKEBOX_INIT, this.onEvent); - Nitro.instance.roomEngine.events.addEventListener(RoomObjectSoundMachineEvent.JUKEBOX_DISPOSE, this.onEvent); - Nitro.instance.roomEngine.events.addEventListener(RoomObjectSoundMachineEvent.SOUND_MACHINE_INIT, this.onEvent); - Nitro.instance.roomEngine.events.addEventListener(RoomObjectSoundMachineEvent.SOUND_MACHINE_DISPOSE, this.onEvent); - this.events.addEventListener(SoundManagerEvent.TRAX_SONG_COMPLETE, this.onEvent); - } - - public onDispose(): void - { - if(this._timerInstance) - { - clearInterval(this._timerInstance); - this._timerInstance = null; - } - - Nitro.instance.communication.connection.removeMessageEvent(new TraxSongInfoMessageEvent(this.onEvent)); - Nitro.instance.communication.connection.removeMessageEvent(new UserSongDisksInventoryMessageEvent(this.onEvent)); - Nitro.instance.communication.connection.removeMessageEvent(new NowPlayingMessageEvent(this.onNowPlayingMessageEvent.bind(this))); - Nitro.instance.communication.connection.removeMessageEvent(new JukeboxSongDisksMessageEvent(this.onEvent)); - Nitro.instance.communication.connection.removeMessageEvent(new JukeboxPlayListFullMessageEvent(this.onEvent)); - Nitro.instance.roomEngine.events.removeEventListener(RoomObjectSoundMachineEvent.JUKEBOX_INIT, this.onEvent); - Nitro.instance.roomEngine.events.removeEventListener(RoomObjectSoundMachineEvent.JUKEBOX_DISPOSE, this.onEvent); - Nitro.instance.roomEngine.events.removeEventListener(RoomObjectSoundMachineEvent.SOUND_MACHINE_INIT, this.onEvent); - Nitro.instance.roomEngine.events.removeEventListener(RoomObjectSoundMachineEvent.SOUND_MACHINE_DISPOSE, this.onEvent); - this.events.removeEventListener(SoundManagerEvent.TRAX_SONG_COMPLETE, this.onEvent); - } - - private onEvent(event: INitroEvent): void - { - - } - - private onTraxSongInfoMessageEvent(event: TraxSongInfoMessageEvent): void - { - const parser = event.getParser(); - - for(const song of parser.songs) - { - const songAvailable: boolean = (this._availableSongs.get(song.id) !== null); - const areSamplesRequested: boolean = (this._requestedSongs.get(song.id) !== null); - - if(!songAvailable) - { - if(areSamplesRequested) - { - //LoadTraxSong - } - - const songInfoEntry: SongInfoEntry = new SongInfoEntry(song.id, song.length, song.name, song.creator, song.data); - this._availableSongs.set(song.id, songInfoEntry); - - const topRequestPriotityIndex: number = this.getTopRequestPriority(); - const songId: number = this.getSongIdRequestedAtPriority(topRequestPriotityIndex); - - - } - } - } - - private onNowPlayingMessageEvent(event: NowPlayingMessageEvent): void - { - const parser = event.getParser(); - - this._isPlaying = (parser.currentSongId !== -1); - - if(parser.currentSongId >= 0) - { - this.playSong(parser.currentSongId, MusicPriorities.PRIORITY_ROOM_PLAYLIST, (parser.syncCount / 1000), 0, 1, 1); - } - else - { - this.stopPlaying(); - } - - if(parser.nextSongId >= 0) this.requestSong(parser.nextSongId, true); - - this._playPosition = parser.currentPosition; - //Dispatch local event NowPlayingEvent - } - - private onTick(): void - { - if(this._songRequestList.length === 0) return; - - Nitro.instance.communication.connection.send(new GetSongInfoMessageComposer(...this._songRequestList)); - this._songRequestList = []; - } - - private requestSong(songId: number, arg2: boolean): void - { - if(this._requestedSongs.get(songId) === null) - { - this._requestedSongs.set(songId, arg2); - this._songRequestList.push(songId); - } - } - - private playSong(songId: number, priority: number, startPos: number = 0, playLength: number = 0, fadeInSeconds: number = 0.5, fadeOutSeconds: number = 0.5) - { - - } - - private stopPlaying(): void - { - this._currentEntryId = -1; - this._playPosition = -1; - this._isPlaying = false; - } - - private getTopRequestPriority(): number - { - return this._songRequestsPerPriority.length - 1; - } - - private getSongIdRequestedAtPriority(priorityIndex: number): number - { - if(priorityIndex < 0 || priorityIndex >= MusicPriorities.PRIORITY_COUNT) return -1; - - if(!this._songRequestsPerPriority[priorityIndex]) return -1; - - return this._songRequestsPerPriority[priorityIndex].songId; - } - - public get playPosition(): number - { - return this._playPosition; - } - - public set playPosition(value: number) - { - this._playPosition = value; - } -} diff --git a/src/nitro/sound/music/MusicPlayer.ts b/src/nitro/sound/music/MusicPlayer.ts new file mode 100644 index 00000000..c87f6f88 --- /dev/null +++ b/src/nitro/sound/music/MusicPlayer.ts @@ -0,0 +1,226 @@ +import { Howl } from 'howler'; +import { Nitro } from '../../Nitro'; +import { SoundManagerEvent } from '../events'; +import { TraxData } from '../trax/TraxData'; + +export class MusicPlayer +{ + private _currentSong: TraxData | undefined; + private _startPos: number; + private _playLength: number; + private _isPlaying: boolean; + private _currentPos: number; + private _cache: Map; + private _sampleUrl: string; + + private _tickerInterval: number | undefined; + private _sequence: ISequenceEntry[][]; + + constructor(sampleUrl: string) + { + this._sampleUrl = sampleUrl; + this._isPlaying = false; + this._startPos = 0; + this._currentPos = 0; + this._playLength = 0; + this._sequence = []; + this._cache = new Map(); + } + + public async play(song: string, startPos: number = 0, playLength: number = -1): Promise + { + this.reset(); + + this._currentSong = new TraxData(song); + this._startPos = Math.trunc(startPos); + this._playLength = playLength; + this._currentPos = this._startPos; + //this.emit('loading'); + await this.preload(); + this._isPlaying = true; + //this.emit('playing', this._currentPos, this._playLength - 1); + this.tick(); // to evade initial 1 sec delay + this._tickerInterval = window.setInterval(() => this.tick(), 1000); + } + + public pause(): void + { + this._isPlaying = false; + //this.emit('paused', this._currentPos); + Howler.stop(); + } + + public resume(): void + { + this._isPlaying = true; + //this.emit('playing', this._currentPos, this._playLength - 1 ); + } + + public stop(): void + { + this.reset(); + //this.emit('stopped'); + } + + private reset(): void + { + this._isPlaying = false; + clearInterval(this._tickerInterval); + Howler.stop(); + this._currentSong = undefined; + this._startPos = 0; + this._playLength = 0; + this._sequence = []; + this._currentPos = 0; + } + + /** + * Sets global howler volume for all sounds + * @param volume value from 0.0 to 1.0 + */ + public setVolume(volume: number): void + { + Howler.volume(volume); + } + + /** + * Gets global howler volume for all sounds + * @returns value from 0.0 to 1.0 + */ + public getVolume(): number + { + return Howler.volume(); + } + + /** + * Gets sample from cache or loads it if not in cache + * @param id sample id + * @returns howl sound object + */ + public async getSample(id: number): Promise + { + let sample = this._cache.get(id); + + if(!sample) sample = await this.loadSong(id); + + return Promise.resolve(sample); + } + + private async preload(): Promise + { + this._sequence = []; + + if(!this._currentSong) return; + + for(const channel of this._currentSong.channels) + { + const sequenceEntryArray: ISequenceEntry[] = []; + for(const sample of channel.items) + { + const sampleSound = await this.getSample(sample.id); + + const sampleCount = Math.ceil( (sample.length * 2) / Math.ceil(sampleSound.duration())); + + for(let i = 0; i < sampleCount; i++) + { + for(let j = 0; j < Math.ceil(sampleSound.duration()); j++) + { + sequenceEntryArray.push({ sampleId: sample.id, offset: j }); + } + } + } + + this._sequence.push(sequenceEntryArray); + } + + if(this._playLength <= 0) this._playLength = Math.max(...this._sequence.map( (value: ISequenceEntry[] ) => value.length)); + } + + public async preloadSamplesForSong(song: string): Promise + { + const traxData = new TraxData(song); + + await Promise.all(traxData.getSampleIds().map(id => this.getSample(id))); + } + + private async loadSong(songId: number): Promise + { + return new Promise ((resolve, reject) => + { + const sample = new Howl({ + src: [this._sampleUrl.replace('%sample%', songId.toString())], + preload: true, + }); + + sample.once('load', () => + { + this._cache.set(songId, sample); + resolve(sample); + }); + + sample.once('loaderror', () => + { + console.log('failed to load sample ' + songId); + reject('failed to load sample ' + songId); + }); + }); + } + + + private tick(): void + { + if(this._currentPos > this._playLength - 1) + { + Nitro.instance.soundManager.events.dispatchEvent(new SoundManagerEvent(SoundManagerEvent.TRAX_SONG_COMPLETE, Nitro.instance.soundManager.musicController.getRoomItemPlaylist().nowPlayingSongId)); + this.stop(); + } + + if(this._isPlaying) + { + if(this._currentSong) + { + //this.emit('time', this._currentPos); + this.playPosition(this._currentPos); + } + + this._currentPos++; + } + } + + private playPosition(pos: number): void + { + if(!this._currentSong || !this._sequence) return; + + for(const sequencyEntry of this._sequence) + { + const entry = sequencyEntry[pos]; + + if(!entry) continue; + + // sample -1 is play none + // sample 0 is 1 second of empty noise + if(entry.sampleId === -1 || entry.sampleId === 0) continue; + + const sampleAudio = this._cache.get(entry.sampleId); + + if(!sampleAudio) continue; + + if(entry.offset === 0) + { + sampleAudio.play(); + } + else if(!sampleAudio.playing()) + { + sampleAudio.seek(entry.offset); + sampleAudio.play(); + } + } + } + +} + +interface ISequenceEntry +{ + sampleId: number; + offset: number; +} diff --git a/src/nitro/sound/music/index.ts b/src/nitro/sound/music/index.ts index 11d21659..d3553d41 100644 --- a/src/nitro/sound/music/index.ts +++ b/src/nitro/sound/music/index.ts @@ -1,2 +1,3 @@ -export * from './MusicManager'; +export * from './JukeboxPlaylistController'; +export * from './MusicController'; export * from './MusicPriorities'; diff --git a/src/nitro/sound/common/TraxChannel.ts b/src/nitro/sound/trax/TraxChannel.ts similarity index 99% rename from src/nitro/sound/common/TraxChannel.ts rename to src/nitro/sound/trax/TraxChannel.ts index 2b9a031b..02d2adef 100644 --- a/src/nitro/sound/common/TraxChannel.ts +++ b/src/nitro/sound/trax/TraxChannel.ts @@ -1,4 +1,5 @@ import { TraxChannelItem } from './TraxChannelItem'; + export class TraxChannel { private _id: number; diff --git a/src/nitro/sound/common/TraxChannelItem.ts b/src/nitro/sound/trax/TraxChannelItem.ts similarity index 100% rename from src/nitro/sound/common/TraxChannelItem.ts rename to src/nitro/sound/trax/TraxChannelItem.ts diff --git a/src/nitro/sound/common/TraxData.ts b/src/nitro/sound/trax/TraxData.ts similarity index 90% rename from src/nitro/sound/common/TraxData.ts rename to src/nitro/sound/trax/TraxData.ts index 7720915f..57545d3e 100644 --- a/src/nitro/sound/common/TraxData.ts +++ b/src/nitro/sound/trax/TraxData.ts @@ -1,5 +1,6 @@ import { TraxChannel } from './TraxChannel'; import { TraxChannelItem } from './TraxChannelItem'; + export class TraxData { private _channels: TraxChannel[]; @@ -8,6 +9,8 @@ export class TraxData constructor(data: string) { this._channels = []; + this._metaData = new Map(); + let channelLines: string[] = []; const lines: string[] = data.split(':'); @@ -75,17 +78,17 @@ export class TraxData public get hasMetaData(): boolean { - return this._metaData.get('meta') !== null; + return this._metaData.has('meta'); } public get metaCutMode(): boolean { - return this._metaData.get('c') !== null; + return this._metaData.has('c'); } - public get metaTempo(): number + public get metaTempo(): number | null { - const tempo: string = this._metaData.get('t'); + const tempo = this._metaData.get('t'); if(!tempo) return null; diff --git a/src/nitro/sound/trax/index.ts b/src/nitro/sound/trax/index.ts new file mode 100644 index 00000000..d0b41282 --- /dev/null +++ b/src/nitro/sound/trax/index.ts @@ -0,0 +1,3 @@ +export * from './TraxChannel'; +export * from './TraxChannelItem'; +export * from './TraxData'; diff --git a/yarn.lock b/yarn.lock index 311fa77d..19b0b998 100644 --- a/yarn.lock +++ b/yarn.lock @@ -229,6 +229,11 @@ resolved "https://registry.yarnpkg.com/@types/earcut/-/earcut-2.1.1.tgz#573a0af609f17005c751f6f4ffec49cfe358ea51" integrity sha512-w8oigUCDjElRHRRrMvn/spybSMyX8MTkKA5Dv+tS1IE/TgmNZPqUYtvYBXGY8cieSE66gm+szeK+bnbxC2xHTQ== +"@types/howler@^2.2.7": + version "2.2.7" + resolved "https://registry.yarnpkg.com/@types/howler/-/howler-2.2.7.tgz#5acfbed57f9e1d99b8dabe1b824729e1c1ea1fae" + integrity sha512-PEZldwZqJJw1PWRTpupyC7ajVTZA8aHd8nB/Y0n6zRZi5u8ktYDntsHj13ltEiBRqWwF06pASxBEvCTxniG8eA== + "@types/json-schema@^7.0.9": version "7.0.9" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz" @@ -712,6 +717,11 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +howler@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/howler/-/howler-2.2.3.tgz#a2eff9b08b586798e7a2ee17a602a90df28715da" + integrity sha512-QM0FFkw0LRX1PR8pNzJVAY25JhIWvbKMBFM4gqk+QdV+kPXOhleWGCB6AiAF/goGjIHK2e/nIElplvjQwhr0jg== + ignore@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz"