1069 lines
37 KiB
CoffeeScript
1069 lines
37 KiB
CoffeeScript
context = window
|
|
|
|
logger = context.JK.logger
|
|
ChannelGroupIds = context.JK.ChannelGroupIds
|
|
CategoryGroupIds = context.JK.CategoryGroupIds
|
|
MIX_MODES = context.JK.MIX_MODES;
|
|
|
|
|
|
@MixerHelper = class MixerHelper
|
|
|
|
constructor: (@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixMode) ->
|
|
@mixMode = MIX_MODES.PERSONAL # TODO - remove mixMode from MixerHelper? Or at least stop using it in most functions
|
|
@app = @session.app
|
|
@mixersByResourceId = {}
|
|
@mixersByTrackId = {}
|
|
@allMixers = {}
|
|
@currentMixerRangeMin = null
|
|
@currentMixerRangeMax = null
|
|
@mediaSummary = {}
|
|
@mediaTrackGroups = [ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup,
|
|
ChannelGroupIds.MetronomeGroup]
|
|
@muteBothMasterAndPersonalGroups = [ChannelGroupIds.AudioInputMusicGroup, ChannelGroupIds.MidiInputMusicGroup, ChannelGroupIds.MediaTrackGroup,
|
|
ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup]
|
|
@vuStats = {}
|
|
@shouldCollectVuStats = false
|
|
@simulatedMusicCategoryMixers = {}
|
|
@simulatedChatCategoryMixers = {}
|
|
@organize()
|
|
|
|
organize: () ->
|
|
for masterMixer 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 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()
|
|
@chatMixer = @resolveChatMixer()
|
|
|
|
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()
|
|
jamTrackMixdown = @session.jamTrackMixdown()
|
|
|
|
###
|
|
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 mixer.id == jamTrackMixdown.id
|
|
isJamTrack = true;
|
|
|
|
if !isJamTrack && 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();
|
|
###
|
|
|
|
@backingTracks = @resolveBackingTracks()
|
|
@jamTracks = @resolveJamTracks()
|
|
@recordedTracks = @resolveRecordedTracks()
|
|
@metronome = @resolveMetronome()
|
|
|
|
if @adhocTrackMixers.length > 0
|
|
logger.warn("some tracks are open that we don't know how to show")
|
|
|
|
@mediaSummary =
|
|
recordingOpen: @recordedTracks.length > 0
|
|
jamTrackOpen: @jamTracks.length > 0
|
|
backingTrackOpen: @backingTracks.length > 0
|
|
metronomeOpen: @session.isMetronomeOpen()
|
|
|
|
|
|
|
|
# figure out if any media is open
|
|
mediaOpenSummary = false
|
|
for mediaType, mediaOpen of @mediaSummary
|
|
mediaOpenSummary = true if mediaOpen
|
|
|
|
@mediaSummary.mediaOpen = mediaOpenSummary
|
|
|
|
# the user needs media controls if any media is open, or, if the user has indicated they want to open a JamTrack
|
|
@mediaSummary.userNeedsMediaControls = @mediaSummary.mediaOpen || window.JamTrackStore.jamTrack?
|
|
|
|
# this defines what the user wants to be open, not what actually is open in the backend and/or session
|
|
@mediaSummary.jamTrack = window.JamTrackStore.jamTrack
|
|
|
|
# figure out if we opened any media
|
|
isOpener = false
|
|
|
|
if @mediaSummary.recordingOpen
|
|
isOpener = @recordedTracks[0].isOpener
|
|
else if @mediaSummary.jamTrackOpen
|
|
isOpener = @jamTracks[0].isOpener
|
|
else if @mediaSummary.backingTrackOpen
|
|
isOpener = @backingTracks[0].isOpener
|
|
|
|
@mediaSummary.isOpener = isOpener
|
|
|
|
@prepareSimulatedMixers()
|
|
|
|
|
|
# this method is pretty complicated because it forks on a key bit of state:
|
|
# sessionModel.isPlayingRecording()
|
|
# a backing track opened as part of a recording has a different behavior and presence on the server (recording.recorded_backing_tracks)
|
|
# than a backing track opend ad-hoc (connection.backing_tracks)
|
|
|
|
resolveBackingTracks: () ->
|
|
backingTracks = []
|
|
|
|
return backingTracks unless @backingTrackMixers.length > 0
|
|
|
|
# find both client and server representation of the backing track
|
|
serverBackingTracks = []
|
|
backingTrackMixers = @backingTrackMixers
|
|
|
|
if @session.isPlayingRecording()
|
|
backingTrackMixers = context._.filter(backingTrackMixers, (mixer) -> return mixer.managed || !mixer.managed?)
|
|
serverBackingTracks = @session.recordedBackingTracks()
|
|
else
|
|
serverBackingTracks = @session.backingTracks();
|
|
backingTrackMixers = context._.filter(backingTrackMixers, (mixer) -> return !mixer.managed)
|
|
if backingTrackMixers.length > 1
|
|
logger.error("multiple, managed backing track mixers encountered", backingTrackMixers)
|
|
@app.notify({
|
|
title: "Multiple Backing Tracks Encountered",
|
|
text: "Only one backing track can be open a time.",
|
|
icon_url: "/assets/content/icon_alert_big.png"
|
|
});
|
|
return backingTracks;
|
|
|
|
# we don't render backing tracks unless we have server data to accompany
|
|
if !serverBackingTracks? || serverBackingTracks.length == 0
|
|
return backingTracks
|
|
|
|
noCorrespondingTracks = false
|
|
for mixer in backingTrackMixers
|
|
# find the track or tracks that correspond to the mixer
|
|
correspondingTracks = []
|
|
noCorrespondingTracks = false
|
|
if @session.isPlayingRecording()
|
|
for backingTrack in serverBackingTracks
|
|
# occurs if this client is the one that opened the track, # occurs if this client is a remote participant
|
|
if mixer.persisted_track_id == backingTrack.client_track_id || mixer.id == 'L' + backingTrack.client_track_id
|
|
correspondingTracks.push(backingTrack)
|
|
else
|
|
# if this is just an open backing track, then we can assume that the 1st backingTrackMixer is ours
|
|
correspondingTracks.push(serverBackingTracks[0])
|
|
|
|
if correspondingTracks.length == 0
|
|
noCorrespondingTracks = true
|
|
logger.debug("renderBackingTracks: could not map backing tracks")
|
|
@app.notify({
|
|
title: "Unable to Open Backing Track",
|
|
text: "Could not correlate server and client tracks",
|
|
icon_url: "/assets/content/icon_alert_big.png"
|
|
});
|
|
break
|
|
|
|
# now we have backing track and mixer in hand; we can render
|
|
serverBackingTrack = correspondingTracks[0]
|
|
|
|
oppositeMixer = @getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL);
|
|
|
|
isOpener = mixer.group_id == ChannelGroupIds.MediaTrackGroup
|
|
data =
|
|
isOpener: isOpener
|
|
shortFilename: context.JK.getNameOfFile(serverBackingTrack.filename)
|
|
instrumentIcon: context.JK.getInstrumentIcon45(serverBackingTrack.instrument_id)
|
|
photoUrl: "/assets/content/icon_recording.png"
|
|
showLoop: isOpener && !@session.isPlayingRecording()
|
|
track: serverBackingTrack
|
|
mixers: @mediaMixers(mixer, isOpener)
|
|
|
|
backingTracks.push(data)
|
|
|
|
backingTracks
|
|
|
|
resolveJamTracks: () ->
|
|
_jamTracks = []
|
|
|
|
return _jamTracks unless @jamTrackMixers.length > 0
|
|
|
|
|
|
jamTrackMixers = @jamTrackMixers.slice();
|
|
jamTracks = []
|
|
jamTrackName = null;
|
|
jamTrackMixdown = {id: null}
|
|
|
|
if @session.isPlayingRecording()
|
|
# only return managed mixers for recorded backing tracks
|
|
jamTracks = @session.recordedJamTracks()
|
|
jamTrackName = @session.recordedJamTrackName()
|
|
else
|
|
# only return un-managed (ad-hoc) mixers for normal backing tracks
|
|
jamTracks = @session.jamTracks()
|
|
jamTrackName = @session.jamTrackName()
|
|
jamTrackMixdown = @session.jamTrackMixdown()
|
|
|
|
# pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between JamTrack vs Peer)
|
|
# if it's a locally opened track (JamTrackGroup), then we can say this person is the opener
|
|
isOpener = jamTrackMixers[0].group_id == ChannelGroupIds.JamTrackGroup;
|
|
|
|
if jamTracks
|
|
noCorrespondingTracks = false
|
|
|
|
# Are we opening a mixdown, or a full track?
|
|
if jamTrackMixdown.id?
|
|
logger.debug("MixerHelper: mixdown is active. id: #{jamTrackMixdown.id}")
|
|
if jamTrackMixers.length == 0
|
|
noCorrespondingTracks = true
|
|
logger.error("could not correlate mixdown tracks", jamTrackMixers, jamTrackMixdown)
|
|
@app.notify({
|
|
title: "Unable to Open Custom Mix",
|
|
text: "Could not correlate server and client tracks",
|
|
icon_url: "/assets/content/icon_alert_big.png"})
|
|
return _jamTracks
|
|
else if jamTrackMixers.length > 1
|
|
logger.warn("ignoring wrong amount of mixers for JamTrack in mixdown mode")
|
|
return _jamTracks
|
|
else
|
|
|
|
instrumentIcon = context.JK.getInstrumentIcon24('other')
|
|
part = null
|
|
instrumentName = 'Custom Mix'
|
|
trackName = 'Custom Mix'
|
|
|
|
data =
|
|
name: jamTrackName
|
|
trackName: trackName
|
|
part: part
|
|
isOpener: isOpener
|
|
instrumentIcon: instrumentIcon
|
|
track: jamTrackMixdown
|
|
mixers: @mediaMixers(jamTrackMixers[0], isOpener)
|
|
|
|
_jamTracks.push(data)
|
|
else
|
|
logger.debug("MixerHelper: full jamtrack is active")
|
|
|
|
if jamTrackMixers.length == 1
|
|
logger.warn("ignoring wrong amount of mixers for JamTrack in Full Track mode")
|
|
return _jamTracks
|
|
|
|
for jamTrack in jamTracks
|
|
mixer = null
|
|
preMasteredClass = ""
|
|
# find the track or tracks that correspond to the mixer
|
|
correspondingTracks = []
|
|
|
|
for matchMixer in @jamTrackMixers
|
|
if matchMixer.id == jamTrack.id
|
|
correspondingTracks.push(jamTrack)
|
|
mixer = matchMixer
|
|
|
|
if correspondingTracks.length == 0
|
|
noCorrespondingTracks = true
|
|
logger.error("could not correlate jam tracks", jamTrackMixers, jamTracks)
|
|
@app.notify({
|
|
title: "Unable to Open JamTrack",
|
|
text: "Could not correlate server and client tracks",
|
|
icon_url: "/assets/content/icon_alert_big.png"})
|
|
return _jamTracks
|
|
|
|
#jamTracks = $.grep(jamTracks, (value) =>
|
|
# $.inArray(value, correspondingTracks) < 0
|
|
#)
|
|
|
|
# prune found mixers
|
|
jamTrackMixers.splice(mixer);
|
|
|
|
oneOfTheTracks = correspondingTracks[0];
|
|
instrumentIcon = context.JK.getInstrumentIcon24(oneOfTheTracks.instrument.id);
|
|
|
|
part = oneOfTheTracks.part
|
|
|
|
instrumentName = oneOfTheTracks.instrument.description
|
|
|
|
if part?
|
|
trackName = "#{instrumentName}: #{part}"
|
|
else
|
|
trackName = instrumentName
|
|
|
|
if jamTrack.track_type == 'Click'
|
|
trackName = 'Clicktrack'
|
|
|
|
data =
|
|
name: jamTrackName
|
|
trackName: trackName
|
|
part: part
|
|
isOpener: isOpener
|
|
instrumentIcon: instrumentIcon
|
|
track: oneOfTheTracks
|
|
mixers: @mediaMixers(mixer, isOpener)
|
|
|
|
_jamTracks.push(data)
|
|
|
|
_jamTracks
|
|
|
|
resolveRecordedTracks: () ->
|
|
recordedTracks = []
|
|
|
|
return recordedTracks unless @recordingTrackMixers.length > 0
|
|
|
|
serverRecordedTracks = @session.recordedTracks()
|
|
|
|
isOpener = @recordingTrackMixers[0].group_id == ChannelGroupIds.MediaTrackGroup
|
|
|
|
# using the server's info in conjuction with the client's, draw the recording tracks
|
|
if serverRecordedTracks
|
|
recordingName = @session.recordingName()
|
|
noCorrespondingTracks = false
|
|
for mixer in @recordingTrackMixers
|
|
preMasteredClass = ""
|
|
correspondingTracks = []
|
|
for recordedTrack in serverRecordedTracks
|
|
if mixer.id.indexOf("L") == 0
|
|
if mixer.id.substring(1) == recordedTrack.client_track_id
|
|
correspondingTracks.push(recordedTrack)
|
|
else if mixer.id.indexOf("C") == 0
|
|
if mixer.id.substring(1) == recordedTrack.client_id
|
|
correspondingTracks.push(recordedTrack)
|
|
preMasteredClass = "pre-mastered-track"
|
|
else
|
|
# this should not be possible
|
|
alert("Invalid state: the recorded track had neither persisted_track_id or persisted_client_id")
|
|
|
|
if correspondingTracks.length == 0
|
|
noCorrespondingTracks = true
|
|
logger.debug("unable to correlate all recorded tracks", recordingMixers, serverRecordedTracks)
|
|
@app.notify({
|
|
title: "Unable to Open Recording",
|
|
text: "Could not correlate server and client tracks",
|
|
icon_url: "/assets/content/icon_alert_big.png"});
|
|
return recordedTracks
|
|
|
|
serverRecordedTracks = $.grep(serverRecordedTracks,
|
|
(value) =>
|
|
$.inArray(value, correspondingTracks) < 0
|
|
)
|
|
|
|
oneOfTheTracks = correspondingTracks[0]
|
|
instrumentIcon = context.JK.getInstrumentIcon24(oneOfTheTracks.instrument_id)
|
|
userName = oneOfTheTracks.user.name
|
|
userName = oneOfTheTracks.user.first_name + ' ' + oneOfTheTracks.user.last_name unless userName?
|
|
|
|
data =
|
|
recordingName: recordingName
|
|
isOpener: isOpener
|
|
userName: userName
|
|
instrumentIcon: instrumentIcon
|
|
track: oneOfTheTracks
|
|
mixers: @mediaMixers(mixer, isOpener)
|
|
|
|
recordedTracks.push(data)
|
|
|
|
recordedTracks
|
|
|
|
resolveMetronome: () ->
|
|
|
|
return null if @metronomeTrackMixers.length == 0
|
|
|
|
mixer = @metronomeTrackMixers[0]
|
|
|
|
instrumentIcon = "/assets/content/icon_metronome.png"
|
|
|
|
metronome =
|
|
instrumentIcon: instrumentIcon
|
|
mixers: @mediaMixers(mixer, true)
|
|
|
|
metronome
|
|
|
|
resolveChatMixer: () ->
|
|
masterChatMixers = @mixersForGroupId(ChannelGroupIds.AudioInputChatGroup, MIX_MODES.MASTER);
|
|
|
|
return null if masterChatMixers.length == 0
|
|
|
|
personalChatMixers = @mixersForGroupId(ChannelGroupIds.AudioInputChatGroup, MIX_MODES.PERSONAL);
|
|
|
|
if personalChatMixers.length == 0
|
|
logger.warn("unable to find personal mixer for voice chat");
|
|
return null
|
|
|
|
|
|
masterChatMixer = masterChatMixers[0];
|
|
personalChatMixer = personalChatMixers[0];
|
|
|
|
{
|
|
master: {
|
|
mixer: masterChatMixer
|
|
muteMixer: masterChatMixer
|
|
vuMixer: masterChatMixer
|
|
oppositeMixer: personalChatMixer
|
|
}
|
|
personal: {
|
|
mixer: personalChatMixer
|
|
muteMixer: personalChatMixer
|
|
vuMixer: personalChatMixer
|
|
oppositeMixer: masterChatMixer
|
|
}
|
|
}
|
|
|
|
# supply the master mixer of a media track, and this function will harvest out the rest
|
|
mediaMixers:(masterMixer, isOpener) ->
|
|
personalMixer = if isOpener then @getMixerByResourceId(masterMixer.rid, MIX_MODES.PERSONAL) else null
|
|
personalVuMixer = if isOpener then personalMixer else masterMixer
|
|
{
|
|
isOpener: isOpener
|
|
|
|
master: {
|
|
mixer: masterMixer
|
|
muteMixer: masterMixer
|
|
vuMixer: masterMixer
|
|
}
|
|
personal: {
|
|
mixer: personalMixer
|
|
muteMixer: personalMixer
|
|
vuMixer: personalVuMixer
|
|
}
|
|
}
|
|
|
|
|
|
mixersForGroupIds: (groupIds, mixMode) ->
|
|
foundMixers = []
|
|
mixers = if mixMode == MIX_MODES.MASTER then @masterMixers else @personalMixers;
|
|
|
|
for mixer in mixers
|
|
for groupId in groupIds
|
|
if mixer.group_id == groupId
|
|
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
|
|
|
|
mixerForGroupId: (groupId, mixMode) ->
|
|
mixers = @mixersForGroupId(groupId, mixMode)
|
|
if mixers? && mixers.length > 0
|
|
mixers[0]
|
|
else
|
|
null
|
|
|
|
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
|
|
unless mixer?
|
|
#logger.debug("empty mixer: ", mixers)
|
|
continue
|
|
|
|
if mixer.client_id == clientId
|
|
for groupId in groupIds
|
|
if mixer.group_id == groupId
|
|
if (mixer.groupId != ChannelGroupIds.UserMusicInputGroup) && !(mixer.id of usedMixers)
|
|
mixers = foundMixers[mixer.group_id]
|
|
if !mixers
|
|
mixers = []
|
|
foundMixers[mixer.group_id] = mixers
|
|
mixers.push(mixer)
|
|
|
|
foundMixers
|
|
|
|
getMixerByResourceId:(resourceId, mode) ->
|
|
mixerPair = @mixersByResourceId[resourceId];
|
|
|
|
return null if(!mixerPair)
|
|
|
|
if !mode?
|
|
return mixerPair;
|
|
else
|
|
if mode == MIX_MODES.MASTER
|
|
return mixerPair.master
|
|
else
|
|
return mixerPair.personal
|
|
|
|
|
|
findMixerForTrack: (client_id, track, myTrack, mode = MIX_MODES.PERSONAL) ->
|
|
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, mode)
|
|
vuMixer = mixer
|
|
muteMixer = mixer
|
|
|
|
# sanity checks
|
|
if mixer && (mixer.group_id != ChannelGroupIds.AudioInputMusicGroup && mixer.group_id != ChannelGroupIds.MidiInputMusicGroup)
|
|
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, !mode)
|
|
|
|
if mode == 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 && oppositeMixer.group_id != ChannelGroupIds.MidiInputMusicGroup
|
|
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, @allMixers)
|
|
else
|
|
switch mode
|
|
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 && mixer.group_id != ChannelGroupIds.PeerMidiInputMusicGroup)
|
|
logger.warn("master: found remote mixer that was not of groupID: PeerAudioInputMusicGroup", client_id, track.client_track_id, 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.warn("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("personal: unable to find a PeerAudioInputMusicGroup master mixer matching a UserMusicInput", client_id, track.client_track_id)
|
|
else if oppositeMixer.group_id != ChannelGroupIds.PeerAudioInputMusicGroup && oppositeMixer.group_id != mixer.group_id != ChannelGroupIds.PeerMidiInputMusicGroup
|
|
logger.error("personaol: found remote mixer that was not of groupID: PeerAudioInputMusicGroup", client_id, track.client_track_id, 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
|
|
|
|
getOriginalVolume: (mixers, gainType) ->
|
|
originalVolume = null
|
|
if gainType == 'music'
|
|
|
|
# find a non media volL to compare against for later 'relative move'
|
|
for mixer in mixers
|
|
if mixer.name != CategoryGroupIds.UserMedia && mixer.name != CategoryGroupIds.MediaTrack
|
|
originalVolume = mixer.volume_left
|
|
break
|
|
else
|
|
originalVolume = mixers[0].volume_left
|
|
|
|
originalVolume
|
|
|
|
faderChanged: (data, mixers, gainType) ->
|
|
mixers = [mixers] unless $.isArray(mixers)
|
|
|
|
originalVolume = @getOriginalVolume(mixers, gainType)
|
|
|
|
for mixer in mixers
|
|
broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
|
|
mixer = @fillTrackVolumeObject(mixer.id, mixer.mode, broadcast)
|
|
|
|
relative = gainType == 'music' && (mixer.name == CategoryGroupIds.UserMedia || mixer.name == CategoryGroupIds.MediaTrack)
|
|
|
|
@setMixerVolume(mixer, data.percentage, relative, originalVolume)
|
|
|
|
# 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) ->
|
|
if $.isArray(mixer)
|
|
mixer = mixer[0]
|
|
|
|
gainPercent = context.JK.FaderHelpers.convertAudioTaperToPercent(mixer.volume_left)
|
|
context.JK.FaderHelpers.setFaderValue(mixer.id, gainPercent)
|
|
context.JK.FaderHelpers.showFader(mixer.id)
|
|
|
|
panChanged: (data, mixers, groupId) ->
|
|
mixers = [mixers] unless $.isArray(mixers)
|
|
# media tracks are the only controls that sometimes set two mixers right now
|
|
for mixer in mixers
|
|
broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
|
|
mixer = @fillTrackVolumeObject(mixer.id, mixer.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, Math.abs(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);
|
|
|
|
loopChanged: (mixer, shouldLoop) ->
|
|
|
|
@fillTrackVolumeObject(mixer.id, mixer.mode)
|
|
context.trackVolumeObject.loop = shouldLoop
|
|
context.jamClient.SessionSetControlState(mixer.id, mixer.mode)
|
|
|
|
# keep state of mixer in sync with backend
|
|
mixer = @getMixer(mixer.id, mixer.mode)
|
|
mixer.loop = context.trackVolumeObject.loop
|
|
|
|
setMixerVolume: (mixer, volumePercent, relative, originalVolume) ->
|
|
###
|
|
// 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.
|
|
###
|
|
|
|
newVolume = context.JK.FaderHelpers.convertPercentToAudioTaper(volumePercent);
|
|
if relative
|
|
context.trackVolumeObject.volL = context.trackVolumeObject.volL + (newVolume - originalVolume)
|
|
context.trackVolumeObject.volR = context.trackVolumeObject.volR + (newVolume - originalVolume)
|
|
|
|
# keep within range
|
|
if context.trackVolumeObject.volL < -80
|
|
context.trackVolumeObject.volL = -80
|
|
else if context.trackVolumeObject.volL > 20
|
|
context.trackVolumeObject.volL = 20
|
|
|
|
if context.trackVolumeObject.volR < -80
|
|
context.trackVolumeObject.volR = -80
|
|
else if context.trackVolumeObject.volR > 20
|
|
context.trackVolumeObject.volR = 20
|
|
|
|
|
|
else
|
|
context.trackVolumeObject.volL = newVolume
|
|
context.trackVolumeObject.volR = newVolume
|
|
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
|
|
|
|
collectStats: (mixer) ->
|
|
mixerStats = @vuStats[mixer.id]
|
|
|
|
unless mixerStats?
|
|
mixerStats = {count: 0, group_name: context.JK.groupIdDisplay(mixer)}
|
|
@vuStats[mixer.id] = mixerStats
|
|
|
|
mixerStats.count++
|
|
|
|
dumpVUStats: () ->
|
|
|
|
# to use: check MixerStore for setInterval in cstr
|
|
logger.debug("VU STAT DUMP")
|
|
for mixerId, mixerStat of @vuStats
|
|
logger.debug("VU STAT: #{mixerState.group_name} count=#{mixerStat.count}")
|
|
|
|
updateVU: (mixerId, mode, leftValue, leftClipping, rightValue, rightClipping) ->
|
|
mixer = @getMixer(mixerId, mode)
|
|
|
|
if mixer?
|
|
@collectStats(mixer) if @shouldCollectVuStats
|
|
context.JK.VuHelpers.updateVU3(mixer, leftValue, leftClipping, rightValue, rightClipping)
|
|
|
|
###
|
|
if mixer
|
|
if mixer.stereo # // stereo track
|
|
if mixerId.substr(-4) == "_vul"
|
|
context.JK.VuHelpers.updateVU2('vul', mixer, value)
|
|
else
|
|
context.JK.VuHelpers.updateVU2('vur', 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)
|
|
###
|
|
getTrackInfo: () ->
|
|
context.JK.TrackHelpers.getTrackInfo(context.jamClient, @masterMixers)
|
|
|
|
getGroupMixer: (categoryId, mode) ->
|
|
groupId = if mode == MIX_MODES.MASTER then ChannelGroupIds.MasterCatGroup else ChannelGroupIds.MonitorCatGroup
|
|
oppositeGroupId = if !mode == MIX_MODES.MASTER then ChannelGroupIds.MasterCatGroup else ChannelGroupIds.MonitorCatGroup
|
|
mixers = @mixersForGroupId(groupId, mode)
|
|
oppositeMixers = @mixersForGroupId(oppositeGroupId, !mode)
|
|
|
|
if mixers.length == 0
|
|
#logger.warn("could not find mixer with group ID: " + groupId + ', mode:' + mode)
|
|
return null
|
|
|
|
found = null
|
|
oppositeFound = null
|
|
for mixer in mixers
|
|
if mixer.name == categoryId
|
|
found = mixer
|
|
break
|
|
|
|
for mixer in oppositeMixers
|
|
if mixer.name == categoryId
|
|
oppositeFound = mixer
|
|
break
|
|
|
|
unless found?
|
|
logger.warn("could not find mixer with categoryId: " + categoryId)
|
|
return null
|
|
else
|
|
{
|
|
mixer: found,
|
|
muteMixer : found,
|
|
vuMixer: found,
|
|
oppositeMixer: oppositeFound
|
|
}
|
|
|
|
prepareSimulatedMixers: () ->
|
|
@simulatedMusicCategoryMixers[MIX_MODES.MASTER] = @getSimulatedMusicCategoryMixer(MIX_MODES.MASTER)
|
|
@simulatedMusicCategoryMixers[MIX_MODES.PERSONAL] = @getSimulatedMusicCategoryMixer(MIX_MODES.PERSONAL)
|
|
@simulatedChatCategoryMixers[MIX_MODES.MASTER] = @getSimulatedChatCategoryMixer(MIX_MODES.MASTER)
|
|
@simulatedChatCategoryMixers[MIX_MODES.PERSONAL] = @getSimulatedChatCategoryMixer(MIX_MODES.PERSONAL)
|
|
|
|
getSimulatedMusicCategoryMixer: (mode) ->
|
|
myInputs = @getAudioInputCategoryMixer(mode)?.mixer
|
|
peerInputs = @getUserMusicCategoryMixer(mode)?.mixer
|
|
myMedia= @getMediaCategoryMixer(mode)?.mixer
|
|
peerMedia = @getUserMediaCategoryMixer(mode)?.mixer
|
|
metronome = @getMetronomeCategoryMixer(mode)?.mixer
|
|
output = @getOutputMixer(mode)
|
|
oppositeOutput = @getOutputMixer(!mode)
|
|
|
|
# if myInputs category is missing, all categories are missing (seen when audio is first starting)
|
|
if myInputs
|
|
{
|
|
first: myInputs
|
|
mixer: [myInputs, peerInputs, myMedia, peerMedia, metronome]
|
|
muteMixer: [myInputs, peerInputs, myMedia, peerMedia, metronome]
|
|
vuMixer: output
|
|
}
|
|
else
|
|
null
|
|
|
|
|
|
getSimulatedChatCategoryMixer: (mode) ->
|
|
myChats = @getChatCategoryMixer(mode)?.mixer
|
|
peerChats = @getUserChatCategoryMixer(mode)?.mixer
|
|
if myChats
|
|
{
|
|
first: myChats
|
|
mixer: [myChats, peerChats]
|
|
muteMixer: [myChats, peerChats]
|
|
vuMixer: myChats
|
|
}
|
|
else
|
|
null
|
|
|
|
|
|
getAudioInputCategoryMixer: (mode) ->
|
|
@getGroupMixer(CategoryGroupIds.AudioInputMusic, mode)
|
|
|
|
getChatCategoryMixer: (mode) ->
|
|
@getGroupMixer(CategoryGroupIds.AudioInputChat, mode)
|
|
|
|
getUserChatCategoryMixer: (mode) ->
|
|
@getGroupMixer(CategoryGroupIds.UserChat, mode)
|
|
|
|
getMediaCategoryMixer: (mode) ->
|
|
@getGroupMixer(CategoryGroupIds.MediaTrack, mode)
|
|
|
|
getUserMediaCategoryMixer: (mode) ->
|
|
@getGroupMixer(CategoryGroupIds.UserMedia, mode)
|
|
|
|
getUserMusicCategoryMixer: (mode) ->
|
|
@getGroupMixer(CategoryGroupIds.UserMusic, mode)
|
|
|
|
getMetronomeCategoryMixer: (mode) ->
|
|
@getGroupMixer(CategoryGroupIds.Metronome, mode)
|
|
|
|
getOutputCategoryMixer: (mode) ->
|
|
if mode == MIX_MODES.MASTER
|
|
@getGroupMixer(CategoryGroupIds.MasterCatGroup, mode)
|
|
else
|
|
@getGroupMixer(CategoryGroupIds.MonitorCatGroup, mode)
|
|
|
|
getOutputMixer: (mode) ->
|
|
if mode == MIX_MODES.MASTER
|
|
@mixerForGroupId(ChannelGroupIds.MasterGroup, mode)
|
|
else
|
|
@mixerForGroupId(ChannelGroupIds.MonitorGroup, mode)
|
|
|
|
refreshMixer: (mixers) ->
|
|
return null unless mixers? && mixers.mixer?
|
|
|
|
updateMixers = null
|
|
if $.isArray(mixers.mixer)
|
|
if mixers.mixer.length > 0
|
|
updateMixers = []
|
|
for mixer in mixers.mixer
|
|
updateMixers.push(@getMixer(mixer.id, mixer.mode))
|
|
else
|
|
updateMixers = @getMixer(mixers.mixer.id, mixers.mixer.mode)
|
|
|
|
updatedVUMixers = null
|
|
if $.isArray(mixers.vuMixer)
|
|
updatedVUMixers = []
|
|
for vuMixer in mixers.vuMixer
|
|
updateVUMixers.push(@getMixer(vuMixer.id, vuMixer.mode))
|
|
else
|
|
updateVUMixers = @getMixer(mixers.vuMixer.id, mixers.vuMixer.mode)
|
|
|
|
updateMuteMixers = null
|
|
if $.isArray(mixers.muteMixer)
|
|
updateMuteMixers = []
|
|
for muteMixer in mixers.muteMixer
|
|
updateMuteMixers.push(@getMixer(muteMixer.id, muteMixer.mode))
|
|
else
|
|
updateMuteMixers = @getMixer(mixers.muteMixer.id, mixers.muteMixer.mode)
|
|
|
|
oppositeMixer = if mixers.oppositeMixer then @getMixer(mixers.oppositeMixer.id, mixers.oppositeMixer.mode) else null
|
|
|
|
if updateMixers
|
|
{
|
|
mixer: updateMixers
|
|
vuMixer: updateVUMixers
|
|
muteMixer: updateMuteMixers
|
|
oppositeMixer: oppositeMixer
|
|
}
|
|
else
|
|
return null
|
|
|
|
|
|
recordingName: () ->
|
|
@session.recordingName()
|
|
|
|
jamTrackName: () ->
|
|
@session.jamTrackName()
|