jam-cloud/web/app/assets/javascripts/react-components/stores/MixerStore.js.coffee

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.");
}
});
}`
}
)