jam-cloud/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee

462 lines
16 KiB
CoffeeScript

context = window
ChannelGroupIds = context.JK.ChannelGroupIds
MIX_MODES = context.JK.MIX_MODES;
@MixerHelper = class MixerHelper
constructor: (@session, @masterMixers, @personalMixers, @mixMode) ->
@mixersByResourceId = {}
@mixersByTrackId = {}
@allMixers = {}
@currentMixerRangeMin = null
@currentMixerRangeMax = null
@mediaTrackGroups = [ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup,
ChannelGroupIds.MetronomeGroup]
@muteBothMasterAndPersonalGroups = [ChannelGroupIds.AudioInputMusicGroup, ChannelGroupIds.MediaTrackGroup,
ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup]
@organize()
updateMixers: (type, text, @masterMixers, @personalMixers) ->
@organize()
if @session.inSession() && text == 'RebuildAudioIoControl'
SessionActions.myTracksChanged.trigger()
organize: () ->
for masterMixer, i in @masterMixers
@allMixers['M' + masterMixer.id] = masterMixer; # populate allMixers by mixer.id
# populate mixer pair
mixerPair = {}
@mixersByResourceId[masterMixer.rid] = mixerPair
@mixersByTrackId[masterMixer.id] = mixerPair
mixerPair.master = masterMixer;
for personalMixer, i in @personalMixers
@allMixers['P' + personalMixer.id] = personalMixer
# populate other side of mixer pair
mixerPair = @mixersByResourceId[personalMixer.rid]
unless mixerPair
if personalMixer.group_id != ChannelGroupIds.MonitorGroup
logger.warn("there is no master version of ", personalMixer)
mixerPair = {}
@mixersByResourceId[personalMixer.rid] = mixerPair
@mixersByTrackId[personalMixer.id] = mixerPair;
mixerPair.personal = personalMixer;
@groupTypes()
groupTypes: () ->
localMediaMixers = @mixersForGroupIds(@mediaTrackGroups, MIX_MODES.MASTER)
peerLocalMediaMixers = @mixersForGroupId(ChannelGroupIds.PeerMediaTrackGroup, MIX_MODES.MASTER)
logger.debug("localMediaMixers", localMediaMixers)
#logger.debug("peerLocalMediaMixers", peerLocalMediaMixers)
# get the server data regarding various media tracks
recordedBackingTracks = @session.recordedBackingTracks()
backingTracks = @session.backingTracks()
recordedJamTracks = @session.recordedJamTracks()
jamTracks = @session.jamTracks()
###
with mixer info, we use these to decide what kind of tracks are open in the backend
each mixer has a media_type field, which describes the type of media track it is.
* JamTrack
* BackingTrack
* RecordingTrack
* MetronomeTrack
* "" - adhoc track (not supported visually)
it is supposed to be the case that there are only one type of track open at a time, however, that's a business policy/logic
constraint; and may be buggy. **So, we should render whatever we have, so that it's obvious what's really going on.**
so, let's group up all mixers by type, and then ask them to be rendered
###
recordingTrackMixers = []
backingTrackMixers = []
jamTrackMixers = []
metronomeTrackMixers = []
adhocTrackMixers = []
groupByType = (mixers, isLocalMixer) ->
for mixer in mixers
mediaType = mixer.media_type
groupId = mixer.group_id
if mediaType == 'MetronomeTrack' || groupId == ChannelGroupIds.MetronomeGroup
# Metronomes come across with a blank media type, so check group_id:
metronomeTrackMixers.push(mixer)
else if mediaType == null || mediaType == "" || mediaType == 'RecordingTrack'
# additional check; if we can match an id in backing tracks or recorded backing track,
# we need to remove it from the recorded track set, but move it to the backing track set
isJamTrack = false;
if jamTracks
# check if the ID matches that of an open jam track
for jamTrack in jamTracks
if mixer.id == jamTrack.id
isJamTrack = true;
break
if !isJamTrack && recordedJamTracks
# then check if the ID matches that of a open, recorded jam track
for recordedJamTrack in recordedJamTracks
if mixer.id == recordedJamTrack.id
isJamTrack = true
break
if isJamTrack
jamTrackMixers.push(mixer)
else
isBackingTrack = false
if recordedBackingTracks
for recordedBackingTrack in recordedBackingTracks
if mixer.id == 'L' + recordedBackingTrack.client_track_id
isBackingTrack = true
break
if backingTracks
for backingTrack in backingTracks
if mixer.id == 'L' + backingTrack.client_track_id
isBackingTrack = true
break
if isBackingTrack
backingTrackMixers.push(mixer)
else
# couldn't resolve this as a JamTrack or Backing track, must be a normal recorded file
recordingTrackMixers.push(mixer)
else if mediaType == 'PeerMediaTrack' || mediaType == 'BackingTrack'
backingTrackMixers.push(mixer)
else if mediaType == 'JamTrack'
jamTrackMixers.push(mixer);
else if mediaType == null || mediaType == "" || mediaType == 'RecordingTrack'
# mediaType == null is for backwards compat with older clients. Can be removed soon
recordingTrackMixers.push(mixer)
else
logger.warn("Unknown track type: " + mediaType)
adhocTrackMixers.push(mixer)
groupByType(localMediaMixers, true);
groupByType(peerLocalMediaMixers, false);
###
if recordingTrackMixers.length > 0
renderRecordingTracks(recordingTrackMixers)
if backingTrackMixers.length > 0
renderBackingTracks(backingTrackMixers)
if jamTrackMixers.length > 0
renderJamTracks(jamTrackMixers);
if metronomeTrackMixers.length > 0 && @session.jamTracks() == null && @session.recordedJamTracks() == null
renderMetronomeTracks(metronomeTrackMixers);
checkMetronomeTransition();
###
if adhocTrackMixers.length > 0
logger.warn("some tracks are open that we don't know how to show")
mixersForGroupIds: (groupIds, mixMode) ->
foundMixers = []
mixers = if mixMode == MIX_MODES.MASTER then @masterMixers else @personalMixers;
for mixer in mixers
groupIdLen = groupIds.length
for i in groupIdLen
if mixer.group_id == groupIds[i]
foundMixers.push(mixer)
foundMixers
mixersForGroupId: (groupId, mixMode) ->
foundMixers = [];
mixers = if mixMode == MIX_MODES.MASTER then @masterMixers else @personalMixers;
for mixer in mixers
if mixer.group_id == groupId
foundMixers.push(mixer)
foundMixers
getMixer: (mixerId, mode) ->
mode = @mixMode unless mode?
@allMixers[(if mode then 'M' else 'P') + mixerId]
getMixerByTrackId: (trackId, mode) ->
mixerPair = @mixersByTrackId[trackId]
return null unless mixerPair
if mode == undefined
return mixerPair
else
if mode == MIX_MODES.MASTER
return mixerPair.master
else
return mixerPair.personal
groupedMixersForClientId: (clientId, groupIds, usedMixers, mixMode) ->
foundMixers = {};
mixers = if mixMode == MIX_MODES.MASTER then @masterMixers else @personalMixers;
for mixer in mixers
if mixer.client_id == clientId
for groupId in groupIds
if mixer.group_id == groupId
if (mixer.groupId != ChannelGroupIds.UserMusicInputGroup) && !(mixer.id in usedMixers)
mixers = foundMixers[mixer.group_id]
if !mixers
mixers = []
foundMixers[mixer.group_id] = mixers
mixers.push(mixer)
foundMixers
findMixerForTrack: (client_id, track, myTrack) ->
mixer = null # what is the best mixer for this track/client ID?
oppositeMixer = null # what is the corresponding mixer in the opposite mode?
vuMixer = null
muteMixer = null
if myTrack
# when it's your track, look it up by the backend resource ID
mixer = @getMixerByTrackId(track.client_track_id, @mixMode)
vuMixer = mixer
muteMixer = mixer
# sanity checks
if mixer && mixer.group_id != ChannelGroupIds.AudioInputMusicGroup
logger.error("found local mixer that was not of groupID: AudioInputMusicGroup", mixer)
if mixer
# find the matching AudioInputMusicGroup for the opposite mode
oppositeMixer = @getMixerByTrackId(track.client_track_id, !@mixMode)
if @mixMode == MIX_MODES.PERSONAL
muteMixer = oppositeMixer; # make the master mixer the mute mixer
# sanity checks
if !oppositeMixer
logger.error("unable to find opposite mixer for local mixer", mixer)
else if oppositeMixer.group_id != ChannelGroupIds.AudioInputMusicGroup
logger.error("found local mixer in opposite mode that was not of groupID: AudioInputMusicGroup", mixer, oppositeMixer)
else
logger.debug("local track is not present: ", track, mixer)
else
switch @mixMode
when MIX_MODES.MASTER
# when it's a remote track and in master mode, we should find the PeerAudioInputMusicGroup
mixer = @getMixerByTrackId(track.client_track_id, MIX_MODES.MASTER)
# sanity check
if mixer && mixer.group_id != ChannelGroupIds.PeerAudioInputMusicGroup
logger.error("found remote mixer that was not of groupID: PeerAudioInputMusicGroup", mixer)
vuMixer = mixer
muteMixer = mixer
if mixer
# we should be able to find a UserMusicInputGroup for this clientId in personal mode
oppositeMixers = @groupedMixersForClientId(client_id, [ ChannelGroupIds.UserMusicInputGroup], {}, MIX_MODES.PERSONAL)
if oppositeMixers[ChannelGroupIds.UserMusicInputGroup]
oppositeMixer = oppositeMixers[ChannelGroupIds.UserMusicInputGroup][0]
if !oppositeMixer
logger.error("unable to find UserMusicInputGroup corresponding to PeerAudioInputMusicGroup mixer", mixer )
when MIX_MODES.PERSONAL
mixers = @groupedMixersForClientId(client_id, [ ChannelGroupIds.UserMusicInputGroup], {}, MIX_MODES.PERSONAL)
if mixers[ChannelGroupIds.UserMusicInputGroup]
mixer = mixers[ChannelGroupIds.UserMusicInputGroup][0]
vuMixer = mixer
muteMixer = mixer
if mixer
# now grab the PeerAudioInputMusicGroup in master mode to satisfy the 'opposite' mixer
oppositeMixer = @getMixerByTrackId(track.client_track_id, MIX_MODES.MASTER)
if !oppositeMixer
logger.debug("unable to find a PeerAudioInputMusicGroup master mixer matching a UserMusicInput", track.client_track_id, @mixersByTrackId)
else if oppositeMixer.group_id != ChannelGroupIds.PeerAudioInputMusicGroup
logger.error("found remote mixer that was not of groupID: PeerAudioInputMusicGroup", mixer)
vuMixer = oppositeMixer; # for personal mode, use the PeerAudioInputMusicGroup's VUs
{
mixer: mixer,
oppositeMixer: oppositeMixer,
vuMixer: vuMixer,
muteMixer: muteMixer
}
mute: (mixerId, mode, muting) ->
mode = @mixMode unless mode?
@fillTrackVolumeObject(mixerId, mode)
context.trackVolumeObject.mute = muting
context.jamClient.SessionSetControlState(mixerId, mode)
# keep state of mixer in sync with backend
mixer = @getMixer(mixerId, mode)
mixer.mute = muting
faderChanged: (data, mixerIds, groupId) ->
# media tracks are the only controls that sometimes set two mixers right now
hasMasterAndPersonalControls = mixerIds.length == 2
for mixerId, i in mixerIds
broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
mode = undefined
if hasMasterAndPersonalControls
mode = if i == 0 then MIX_MODES.MASTER else MIX_MODES.PERSONAL
mixer = @fillTrackVolumeObject(mixerId, mode, broadcast)
@setMixerVolume(mixer, data.percentage)
# keep state of mixer in sync with backend
mixer = @getMixer(mixer.id, mixer.mode)
mixer.volume_left = context.trackVolumeObject.volL
if groupId == ChannelGroupIds.UserMusicInputGroup
# there may be other mixers with this same ID in the case of a Peer Music Stream, so update them as well
context.JK.FaderHelpers.setFaderValue(mixerId, data.percentage)
initGain: (mixer) ->
gainPercent = context.JK.FaderHelpers.convertAudioTaperToPercent(mixer.volume_left)
context.JK.FaderHelpers.setFaderValue(mixer.id, gainPercent)
context.JK.FaderHelpers.showFader(mixer.id)
panChanged: (data, mixerIds, groupId) ->
# media tracks are the only controls that sometimes set two mixers right now
for mixerId, i in mixerIds
broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
mode = undefined
mixer = @fillTrackVolumeObject(mixerId, mode, broadcast)
@setMixerPan(mixer, data.percentage)
# keep state of mixer in sync with backend
mixer = @getMixer(mixer.id, mixer.mode)
mixer.pan = context.trackVolumeObject.pan
initPan: (mixer) ->
panPercent= context.JK.PanHelpers.convertPanToPercent(mixer.pan)
context.JK.FaderHelpers.setFaderValue(mixer.id, panPercent, mixer.pan)
context.JK.FaderHelpers.showFader(mixer.id)
setMixerPan: (mixer, panPercent) ->
context.trackVolumeObject.pan = context.JK.PanHelpers.convertPercentToPan(panPercent);
context.jamClient.SessionSetControlState(mixer.id, mixer.mode);
setMixerVolume: (mixer, volumePercent) ->
###
// The context.trackVolumeObject has been filled with the mixer values
// that go with mixerId, and the range of that mixer
// has been set in currentMixerRangeMin-Max.
// All that needs doing is to translate the incoming percent
// into the real value ont the sliders range. Set Left/Right
// volumes on trackVolumeObject, and call SetControlState to stick.
###
context.trackVolumeObject.volL = context.JK.FaderHelpers.convertPercentToAudioTaper(volumePercent);
context.trackVolumeObject.volR = context.JK.FaderHelpers.convertPercentToAudioTaper(volumePercent);
context.jamClient.SessionSetControlState(mixer.id, mixer.mode);
percentFromMixerValue: (min, max, value) ->
try
range = Math.abs(max - min)
magnitude = value - min
percent = Math.round(100*(magnitude/range))
percent
catch err
0
percentToMixerValue:(min, max, percent) ->
range = Math.abs(max - min);
multiplier = percent/100; # Change 85 into 0.85
value = min + (multiplier * range);
# Protect against percents < 0 and > 100
if value < min
value = min;
if value > max
value = max;
return value;
fillTrackVolumeObject: (mixerId, mode, broadcast) ->
_broadcast = true
if broadcast?
_broadcast = broadcast
mixer = @getMixer(mixerId, mode)
context.trackVolumeObject.clientID = mixer.client_id
context.trackVolumeObject.broadcast = _broadcast
context.trackVolumeObject.master = mixer.master
context.trackVolumeObject.monitor = mixer.monitor
context.trackVolumeObject.mute = mixer.mute
context.trackVolumeObject.name = mixer.name
context.trackVolumeObject.record = mixer.record
context.trackVolumeObject.volL = mixer.volume_left
context.trackVolumeObject.pan = mixer.pan
# today we treat all tracks as mono, but this is required to make a stereo track happy
# context.trackVolumeObject.volR = mixer.volume_right;
context.trackVolumeObject.volR = mixer.volume_left;
context.trackVolumeObject.loop = mixer.loop;
# trackVolumeObject doesn't have a place for range min/max
@currentMixerRangeMin = mixer.range_low;
@currentMixerRangeMax = mixer.range_high;
mixer
updateVU: (mixerId, value, isClipping) ->
selector = null
pureMixerId = mixerId.replace("_vul", "")
pureMixerId = pureMixerId.replace("_vur", "")
mixer = @getMixer(pureMixerId, @mixMode)
unless mixer
# try again, in the opposite mode (awful that this is necessary)
mixer = @getMixer(pureMixerId, !@mixMode)
if mixer
if mixer.stereo # // stereo track
context.JK.VuHelpers.updateVU2('vul', mixer, value)
else
if mixerId.substr(-4) == "_vul"
# Do the left
context.JK.VuHelpers.updateVU2('vul', mixer, value)
# Do the right
context.JK.VuHelpers.updateVU2('vur', mixer, value)