487 lines
19 KiB
CoffeeScript
487 lines
19 KiB
CoffeeScript
context = window
|
|
logger = context.JK.logger
|
|
SessionActions = context.SessionActions
|
|
JamTrackActions = context.JamTrackActions
|
|
MIX_MODES = context.JK.MIX_MODES
|
|
rest = context.JK.Rest()
|
|
|
|
@MixerStore = Reflux.createStore(
|
|
{
|
|
METRO_SOUND_LOOKUP: {
|
|
0 : "BuiltIn",
|
|
1 : "SineWave",
|
|
2 : "Beep",
|
|
3 : "Click",
|
|
4 : "Kick",
|
|
5 : "Snare",
|
|
6 : "MetroFile"
|
|
}
|
|
|
|
metro: {tempo: 120, cricket: false, sound: "Beep" }
|
|
noAudioUsers : {}
|
|
checkingMissingPeers : {}
|
|
missingMixerPeers : {}
|
|
recheckTimeout : null
|
|
clientsWithAudioOverride : {}
|
|
vuMeterUpdatePrefMap: {}
|
|
|
|
init: ->
|
|
# Register with the app store to get @app
|
|
this.listenTo(context.AppStore, this.onAppInit);
|
|
this.listenTo(context.SessionStore, this.onSessionChange)
|
|
this.listenTo(context.MixerActions.mute, this.onMute)
|
|
this.listenTo(context.MixerActions.faderChanged, this.onFaderChanged)
|
|
this.listenTo(context.MixerActions.initGain, this.onInitGain)
|
|
this.listenTo(context.MixerActions.initPan, this.onInitPan)
|
|
this.listenTo(context.MixerActions.panChanged, this.onPanChanged)
|
|
this.listenTo(context.MixerActions.mixersChanged, this.onMixersChanged)
|
|
this.listenTo(context.MixerActions.syncTracks, this.onSyncTracks)
|
|
this.listenTo(context.MixerActions.mixerModeChanged, this.onMixerModeChanged)
|
|
this.listenTo(context.MixerActions.loopChanged, this.onLoopChanged)
|
|
this.listenTo(context.MixerActions.openMetronome, this.onOpenMetronome)
|
|
this.listenTo(context.MixerActions.metronomeChanged, this.onMetronomeChanged)
|
|
this.listenTo(context.MixerActions.deadUserRemove, this.onDeadUserRemove)
|
|
this.listenTo(context.MixerActions.missingPeerMixer, this.onMissingPeerMixer)
|
|
this.listenTo(context.MixerActions.clientsWithAudio, this.onClientsWithAudio)
|
|
this.listenTo(context.MixerActions.closeMedia, this.onCloseMedia)
|
|
|
|
context.JK.HandleVolumeChangeCallback2 = @handleVolumeChangeCallback
|
|
context.JK.HandleMetronomeCallback2 = @handleMetronomeCallback
|
|
context.JK.HandleBridgeCallback2 = @handleBridgeCallback
|
|
context.JK.HandleBackingTrackSelectedCallback2 = @handleBackingTrackSelectedCallback
|
|
|
|
#setInterval(@dumpVUStats, 5000)
|
|
|
|
dumpVUStats: () ->
|
|
@mixers.dumpVUStats() if @mixers?
|
|
|
|
issueChange: () ->
|
|
@trigger({session: @session, mixers: @mixers})
|
|
|
|
handleVolumeChangeCallback: (mixerId, isLeft, value, isMuted) ->
|
|
# TODO
|
|
# Visually update mixer
|
|
# There is no need to actually set the back-end mixer value as the
|
|
# back-end will already have updated the audio mixer directly prior to sending
|
|
# me this event. I simply need to visually show the new fader position.
|
|
# TODO: Use mixer's range
|
|
#faderValue = percentFromMixerValue(-80, 20, value);
|
|
#context.JK.FaderHelpers.setFaderValue(mixerId, faderValue);
|
|
#var $muteControl = $('[control="mute"][mixer-id="' + mixerId + '"]');
|
|
#_toggleVisualMuteControl($muteControl, isMuted);
|
|
logger.debug("volume change")
|
|
|
|
|
|
handleMetronomeCallback: (args) ->
|
|
logger.debug("MetronomeCallback: ", args)
|
|
@metro.tempo = args.bpm
|
|
@metro.cricket = args.cricket;
|
|
@metro.sound = @METRO_SOUND_LOOKUP[args.sound];
|
|
|
|
# This isn't actually there, so we rely on the metroSound as set from select on form:
|
|
# metroSound = args.sound
|
|
SessionActions.syncWithServer()
|
|
|
|
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
|
|
console.log('MetronomeCallback#mixers', @mixers)
|
|
@issueChange()
|
|
|
|
handleBridgeCallback: (vuData) ->
|
|
eventName = null
|
|
mixerId = null
|
|
value = null
|
|
vuInfo = null
|
|
|
|
vuMeterUpdateRate = localStorage.getItem('vuMeterUpdateRate') || 'fast'
|
|
@vuMeterUpdatePrefMap['count'] = (@vuMeterUpdatePrefMap['count'] || 0) + 1
|
|
|
|
if vuMeterUpdateRate == 'medium' && @vuMeterUpdatePrefMap['count'] % 3 != 0
|
|
# skip this update
|
|
return
|
|
if vuMeterUpdateRate == 'slow' && @vuMeterUpdatePrefMap['count'] % 9 != 0
|
|
# skip this update
|
|
return
|
|
#console.log('vuMeterUpdateRate', vuMeterUpdateRate, @vuMeterUpdatePrefMap['count'])
|
|
@vuMeterUpdatePrefMap['count'] = 0
|
|
|
|
for vuInfo in vuData
|
|
eventName = vuInfo[0];
|
|
vuVal = 0.0;
|
|
if eventName == "vu"
|
|
mixerId = vuInfo[1];
|
|
mode = vuInfo[2];
|
|
leftValue = vuInfo[3];
|
|
leftClipping = vuInfo[4];
|
|
rightValue = vuInfo[5];
|
|
rightClipping = vuInfo[6];
|
|
# TODO - no guarantee range will be -80 to 20. Get from the
|
|
# GetControlState for this mixer which returns min/max
|
|
# value is a DB value from -80 to 20. Convert to float from 0.0-1.0
|
|
#console.log('handleBridgeCallback@mixers',@mixers)
|
|
|
|
if @mixers?
|
|
@mixers.updateVU(mixerId, mode, (leftValue + 80) / 80, leftClipping, (rightValue + 80) / 80, rightClipping)
|
|
#@mixers.updateVU(mixerId + "_vur", (rightValue + 80) / 80, rightClipping)
|
|
|
|
|
|
handleBackingTrackSelectedCallback: () ->
|
|
logger.debug("backing track selected")
|
|
|
|
# onAppInit: (@app) ->
|
|
# @gearUtils = context.JK.GearUtilsInstance
|
|
# @sessionUtils = context.JK.SessionUtils
|
|
|
|
# context.jamClient.SetVURefreshRate(150)
|
|
# context.jamClient.RegisterVolChangeCallBack("JK.HandleVolumeChangeCallback2")
|
|
# context.jamClient.setMetronomeOpenCallback("JK.HandleMetronomeCallback2")
|
|
|
|
onAppInit: `async function(app) {
|
|
this.app = app;
|
|
this.gearUtils = context.JK.GearUtilsInstance;
|
|
this.sessionUtils = context.JK.SessionUtils;
|
|
|
|
await context.jamClient.SetVURefreshRate(150);
|
|
await context.jamClient.RegisterVolChangeCallBack("JK.HandleVolumeChangeCallback2");
|
|
await context.jamClient.setMetronomeOpenCallback("JK.HandleMetronomeCallback2");
|
|
}`
|
|
|
|
|
|
sessionEnded: () ->
|
|
@checkingMissingPeers = {}
|
|
@noAudioUsers = {}
|
|
@missingMixerPeers = {}
|
|
clearTimeout(@recheckTimeout) if @recheckTimeout?
|
|
|
|
# onSessionChange: (session) ->
|
|
|
|
# @sessionEnded() unless session.inSession()
|
|
|
|
# @session = session
|
|
|
|
# @masterMixers = context.jamClient.SessionGetAllControlState(true);
|
|
# @personalMixers = context.jamClient.SessionGetAllControlState(false);
|
|
|
|
# @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
|
|
|
|
# @issueChange()
|
|
|
|
onSessionChange: `async function(session) {
|
|
if(!session){ return }
|
|
if (!session.inSession()) { this.sessionEnded(); }
|
|
|
|
this.session = session;
|
|
|
|
this.masterMixers = await context.jamClient.SessionGetAllControlState(true);
|
|
this.personalMixers = await context.jamClient.SessionGetAllControlState(false);
|
|
|
|
this.mixers = new context.MixerHelper(this.session, this.masterMixers, this.personalMixers, this.metro, this.noAudioUsers, this.clientsWithAudioOverride, (this.mixers != null ? this.mixers.mixMode : undefined) || MIX_MODES.PERSONAL);
|
|
return this.issueChange();
|
|
}`
|
|
|
|
# onMute: (mixers, muting) ->
|
|
|
|
# mixers = [mixers] unless $.isArray(mixers)
|
|
|
|
# for mixer in mixers
|
|
# @mixers.mute(mixer.id, mixer.mode, muting);
|
|
|
|
# # simulate a state change to cause a UI redraw
|
|
# @issueChange()
|
|
|
|
onMute: `async function(mixers, muting) {
|
|
|
|
if (!$.isArray(mixers)) { mixers = [mixers]; }
|
|
|
|
for (let mixer of Array.from(mixers)) {
|
|
await this.mixers.mute(mixer.id, mixer.mode, muting);
|
|
}
|
|
|
|
// simulate a state change to cause a UI redraw
|
|
this.issueChange();
|
|
}`
|
|
|
|
# onFaderChanged: (data, mixers, gainType, controlGroup) ->
|
|
|
|
# @mixers.faderChanged(data, mixers, gainType, controlGroup)
|
|
|
|
# @issueChange()
|
|
|
|
onFaderChanged: `async function(data, mixers, gainType, controlGroup) {
|
|
await this.mixers.faderChanged(data, mixers, gainType, controlGroup);
|
|
this.issueChange();
|
|
}`
|
|
|
|
onPanChanged: (data, mixers, groupId) ->
|
|
@mixers.panChanged(data, mixers, groupId)
|
|
|
|
@issueChange()
|
|
|
|
# onLoopChanged: (mixer, shouldLoop) ->
|
|
# @mixers.loopChanged(mixer, shouldLoop)
|
|
|
|
onLoopChanged: `async function (mixer, shouldLoop) {
|
|
await this.mixers.loopChanged(mixer, shouldLoop);
|
|
}`
|
|
|
|
# onOpenMetronome: () ->
|
|
# context.jamClient.SessionStopPlay()
|
|
# context.jamClient.SessionOpenMetronome(@mixers.metro.tempo, @mixers.metro.sound, 1, 0)
|
|
|
|
onOpenMetronome: `async function() {
|
|
await context.jamClient.SessionStopPlay();
|
|
await context.jamClient.SessionOpenMetronome(this.mixers.metro.tempo, this.mixers.metro.sound, 1, 0);
|
|
}`
|
|
|
|
# onMetronomeChanged: (tempo, sound) ->
|
|
# logger.debug("onMetronomeChanged", tempo, sound)
|
|
|
|
# @metro.tempo = tempo
|
|
# @metro.sound = sound
|
|
# context.jamClient.SessionSetMetronome(@metro.tempo, @metro.sound, 1, 0);
|
|
|
|
# @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
|
|
# @issueChange()
|
|
|
|
onMetronomeChanged: `async function(tempo, sound) {
|
|
logger.debug("onMetronomeChanged", tempo, sound);
|
|
|
|
this.metro.tempo = tempo;
|
|
this.metro.sound = sound;
|
|
await context.jamClient.SessionSetMetronome(this.metro.tempo, this.metro.sound, 1, 0);
|
|
|
|
this.mixers = new context.MixerHelper(this.session, this.masterMixers, this.personalMixers, this.metro, this.noAudioUsers, this.clientsWithAudioOverride, (this.mixers != null ? this.mixers.mixMode : undefined) || MIX_MODES.PERSONAL);
|
|
return this.issueChange();
|
|
}`
|
|
|
|
# codeInitiated means the user did not initiate this
|
|
onCloseMedia: (codeInitiated) ->
|
|
logger.debug("MixerStore: onCloseMedia", codeInitiated)
|
|
if @session.recordedTracks()
|
|
@closeRecording(codeInitiated)
|
|
else if @session.jamTracks() || @session.downloadingJamTrack
|
|
@closeJamTrack(codeInitiated)
|
|
else if @session.backingTrack() && @session.backingTrack().path
|
|
@closeBackingTrack(codeInitiated)
|
|
else if @session.isMetronomeOpen()
|
|
@closeMetronomeTrack(codeInitiated)
|
|
else
|
|
logger.error("don't know how to close open media", @session) unless codeInitiated
|
|
|
|
closeRecording: (codeInitiated) ->
|
|
logger.debug("closing recording");
|
|
|
|
if !@mixers.mediaSummary.isOpener
|
|
logger.debug("not recordingas not opener")
|
|
return
|
|
|
|
SessionActions.closeMedia(codeInitiated)
|
|
|
|
closeMetronomeTrack:(codeInitiated) ->
|
|
logger.debug("closing metronome")
|
|
|
|
SessionActions.closeMedia(codeInitiated)
|
|
|
|
closeBackingTrack: (codeInitiated) ->
|
|
logger.debug("closing backing track");
|
|
|
|
if !@mixers.mediaSummary.isOpener
|
|
logger.debug("not closing backing as not opener")
|
|
return
|
|
|
|
SessionActions.closeMedia(codeInitiated)
|
|
|
|
closeJamTrack: (codeInitiated) ->
|
|
logger.debug("closing jam track");
|
|
|
|
if !@mixers.mediaSummary.isOpener
|
|
logger.debug("not closing jamtrack as not opener")
|
|
return
|
|
|
|
SessionActions.closeMedia(codeInitiated)
|
|
|
|
|
|
onDeadUserRemove: (clientId) ->
|
|
return unless @session.inSession()
|
|
|
|
participant = @session.participantsEverSeen[clientId];
|
|
|
|
if participant?
|
|
logger.debug("todo :notify dead user")
|
|
# XXX TODO trigger some notification store
|
|
|
|
#app.notify({
|
|
# "title": ALERT_TYPES[type].title,
|
|
# "text": participant.user.name + " is no longer sending audio.",
|
|
# "icon_url": context.JK.resolveAvatarUrl(participant.user.photo_url)
|
|
#});
|
|
|
|
@noAudioUsers[clientId] = true
|
|
|
|
@issueChange()
|
|
|
|
onClientsWithAudio: (clients) ->
|
|
@clientsWithAudioOverride = clients
|
|
|
|
onMissingPeerMixer: (clientId) ->
|
|
|
|
missingPeerAttempts = @missingMixerPeers[clientId]
|
|
|
|
# check for 5 tries (10 seconds if setTimeout is 2000ms)
|
|
if !missingPeerAttempts? || missingPeerAttempts < 5
|
|
@checkingMissingPeers[clientId] = true
|
|
@recheckTimeout = setTimeout(@recheckForMixers, 2000) unless @recheckTimeout?
|
|
else
|
|
logger.debug("ignoring missing peer recheck. missingPeerAttempts: #{missingPeerAttempts}")
|
|
|
|
# recheckForMixers: () ->
|
|
# # increment how many times we've checked for this particular peer
|
|
# for clientId, meh of @checkingMissingPeers
|
|
# missingPeerAttempts = @missingMixerPeers[clientId]
|
|
# missingPeerAttempts = 0 unless missingPeerAttempts?
|
|
# missingPeerAttempts++
|
|
# @missingMixerPeers[clientId] = missingPeerAttempts
|
|
|
|
# # reset the peers we are looking for
|
|
# @checkingMissingPeers = {}
|
|
|
|
# @recheckTimeout = null
|
|
# @masterMixers = context.jamClient.SessionGetAllControlState(true);
|
|
# @personalMixers = context.jamClient.SessionGetAllControlState(false);
|
|
# logger.debug("MixerStore: recheckForMixers")
|
|
# @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
|
|
# @issueChange()
|
|
|
|
recheckForMixers: `async function() {
|
|
// increment how many times we have checked for this particular peer
|
|
for (let clientId in this.checkingMissingPeers) {
|
|
const meh = this.checkingMissingPeers[clientId];
|
|
let missingPeerAttempts = this.missingMixerPeers[clientId];
|
|
if (missingPeerAttempts == null) { missingPeerAttempts = 0; }
|
|
missingPeerAttempts++;
|
|
this.missingMixerPeers[clientId] = missingPeerAttempts;
|
|
}
|
|
|
|
// reset the peers we are looking for
|
|
this.checkingMissingPeers = {};
|
|
|
|
this.recheckTimeout = null;
|
|
this.masterMixers = await context.jamClient.SessionGetAllControlState(true);
|
|
this.personalMixers = await context.jamClient.SessionGetAllControlState(false);
|
|
logger.debug("MixerStore: recheckForMixers");
|
|
this.mixers = new context.MixerHelper(this.session, this.masterMixers, this.personalMixers, this.metro, this.noAudioUsers, this.clientsWithAudioOverride, (this.mixers != null ? this.mixers.mixMode : undefined) || MIX_MODES.PERSONAL);
|
|
return this.issueChange();
|
|
}`
|
|
|
|
onInitGain: (mixer) ->
|
|
@mixers.initGain(mixer)
|
|
|
|
onInitPan: (mixer) ->
|
|
@mixers.initPan(mixer)
|
|
|
|
# onMixersChanged: (type, text) ->
|
|
# @masterMixers = context.jamClient.SessionGetAllControlState(true);
|
|
# @personalMixers = context.jamClient.SessionGetAllControlState(false);
|
|
|
|
# logger.debug("MixerStore: onMixersChanged")
|
|
|
|
# @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
|
|
|
|
# SessionActions.mixersChanged.trigger(type, text, @mixers.getTrackInfo())
|
|
|
|
# @issueChange()
|
|
|
|
onMixersChanged: `async function(type, text) {
|
|
this.masterMixers = await context.jamClient.SessionGetAllControlState(true);
|
|
this.personalMixers = await context.jamClient.SessionGetAllControlState(false);
|
|
|
|
logger.debug("MixerStore: onMixersChanged");
|
|
|
|
this.mixers = new context.MixerHelper(this.session, this.masterMixers, this.personalMixers, this.metro, this.noAudioUsers, this.clientsWithAudioOverride, (this.mixers != null ? this.mixers.mixMode : undefined) || MIX_MODES.PERSONAL);
|
|
|
|
SessionActions.mixersChanged.trigger(type, text, await this.mixers.getTrackInfo());
|
|
|
|
return this.issueChange();
|
|
}`
|
|
|
|
onMixerModeChanged: (mode) ->
|
|
if mode == MIX_MODES.MASTER
|
|
@app.layout.showDialog('session-master-mix-dialog') unless @app.layout.isDialogShowing('session-master-mix-dialog')
|
|
else
|
|
@app.layout.closeDialog('session-master-mix-dialog') if @app.layout.isDialogShowing('session-master-mix-dialog')
|
|
|
|
# onSyncTracks: () ->
|
|
# logger.debug("MixerStore: onSyncTracks")
|
|
# unless @session.inSession()
|
|
# logger.debug("dropping sync tracks because no longer in session")
|
|
# return
|
|
|
|
# allTracks = @mixers.getTrackInfo()
|
|
# console.log('allTracks', allTracks)
|
|
# inputTracks = allTracks.userTracks;
|
|
# backingTracks = allTracks.backingTracks;
|
|
# metronomeTracks = allTracks.metronomeTracks;
|
|
|
|
# # create a trackSync request based on backend data
|
|
# syncTrackRequest = {}
|
|
# syncTrackRequest.client_id = @app.clientId
|
|
# syncTrackRequest.tracks = inputTracks
|
|
# syncTrackRequest.backing_tracks = backingTracks
|
|
# syncTrackRequest.metronome_open = metronomeTracks.length > 0
|
|
# syncTrackRequest.id = @session.id()
|
|
|
|
# rest.putTrackSyncChange(syncTrackRequest)
|
|
# .fail((jqXHR)=>
|
|
# if jqXHR.status != 404
|
|
# @app.notify({
|
|
# "title": "Can't Sync Local Tracks",
|
|
# "text": "The client is unable to sync local track information with the server. You should rejoin the session to ensure a good experience.",
|
|
# "icon_url": "/assets/content/icon_alert_big.png"
|
|
# })
|
|
|
|
# else
|
|
# logger.debug("Unable to sync local tracks because session is gone.")
|
|
# )
|
|
|
|
|
|
onSyncTracks: `async function() {
|
|
logger.debug("MixerStore: onSyncTracks");
|
|
if (!this.session.inSession()) {
|
|
logger.debug("dropping sync tracks because no longer in session");
|
|
return;
|
|
}
|
|
|
|
const allTracks = await this.mixers.getTrackInfo();
|
|
console.log('allTracks', allTracks);
|
|
const inputTracks = allTracks.userTracks;
|
|
const {
|
|
backingTracks
|
|
} = allTracks;
|
|
const {
|
|
metronomeTracks
|
|
} = allTracks;
|
|
|
|
// create a trackSync request based on backend data
|
|
const syncTrackRequest = {};
|
|
syncTrackRequest.client_id = this.app.clientId;
|
|
syncTrackRequest.tracks = inputTracks;
|
|
syncTrackRequest.backing_tracks = backingTracks;
|
|
syncTrackRequest.metronome_open = metronomeTracks.length > 0;
|
|
syncTrackRequest.id = this.session.id();
|
|
|
|
return rest.putTrackSyncChange(syncTrackRequest)
|
|
.fail(jqXHR=> {
|
|
if (jqXHR.status !== 404) {
|
|
return this.app.notify({
|
|
"title": "Can't Sync Local Tracks",
|
|
"text": "The client is unable to sync local track information with the server. You should rejoin the session to ensure a good experience.",
|
|
"icon_url": "/assets/content/icon_alert_big.png"
|
|
});
|
|
|
|
} else {
|
|
return logger.debug("Unable to sync local tracks because session is gone.");
|
|
}
|
|
});
|
|
}`
|
|
}
|
|
)
|