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, @clientsWithAudioOverride, @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 masterMixer 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) -> beforeMode = 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) -> 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, @personalMixers) 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 else logger.error("no UserMusicInputGroup for client_id #{client_id} in PERSONAL mode", mixers) { 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, controlGroup) -> mixers = [mixers] unless $.isArray(mixers) originalVolume = @getOriginalVolume(mixers, gainType) if controlGroup? mixers = [mixers[0]] 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, controlGroup) # keep state of mixer in sync with backend mixer = @getMixer(mixer.id, mixer.mode) mixer.volume_left = context.trackVolumeObject.volL else 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, controlGroup) -> ### // 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 if controlGroup? if mixer.mode == MIX_MODES.PERSONAL controlGroupsArg = 0 else controlGroupsArg = 1 context.jamClient.setSessionMixerCategoryPlayoutState(controlGroup == 'music', controlGroupsArg); else 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()