mirror of
https://github.com/billsonnn/nitro-renderer.git
synced 2024-11-30 03:00:51 +01:00
Merge branch 'feature/jukebox' into 'main'
Feature/jukebox See merge request nitro/nitro-renderer!24
This commit is contained in:
commit
b9dab09c60
@ -21,6 +21,7 @@
|
|||||||
"postinstall": "node ./post-install.js"
|
"postinstall": "node ./post-install.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"howler": "^2.2.3",
|
||||||
"@pixi/app": "~6.4.2",
|
"@pixi/app": "~6.4.2",
|
||||||
"@pixi/basis": "~6.4.2",
|
"@pixi/basis": "~6.4.2",
|
||||||
"@pixi/canvas-display": "~6.4.2",
|
"@pixi/canvas-display": "~6.4.2",
|
||||||
@ -58,6 +59,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/pako": "^1.0.3",
|
"@types/pako": "^1.0.3",
|
||||||
|
"@types/howler": "^2.2.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
||||||
"@typescript-eslint/parser": "^5.30.7",
|
"@typescript-eslint/parser": "^5.30.7",
|
||||||
"eslint": "^8.20.0",
|
"eslint": "^8.20.0",
|
||||||
|
25
src/api/nitro/sound/IMusicController.ts
Normal file
25
src/api/nitro/sound/IMusicController.ts
Normal file
@ -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<number, number>
|
||||||
|
init(): void;
|
||||||
|
dispose():void;
|
||||||
|
}
|
@ -1,6 +0,0 @@
|
|||||||
import { INitroManager } from '../../common';
|
|
||||||
|
|
||||||
export interface IMusicManager extends INitroManager
|
|
||||||
{
|
|
||||||
playPosition: number;
|
|
||||||
}
|
|
15
src/api/nitro/sound/IPlaylistController.ts
Normal file
15
src/api/nitro/sound/IPlaylistController.ts
Normal file
@ -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;
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import { INitroManager } from '../../common';
|
import { INitroManager } from '../../common';
|
||||||
import { IMusicManager } from './IMusicManager';
|
import { IMusicController } from './IMusicController';
|
||||||
|
|
||||||
export interface ISoundManager extends INitroManager
|
export interface ISoundManager extends INitroManager
|
||||||
{
|
{
|
||||||
musicManager: IMusicManager;
|
get musicController(): IMusicController;
|
||||||
|
get traxVolume(): number;
|
||||||
}
|
}
|
||||||
|
12
src/api/nitro/sound/common/ISongInfo.ts
Normal file
12
src/api/nitro/sound/common/ISongInfo.ts
Normal file
@ -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;
|
||||||
|
}
|
1
src/api/nitro/sound/common/index.ts
Normal file
1
src/api/nitro/sound/common/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './ISongInfo';
|
@ -1,2 +1,4 @@
|
|||||||
export * from './IMusicManager';
|
export * from './common';
|
||||||
|
export * from './IMusicController';
|
||||||
|
export * from './IPlaylistController';
|
||||||
export * from './ISoundManager';
|
export * from './ISoundManager';
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { IMessageDataWrapper, IMessageParser } from '../../../../../api';
|
import { IAdvancedMap, IMessageDataWrapper, IMessageParser } from '../../../../../api';
|
||||||
|
import { AdvancedMap } from '../../../../../core';
|
||||||
|
|
||||||
export class JukeboxSongDisksMessageParser implements IMessageParser
|
export class JukeboxSongDisksMessageParser implements IMessageParser
|
||||||
{
|
{
|
||||||
private _songDisks: Map<number, number> = new Map();
|
private _songDisks: IAdvancedMap<number, number> = new AdvancedMap();
|
||||||
private _maxLength: number;
|
private _maxLength: number;
|
||||||
|
|
||||||
flush(): boolean
|
flush(): boolean
|
||||||
{
|
{
|
||||||
this._songDisks.clear();
|
this._songDisks.reset();
|
||||||
this._maxLength = 0;
|
this._maxLength = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -19,13 +20,13 @@ export class JukeboxSongDisksMessageParser implements IMessageParser
|
|||||||
|
|
||||||
for(let i = 0; i < count; i++)
|
for(let i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
this._songDisks.set(wrapper.readInt(), wrapper.readInt());
|
this._songDisks.add(wrapper.readInt(), wrapper.readInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get songDisks(): Map<number, number>
|
public get songDisks(): IAdvancedMap<number, number>
|
||||||
{
|
{
|
||||||
return this._songDisks;
|
return this._songDisks;
|
||||||
}
|
}
|
||||||
|
@ -8,5 +8,6 @@ export * from './localization';
|
|||||||
export * from './Nitro';
|
export * from './Nitro';
|
||||||
export * from './room';
|
export * from './room';
|
||||||
export * from './session';
|
export * from './session';
|
||||||
|
export * from './sound';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
export * from './window';
|
export * from './window';
|
||||||
|
@ -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 { AdvancedMap, NitroManager } from '../../core';
|
||||||
import { NitroSettingsEvent, NitroSoundEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomEngineSamplePlaybackEvent } from '../../events';
|
import { NitroSettingsEvent, NitroSoundEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomEngineSamplePlaybackEvent } from '../../events';
|
||||||
import { Nitro } from '../Nitro';
|
import { Nitro } from '../Nitro';
|
||||||
import { MusicManager } from './music';
|
import { MusicController } from './music/MusicController';
|
||||||
|
|
||||||
export class SoundManager extends NitroManager implements ISoundManager
|
export class SoundManager extends NitroManager implements ISoundManager
|
||||||
{
|
{
|
||||||
@ -14,7 +14,7 @@ export class SoundManager extends NitroManager implements ISoundManager
|
|||||||
private _furniSamples: IAdvancedMap<number, HTMLAudioElement>;
|
private _furniSamples: IAdvancedMap<number, HTMLAudioElement>;
|
||||||
private _furnitureBeingPlayed: IAdvancedMap<number, number>;
|
private _furnitureBeingPlayed: IAdvancedMap<number, number>;
|
||||||
|
|
||||||
private _musicManager: MusicManager;
|
private _musicController: IMusicController;
|
||||||
|
|
||||||
constructor()
|
constructor()
|
||||||
{
|
{
|
||||||
@ -27,15 +27,14 @@ export class SoundManager extends NitroManager implements ISoundManager
|
|||||||
this._internalSamples = new AdvancedMap();
|
this._internalSamples = new AdvancedMap();
|
||||||
this._furniSamples = new AdvancedMap();
|
this._furniSamples = new AdvancedMap();
|
||||||
this._furnitureBeingPlayed = new AdvancedMap();
|
this._furnitureBeingPlayed = new AdvancedMap();
|
||||||
|
this._musicController = new MusicController();
|
||||||
this._musicManager = new MusicManager();
|
|
||||||
|
|
||||||
this.onEvent = this.onEvent.bind(this);
|
this.onEvent = this.onEvent.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onInit(): void
|
public onInit(): void
|
||||||
{
|
{
|
||||||
this._musicManager.init();
|
this._musicController.init();
|
||||||
|
|
||||||
Nitro.instance.roomEngine.events.addEventListener(RoomEngineSamplePlaybackEvent.PLAY_SAMPLE, this.onEvent);
|
Nitro.instance.roomEngine.events.addEventListener(RoomEngineSamplePlaybackEvent.PLAY_SAMPLE, this.onEvent);
|
||||||
Nitro.instance.roomEngine.events.addEventListener(RoomEngineObjectEvent.REMOVED, 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
|
public onDispose(): void
|
||||||
{
|
{
|
||||||
if(this._musicManager)
|
if(this._musicController)
|
||||||
{
|
{
|
||||||
this._musicManager.dispose();
|
this._musicController.dispose();
|
||||||
this._musicManager = null;
|
this._musicController = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Nitro.instance.roomEngine.events.removeEventListener(RoomEngineSamplePlaybackEvent.PLAY_SAMPLE, this.onEvent);
|
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 castedEvent = (event as NitroSettingsEvent);
|
||||||
|
|
||||||
const volumeFurniUpdated = castedEvent.volumeFurni !== this._volumeFurni;
|
const volumeFurniUpdated = castedEvent.volumeFurni !== this._volumeFurni;
|
||||||
|
const volumeTraxUpdated = castedEvent.volumeTrax !== this._volumeTrax;
|
||||||
|
|
||||||
this._volumeSystem = (castedEvent.volumeSystem / 100);
|
this._volumeSystem = (castedEvent.volumeSystem / 100);
|
||||||
this._volumeFurni = (castedEvent.volumeFurni / 100);
|
this._volumeFurni = (castedEvent.volumeFurni / 100);
|
||||||
this._volumeTrax = (castedEvent.volumeTrax / 100);
|
this._volumeTrax = (castedEvent.volumeTrax / 100);
|
||||||
|
|
||||||
if(volumeFurniUpdated) this.updateFurniSamplesVolume(this._volumeFurni);
|
if(volumeFurniUpdated) this.updateFurniSamplesVolume(this._volumeFurni);
|
||||||
|
|
||||||
|
if(volumeTraxUpdated) this._musicController?.updateVolume(this._volumeTrax);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case NitroSoundEvent.PLAY_SOUND: {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
54
src/nitro/sound/common/SongDataEntry.ts
Normal file
54
src/nitro/sound/common/SongDataEntry.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
import { GetTickerTime } from '../../../pixi-proxy';
|
|
||||||
|
|
||||||
export class SongStartRequestData
|
export class SongStartRequestData
|
||||||
{
|
{
|
||||||
@ -9,14 +8,14 @@ export class SongStartRequestData
|
|||||||
private _fadeInSeconds: number;
|
private _fadeInSeconds: number;
|
||||||
private _fadeOutSeconds: 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._songId = songId;
|
||||||
this._startPos = startPos;
|
this._startPos = startPos;
|
||||||
this._playLength = playLength;
|
this._playLength = playLength;
|
||||||
this._playRequestTime = playRequestTime;
|
|
||||||
this._fadeInSeconds = fadeInSeconds;
|
this._fadeInSeconds = fadeInSeconds;
|
||||||
this._fadeOutSeconds = fadeOutSeconds;
|
this._fadeOutSeconds = fadeOutSeconds;
|
||||||
|
this._playRequestTime = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get songId(): number
|
public get songId(): number
|
||||||
@ -28,7 +27,7 @@ export class SongStartRequestData
|
|||||||
{
|
{
|
||||||
if(this._startPos < 0) return 0;
|
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
|
public get playLength(): number
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<number, any>;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,2 @@
|
|||||||
|
export * from './SongDataEntry';
|
||||||
export * from './SongStartRequestData';
|
export * from './SongStartRequestData';
|
||||||
export * from './TraxChannel';
|
|
||||||
export * from './TraxChannelItem';
|
|
||||||
export * from './TraxData';
|
|
||||||
export * from './TraxSample';
|
|
||||||
export * from './TraxSequencer';
|
|
||||||
|
27
src/nitro/sound/events/NotifyPlayedSongEvent.ts
Normal file
27
src/nitro/sound/events/NotifyPlayedSongEvent.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
35
src/nitro/sound/events/NowPlayingEvent.ts
Normal file
35
src/nitro/sound/events/NowPlayingEvent.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
12
src/nitro/sound/events/PlayListStatusEvent.ts
Normal file
12
src/nitro/sound/events/PlayListStatusEvent.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
11
src/nitro/sound/events/SongDiskInventoryReceivedEvent.ts
Normal file
11
src/nitro/sound/events/SongDiskInventoryReceivedEvent.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
19
src/nitro/sound/events/SongInfoReceivedEvent.ts
Normal file
19
src/nitro/sound/events/SongInfoReceivedEvent.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -4,8 +4,16 @@ export class SoundManagerEvent extends NitroEvent
|
|||||||
{
|
{
|
||||||
public static TRAX_SONG_COMPLETE: string = 'SME_TRAX_SONG_COMPLETE';
|
public static TRAX_SONG_COMPLETE: string = 'SME_TRAX_SONG_COMPLETE';
|
||||||
|
|
||||||
constructor(type: string)
|
private _id: number;
|
||||||
|
|
||||||
|
constructor(type: string, id: number)
|
||||||
{
|
{
|
||||||
super(type);
|
super(type);
|
||||||
|
this._id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): number
|
||||||
|
{
|
||||||
|
return this._id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1,6 @@
|
|||||||
|
export * from './NotifyPlayedSongEvent';
|
||||||
|
export * from './NowPlayingEvent';
|
||||||
|
export * from './PlayListStatusEvent';
|
||||||
|
export * from './SongDiskInventoryReceivedEvent';
|
||||||
|
export * from './SongInfoReceivedEvent';
|
||||||
export * from './SoundManagerEvent';
|
export * from './SoundManagerEvent';
|
||||||
|
@ -2,3 +2,4 @@ export * from './common';
|
|||||||
export * from './events';
|
export * from './events';
|
||||||
export * from './music';
|
export * from './music';
|
||||||
export * from './SoundManager';
|
export * from './SoundManager';
|
||||||
|
export * from './trax';
|
||||||
|
198
src/nitro/sound/music/JukeboxPlaylistController.ts
Normal file
198
src/nitro/sound/music/JukeboxPlaylistController.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
599
src/nitro/sound/music/MusicController.ts
Normal file
599
src/nitro/sound/music/MusicController.ts
Normal file
@ -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<number, boolean>;
|
||||||
|
private _availableSongs: Map<number, SongDataEntry>;
|
||||||
|
private _songRequestsPerPriority: SongStartRequestData[];
|
||||||
|
private _songRequestCountsPerPriority:number[];
|
||||||
|
private _diskInventoryMissingData: number[];
|
||||||
|
private _songDiskInventory: IAdvancedMap<number, number>;
|
||||||
|
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<number, boolean>();
|
||||||
|
this._availableSongs = new Map<number, SongDataEntry>();
|
||||||
|
this._songDiskInventory = new AdvancedMap<number, number>();
|
||||||
|
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<string>('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<number, number>
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<number, boolean>;
|
|
||||||
private _availableSongs: Map<number, SongInfoEntry>;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
226
src/nitro/sound/music/MusicPlayer.ts
Normal file
226
src/nitro/sound/music/MusicPlayer.ts
Normal file
@ -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<number, Howl>;
|
||||||
|
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<number, Howl>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async play(song: string, startPos: number = 0, playLength: number = -1): Promise<void>
|
||||||
|
{
|
||||||
|
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<Howl>
|
||||||
|
{
|
||||||
|
let sample = this._cache.get(id);
|
||||||
|
|
||||||
|
if(!sample) sample = await this.loadSong(id);
|
||||||
|
|
||||||
|
return Promise.resolve(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async preload(): Promise<void>
|
||||||
|
{
|
||||||
|
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<void>
|
||||||
|
{
|
||||||
|
const traxData = new TraxData(song);
|
||||||
|
|
||||||
|
await Promise.all(traxData.getSampleIds().map(id => this.getSample(id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadSong(songId: number): Promise<Howl>
|
||||||
|
{
|
||||||
|
return new Promise <Howl>((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;
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
export * from './MusicManager';
|
export * from './JukeboxPlaylistController';
|
||||||
|
export * from './MusicController';
|
||||||
export * from './MusicPriorities';
|
export * from './MusicPriorities';
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { TraxChannelItem } from './TraxChannelItem';
|
import { TraxChannelItem } from './TraxChannelItem';
|
||||||
|
|
||||||
export class TraxChannel
|
export class TraxChannel
|
||||||
{
|
{
|
||||||
private _id: number;
|
private _id: number;
|
@ -1,5 +1,6 @@
|
|||||||
import { TraxChannel } from './TraxChannel';
|
import { TraxChannel } from './TraxChannel';
|
||||||
import { TraxChannelItem } from './TraxChannelItem';
|
import { TraxChannelItem } from './TraxChannelItem';
|
||||||
|
|
||||||
export class TraxData
|
export class TraxData
|
||||||
{
|
{
|
||||||
private _channels: TraxChannel[];
|
private _channels: TraxChannel[];
|
||||||
@ -8,6 +9,8 @@ export class TraxData
|
|||||||
constructor(data: string)
|
constructor(data: string)
|
||||||
{
|
{
|
||||||
this._channels = [];
|
this._channels = [];
|
||||||
|
this._metaData = new Map<string, string>();
|
||||||
|
|
||||||
let channelLines: string[] = [];
|
let channelLines: string[] = [];
|
||||||
|
|
||||||
const lines: string[] = data.split(':');
|
const lines: string[] = data.split(':');
|
||||||
@ -75,17 +78,17 @@ export class TraxData
|
|||||||
|
|
||||||
public get hasMetaData(): boolean
|
public get hasMetaData(): boolean
|
||||||
{
|
{
|
||||||
return this._metaData.get('meta') !== null;
|
return this._metaData.has('meta');
|
||||||
}
|
}
|
||||||
|
|
||||||
public get metaCutMode(): boolean
|
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;
|
if(!tempo) return null;
|
||||||
|
|
3
src/nitro/sound/trax/index.ts
Normal file
3
src/nitro/sound/trax/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './TraxChannel';
|
||||||
|
export * from './TraxChannelItem';
|
||||||
|
export * from './TraxData';
|
10
yarn.lock
10
yarn.lock
@ -229,6 +229,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/earcut/-/earcut-2.1.1.tgz#573a0af609f17005c751f6f4ffec49cfe358ea51"
|
resolved "https://registry.yarnpkg.com/@types/earcut/-/earcut-2.1.1.tgz#573a0af609f17005c751f6f4ffec49cfe358ea51"
|
||||||
integrity sha512-w8oigUCDjElRHRRrMvn/spybSMyX8MTkKA5Dv+tS1IE/TgmNZPqUYtvYBXGY8cieSE66gm+szeK+bnbxC2xHTQ==
|
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":
|
"@types/json-schema@^7.0.9":
|
||||||
version "7.0.9"
|
version "7.0.9"
|
||||||
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz"
|
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"
|
resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
|
||||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
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:
|
ignore@^5.2.0:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz"
|
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user