diff --git a/admin/app/views/admin/jam_tracks/_form.html.slim b/admin/app/views/admin/jam_tracks/_form.html.slim index 9ec25ffcf..2eb08fd27 100644 --- a/admin/app/views/admin/jam_tracks/_form.html.slim +++ b/admin/app/views/admin/jam_tracks/_form.html.slim @@ -2,20 +2,16 @@ = f.semantic_errors *f.object.errors.keys = f.inputs name: 'JamTrack fields' do = f.input :name, :input_html => { :rows=>1, :maxlength=>200 } - b style='margin-left:10px' - i - | JamTrack should only be made available (to end users) if all its sub-component are in place: - = f.input :available, as: :boolean = f.input :description, :input_html => { :rows=>5, :maxlength=>1000 } = f.input :plan_code, :label=>'Recurly Plan Code', :required=>true, :hint => 'Must match plan code in Recurly' - = f.input :version, :label => 'Version', :hint => 'Increment this value whenever you invalidate (update) the definition of this JamTrack' + = f.input :version, :label => 'Version', :hint => 'Increment this value whenever you invalidate (update) the media in the JamTrack. Changing JMEP does not count as a version change; changing anything about a track (audio, instrument, part) does.' //= f.input :initial_play_silence, :label => 'Initial Play Silence (seconds)' = f.input :time_signature, collection: JamRuby::JamTrack::TIME_SIGNATURES, include_blank: false - = f.input :status, collection: JamRuby::JamTrack::STATUS, include_blank: false + = f.input :status, collection: JamRuby::JamTrack::STATUS, include_blank: false, hint: 'Only set to Production when end users should be able to purchase this JamTrack' = f.input :recording_type, collection: JamRuby::JamTrack::RECORDING_TYPE, include_blank: false - = f.input :original_artist, :input_html => { :rows=>2, :maxlength=>200 } - = f.input :songwriter, :input_html => { :rows=>5, :maxlength=>1000 } - = f.input :publisher, :input_html => { :rows=>5, :maxlength=>1000 } + = f.input :original_artist, :input_html => { :rows=>1, :maxlength=>1000 } + = f.input :songwriter, :input_html => { :rows=>1, :maxlength=>1000 } + = f.input :publisher, :input_html => { :rows=>1, :maxlength=>1000 } = f.input :licensor, collection: JamRuby::JamTrackLicensor.all, include_blank: false = f.input :pro, collection: JamRuby::JamTrack::PRO, include_blank: false = f.input :genre, collection: JamRuby::Genre.all, include_blank: false @@ -26,16 +22,13 @@ = f.input :reproduction_royalty_amount, :required=>true, :input_html=>{type:'numeric'} = f.input :licensor_royalty_amount, :required=>true, :input_html=>{type:'numeric'} = f.input :pro_royalty_amount, :required=>true, :input_html=>{type:'numeric'} - = f.input :url, :as => :file, :label => 'Audio File' - = f.input :jmep_text, :as => :text, :label => "JMEP Text", :input_html => {:rows => 5 } - = f.input :jmep_json, :as => :text, :label => "JMEP Json", :input_html => {:rows => 5, :readonly=>true }, :hint => 'readonly' + //= f.input :url, :as => :file, :label => 'Audio File' + = f.input :jmep_text, :as => :text, :label => "JMEP Text", :input_html => {:rows => 5 }, :hint => 'Tap-Ins & Lead Silence. Examples: https://jamkazam.atlassian.net/wiki/pages/viewpage.action?pageId=39289025#JamKazamMeta-EventProcessor(JMEP)-CommonExamples' + = f.input :jmep_json, :as => :text, :label => "JMEP Json", :input_html => {:rows => 5, :readonly => true }, :hint => 'Readonly field. This is shown here just so you can see what your JMEP got converted to readily' = f.semantic_fields_for :jam_track_tracks do |track| = render 'jam_track_track_fields', f: track - = f.semantic_fields_for :jam_track_tap_ins do |tap_in| - = render 'jam_track_tap_in_fields', f: tap_in .links = link_to_add_association 'Add Track', f, :jam_track_tracks, class: 'button', style: 'margin:20px;padding:10px 20px' - = link_to_add_association 'Add Tap In', f, :jam_track_tap_ins, class: 'button', style: 'margin:20px;padding:10px 20px' = f.actions \ No newline at end of file diff --git a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim index 0503f4b54..14de2978c 100644 --- a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim +++ b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim @@ -1,7 +1,7 @@ = f.inputs name: 'Track fields' do ol.nested-fields - = f.input :track_type, :as => :select, collection: JamRuby::JamTrackTrack::TRACK_TYPE, include_blank: false + = f.input :track_type, :as => :select, collection: ['Track', 'Master'], include_blank: false = f.input :instrument, collection: Instrument.all, include_blank: false = f.input :part, :required=>true, :input_html => { :rows=>1, :maxlength=>20, :type=>'numeric' } diff --git a/ruby/lib/jam_ruby/models/active_music_session.rb b/ruby/lib/jam_ruby/models/active_music_session.rb index 5e6ed8036..04724695c 100644 --- a/ruby/lib/jam_ruby/models/active_music_session.rb +++ b/ruby/lib/jam_ruby/models/active_music_session.rb @@ -31,6 +31,8 @@ module JamRuby validate :validate_opening_recording, :if => :opening_recording validate :validate_opening_jam_track, :if => :opening_jam_track validate :validate_opening_backing_track, :if => :opening_backing_track + + # not sure if this is helpful since if one opens, it always stays open validate :validate_opening_metronome, :if => :opening_metronome after_create :started_session @@ -101,11 +103,6 @@ module JamRuby end def validate_other_audio(error_key) - # validate that there is no metronome already open in this session - if metronome_active_was - errors.add(error_key, ValidationMessages::METRONOME_ALREADY_OPEN) - end - # validate that there is no backing track already open in this session if backing_track_path_was.present? errors.add(error_key, ValidationMessages::BACKING_TRACK_ALREADY_OPEN) diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index fa57b623f..4aa67bb1a 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -73,7 +73,7 @@ module JamRuby query = query.where("jam_track_rights.user_id = ?", user.id) end - query = query.where("jam_tracks.available = ?", true) unless user.admin + query = query.where("jam_tracks.status = ?", 'Production') unless user.admin query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank? query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}'") unless options[:instrument].blank? query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank? diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb index c8d7c523f..2e23aad3d 100644 --- a/ruby/lib/jam_ruby/models/jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -5,7 +5,7 @@ module JamRuby include JamRuby::S3ManagerMixin # there should only be one Master per JamTrack, but there can be N Track per JamTrack - TRACK_TYPE = %w{Master Track} + TRACK_TYPE = %w{Track Master} mount_uploader :url, JamTrackTrackUploader diff --git a/ruby/spec/jam_ruby/models/active_music_session_spec.rb b/ruby/spec/jam_ruby/models/active_music_session_spec.rb index 07d6f49a5..c117a65c2 100644 --- a/ruby/spec/jam_ruby/models/active_music_session_spec.rb +++ b/ruby/spec/jam_ruby/models/active_music_session_spec.rb @@ -955,15 +955,6 @@ describe ActiveMusicSession do @music_session.metronome_initiator.should be_nil end - it "disallow a metronome to be opened when another is already opened" do - # if a metronome is open, don't allow another to be opened - @music_session.open_metronome(@user1) - @music_session.errors.any?.should be_false - @music_session.open_metronome(@user1) - @music_session.errors.any?.should be_true - @music_session.errors[:metronome] == [ValidationMessages::METRONOME_ALREADY_OPEN] - end - it "disallow a metronome to be opened when recording is ongoing" do @recording = Recording.start(@music_session, @user1) @music_session.errors.any?.should be_false diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index 01c640719..fbaeed1a8 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -46,9 +46,15 @@ CONNECTION_UP: 'connection_up', CONNECTION_DOWN: 'connection_down', SCREEN_CHANGED: 'screen_changed', - JAMTRACK_DOWNLOADER_STATE_CHANGED: 'jamtrack_downloader_state' + JAMTRACK_DOWNLOADER_STATE_CHANGED: 'jamtrack_downloader_state', + METRONOME_PLAYBACK_MODE_SELECTED: 'metronome_playback_mode_selected' }; + context.JK.PLAYBACK_MONITOR_MODE = { + MEDIA_FILE: 'MEDIA_FILE', + JAMTRACK: 'JAMTRACK', + METRONOME: 'METRONOME' + } context.JK.ALERT_NAMES = { NO_EVENT : 0, BACKEND_ERROR : 1, //generic error - eg P2P message error diff --git a/web/app/assets/javascripts/jquery.metronomePlaybackMode.js b/web/app/assets/javascripts/jquery.metronomePlaybackMode.js new file mode 100644 index 000000000..4bc53bbc6 --- /dev/null +++ b/web/app/assets/javascripts/jquery.metronomePlaybackMode.js @@ -0,0 +1,96 @@ +(function(context, $) { + + "use strict"; + + context.JK = context.JK || {}; + + + // creates an iconic/graphical instrument selector. useful when there is minimal real-estate + + function setValue(val, $target) { + if(val == "cricket") { + $target.html("Play cluster test") + } + else { + $target.html("Play metronome") + } + } + + $.fn.metronomeSetPlaybackMode = function(val) { + return this.each(function (index) { + setValue(val, $(this)) + }); + }; + + $.fn.metronomePlaybackMode = function(options) { + + options = options || {mode: 'self'} + + return this.each(function(index) { + + function close() { + $parent.btOff(); + $parent.focus(); + } + + + var $parent = $(this); + var value = options.mode; + setValue(options.mode, $parent) + + function onModeSelected() { + var $li = $(this); + var playbackMode = $li.attr('data-playback-option'); + + value = playbackMode; + close(); + $parent.triggerHandler(context.JK.EVENTS.METRONOME_PLAYBACK_MODE_SELECTED, {playbackMode: playbackMode}); + return false; + }; + + // if the user goes into the bubble, remove + function waitForBubbleHover($bubble) { + $bubble.hoverIntent({ + over: function() { + if(timeout) { + clearTimeout(timeout); + timeout = null; + } + }, + out: function() { + $parent.btOff(); + }}); + } + + var timeout = null; + + context.JK.hoverBubble($parent, $('#template-metronome-playback-mode').html(), { + trigger:'click', + cssClass: 'metronome-playback-mode-selector-popup', + spikeGirth:0, + spikeLength:0, + width:160, + closeWhenOthersOpen: true, + offsetParent: $parent.offsetParent(), + positions:['top'], + preShow: function() { + $parent.find('.down-arrow').removeClass('down-arrow').addClass('up-arrow') + }, + postShow:function(container) { + $(container).find('li').click(onModeSelected) + if(timeout) { + clearTimeout(timeout); + timeout = null; + } + //waitForBubbleHover($(container)) + //timeout = setTimeout(function() {$parent.btOff()}, 3000) + }, + postHide:function() { + setValue(value, $parent) + } + }); + }); + } + + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/playbackControls.js b/web/app/assets/javascripts/playbackControls.js index 3930635e6..ce7d74075 100644 --- a/web/app/assets/javascripts/playbackControls.js +++ b/web/app/assets/javascripts/playbackControls.js @@ -25,6 +25,8 @@ logger.debug("no $parentElement specified in PlaybackControls"); } + var PLAYBACK_MONITOR_MODE = context.JK.PLAYBACK_MONITOR_MODE; + var $playButton = $('.play-button img.playbutton', $parentElement); var $pauseButton = $('.play-button img.pausebutton', $parentElement); var $currentTime = $('.recording-current', $parentElement); @@ -47,19 +49,19 @@ var canUpdateBackend = false; var playbackMode = PlaybackMode.EveryWhere; var monitorPlaybackTimeout = null; - var jamTrackMode = false; // if true, we use different APIs to determine playback info + var playbackMonitorMode = PLAYBACK_MONITOR_MODE.MEDIA_FILE; function startPlay() { updateIsPlaying(true); if(endReached) { update(0, playbackDurationMs, playbackPlaying); } - $self.triggerHandler('play', {playbackMode: playbackMode}); + $self.triggerHandler('play', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode}); } - function stopPlay() { + function stopPlay(endReached) { updateIsPlaying(false); - $self.triggerHandler('pause'); + $self.triggerHandler('pause', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode, endReached : endReached}); } function updateOffsetBasedOnPosition(offsetLeft) { @@ -68,7 +70,7 @@ playbackPositionMs = parseInt((offsetLeft / sliderBarWidth) * playbackDurationMs); updateCurrentTimeText(playbackPositionMs); if(canUpdateBackend) { - $self.triggerHandler('change-position', {positionMs: playbackPositionMs, jamTrackMode: jamTrackMode}); + $self.triggerHandler('change-position', {positionMs: playbackPositionMs, playbackMonitorMode: playbackMonitorMode}); canUpdateBackend = false; } } @@ -156,8 +158,24 @@ setPlaybackMode(playmode); }); + function styleControls( ) { + $parentElement.removeClass('mediafile-mode jamtrack-mode metronome-mode'); + if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.MEDIA_FILE) { + $parentElement.addClass('mediafile-mode'); + } + else if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.JAMTRACK) { + $parentElement.addClass('jamtrack-mode'); + } + else if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.METRONOME) { + $parentElement.addClass('metronome-mode'); + } + else + { + throw "unknown playbackMonitorMode: " + playbackMonitorMode; + } + } function monitorRecordingPlayback() { - if(jamTrackMode) { + if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.JAMTRACK) { var positionMs = context.jamClient.SessionCurrrentJamTrackPlayPosMs(); var duration = context.jamClient.SessionGetJamTracksPlayDurationMs(); var durationMs = duration.media_len; @@ -176,7 +194,13 @@ positionMs = 0; } - update(positionMs, durationMs, isPlaying); + if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.METRONOME) { + updateIsPlaying(isPlaying); + } + else { + update(positionMs, durationMs, isPlaying); + } + monitorPlaybackTimeout = setTimeout(monitorRecordingPlayback, 500); } @@ -194,7 +218,7 @@ isPlaying = false; durationTimeMs = playbackDurationMs; currentTimeMs = playbackDurationMs; - stopPlay(); + stopPlay(true); endReached = true; logger.debug("end reached"); } @@ -279,14 +303,23 @@ } } - function startMonitor(_jamTrackMode) { + function startMonitor(_playbackMonitorMode) { - jamTrackMode = !!_jamTrackMode; + if(_playbackMonitorMode === undefined || _playbackMonitorMode === null) { + playbackMonitorMode = PLAYBACK_MONITOR_MODE.MEDIA_FILE; + } + else { + playbackMonitorMode = _playbackMonitorMode; + } + logger.debug("playbackControl.startMonitor " + playbackMonitorMode + "") + + styleControls(); monitorRecordingPlayback(); } function stopMonitor() { + logger.debug("playbackControl.stopMonitor") if(monitorPlaybackTimeout!= null) { clearTimeout(monitorPlaybackTimeout); monitorPlaybackTimeout = null; diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 114c12c8a..4dde3a2cc 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -55,6 +55,16 @@ "MetronomeGroup": 12 }; + var METRO_SOUND_LOOKUP = { + 0 : "BuiltIn", + 1 : "SineWave", + 2 : "Beep", + 3 : "Click", + 4 : "Kick", + 5 : "Snare", + 6 : "MetroFile" + } + var sessionModel = null; var sessionId; var tracks = {}; @@ -83,12 +93,14 @@ var claimedRecording = null; var backing_track_path = null; var jamTrack = null; + var metronomeMixer = null; var playbackControls = null; var promptLeave = false; var rateSessionDialog = null; var friendInput = null; var sessionPageDone = null; var metroTempo = 120; + var metroCricket = false; var metroSound = "Beep"; var $recordingManagerViewer = null; var $screen = null; @@ -100,7 +112,10 @@ var downloadJamTrack = null; var $closePlaybackRecording = null; var $openBackingTrack = null; + var $metronomePlaybackSelect = null; + var $metronomePlaybackHelp = null; var mediaTrackGroups = [ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup]; + var muteBothMasterAndPersonalGroups = [ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup]; var rest = context.JK.Rest(); var RENDER_SESSION_DELAY = 750; // When I need to render a session, I have to wait a bit for the mixers to be there. @@ -461,39 +476,69 @@ sessionUtils.SessionPageLeave(); } - function handleTransitionsInRecordingPlayback() { - // let's see if we detect a transition to start playback or stop playback + function getMetronomeMasterMixers() { + return _mixersForGroupId(ChannelGroupIds.MetronomeGroup, MIX_MODES.MASTER); + } - var currentSession = sessionModel.getCurrentSession(); + function checkMetronomeTransition() { + // trust backend over server + var metronomeMasterMixers = getMetronomeMasterMixers(); - if(claimedRecording == null && (currentSession && currentSession.claimed_recording != null)) { - // this is a 'started with a claimed_recording' transition. - // we need to start a timer to watch for the state of the play session - playbackControls.startMonitor(); - } - else if(claimedRecording && (currentSession == null || currentSession.claimed_recording == null)) { - playbackControls.stopMonitor(); - } - claimedRecording = currentSession == null ? null : currentSession.claimed_recording; - - - if(backing_track_path == null && (currentSession && currentSession.backing_track_path != null)) { - playbackControls.startMonitor(); - } - else if(backing_track_path && (currentSession == null || currentSession.backing_track_path == null)) { - playbackControls.stopMonitor(); - } - backing_track_path = currentSession == null ? null : currentSession.backing_track_path; - - if(jamTrack == null && (currentSession && currentSession.jam_track != null)) { - playbackControls.startMonitor(true); - } - else if(jamTrack && (currentSession == null || currentSession.jam_track == null)) { - playbackControls.stopMonitor(); - } - jamTrack = currentSession == null ? null : currentSession.jam_track; + if (metronomeMixer == null && metronomeMasterMixers.length > 0) { + playbackControls.startMonitor(context.JK.PLAYBACK_MONITOR_MODE.METRONOME) } + else if (metronomeMixer != null && metronomeMasterMixers.length == 0) { + playbackControls.stopMonitor(); + } + metronomeMixer = metronomeMasterMixers.length > 0 ? metronomeMasterMixers : null; + } + + function checkJamTrackTransition(currentSession) { +// handle jam tracks + if (jamTrack == null && (currentSession && currentSession.jam_track != null)) { + playbackControls.startMonitor(context.JK.PLAYBACK_MONITOR_MODE.JAMTRACK); + } + else if (jamTrack && (currentSession == null || currentSession.jam_track == null)) { + playbackControls.stopMonitor(); + } + jamTrack = currentSession == null ? null : currentSession.jam_track; + } + + function checkBackingTrackTransition(currentSession) { +// handle backing tracks + if (backing_track_path == null && (currentSession && currentSession.backing_track_path != null)) { + playbackControls.startMonitor(); + } + else if (backing_track_path && (currentSession == null || currentSession.backing_track_path == null)) { + playbackControls.stopMonitor(); + } + backing_track_path = currentSession == null ? null : currentSession.backing_track_path; + } + + function checkRecordingTransition(currentSession) { +// handle claimed recordings + if (claimedRecording == null && (currentSession && currentSession.claimed_recording != null)) { + // this is a 'started with a claimed_recording' transition. + // we need to start a timer to watch for the state of the play session + playbackControls.startMonitor(); + } + else if (claimedRecording && (currentSession == null || currentSession.claimed_recording == null)) { + playbackControls.stopMonitor(); + } + claimedRecording = currentSession == null ? null : currentSession.claimed_recording; + } + + function handleTransitionsInRecordingPlayback() { + // let's see if we detect a transition to start playback or stop playback + + var currentSession = sessionModel.getCurrentSession(); + + checkRecordingTransition(currentSession); + checkBackingTrackTransition(currentSession); + checkJamTrackTransition(currentSession); + checkMetronomeTransition(); + } function sessionChanged() { @@ -510,8 +555,13 @@ * you must iterate. Convenience method to locate a particular * mixer by id. */ - function getMixer(mixerId) { - return allMixers[mixerId]; + function getMixer(mixerId, mode) { + + if(mode === undefined) { + mode = sessionModel.getMixMode(); + } + + return allMixers[(mode ? 'M' : 'P') + mixerId]; } function getMixerByResourceId(resourceId, mode) { @@ -632,7 +682,7 @@ var i; for(i = 0; i < masterMixers.length; i++) { var masterMixer = masterMixers[i]; - allMixers[masterMixer.id] = masterMixer; // populate allMixers by mixer.id + allMixers['M' + masterMixer.id] = masterMixer; // populate allMixers by mixer.id // populate mixer pair var mixerPair = {} @@ -643,16 +693,7 @@ for(i = 0; i < personalMixers.length; i++) { var personalMixer = personalMixers[i]; - if(personalMixer.group_id == ChannelGroupIds.MediaTrackGroup) { - // the reason we do this is because some media tracks have same ID in both master and personal moe - personalMixer.uniqueId = 'P--' + personalMixer.id - allMixers[personalMixer.uniqueId] = personalMixer - } - else { - allMixers[personalMixer.id] = personalMixer - - } - + allMixers['P' + personalMixer.id] = personalMixer // populate other side of mixer pair @@ -1000,6 +1041,7 @@ logger.warn("some tracks are open that we don't know how to show") } + checkMetronomeTransition(); } // this method is pretty complicated because it forks on a key bit of state: @@ -1070,7 +1112,7 @@ if(isOpener) { var oppositeMixer = getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL); - var mixerId = mixer.id + "," + oppositeMixer.uniqueId + var mixerId = mixer.id + "," + oppositeMixer.id } else { var mixerId = mixer.id; @@ -1176,7 +1218,7 @@ if(isOpener) { var oppositeMixer = getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL); - var mixerId = mixer.id + "," + oppositeMixer.uniqueId + var mixerId = mixer.id + "," + oppositeMixer.id } else { var mixerId = mixer.id; @@ -1226,17 +1268,15 @@ } function renderMetronomeTracks(metronomeTrackMixers) { - var metronomeActive = sessionModel.metronomeActive(); - logger.debug("rendering metronome track",metronomeActive) + logger.debug("rendering metronome track") // 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 (MediaTrackGroup), then we can say this person is the opener - var isOpener = metronomeTrackMixers[0].group_id == ChannelGroupIds.MediaTrackGroup; var name = "Metronome" // using the server's info in conjuction with the client's, draw the recording tracks - if(metronomeActive && metronomeTrackMixers.length > 0) { - var metronome = {active: metronomeActive} + if(metronomeTrackMixers.length > 0) { + var metronome = {} $('.session-recording-name').text(name);//sessionModel.getCurrentSession().backing_track_path); var noCorrespondingTracks = false; @@ -1264,33 +1304,8 @@ var instrumentIcon = context.JK.getInstrumentIcon45(oneOfTheTracks.instrument_id); var photoUrl = "/assets/content/icon_metronome_small.png"; - // var trackData = { - // trackId: oneOfTheTracks.id, - // clientId: oneOfTheTracks.client_id, - // name: "Tempo", - // instrumentIcon: photoUrl, - // avatar: instrumentIcon, - // latency: "good", - // gainPercent: 0, - // muteClass: 'hidden', - // mixerId: "", - // avatarClass : 'avatar-recording', - // preMasteredClass: "", - // hideVU: true, - // faderChanged : tempoFaderChanged, - // showMetronomeControls: true - // }; - - // _addRecordingTrack(trackData); - - if(isOpener) { - var oppositeMixer = getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL); - var mixerId = mixer.id + "," + oppositeMixer.uniqueId - } - else { - var mixerId = mixer.id; - } - + var oppositeMixer = getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL); + var mixerId = mixer.id + "," + oppositeMixer.id // Default trackData to participant + no Mixer state. var trackData = { @@ -1320,14 +1335,15 @@ trackData.mixerId = mixerId; // the master mixer controls the volume control for recordings (no personal controls in either master or personal mode) trackData.vuMixerId = mixer.id; // the master mixer controls the VUs for recordings (no personal controls in either master or personal mode) trackData.muteMixerId = mixer.id; // the master mixer controls the mute for recordings (no personal controls in either master or personal mode) - trackData.mediaTrackOpener = isOpener; - trackData.mediaControlsDisabled = !isOpener; - trackData.showHelpAboutMediaMixers = sessionModel.isPersonalMixMode() && isOpener; - + trackData.mediaTrackOpener = true + trackData.mediaControlsDisabled = false + trackData.showHelpAboutMediaMixers = false _addRecordingTrack(trackData, mixer, oppositeMixer); }// if setFormFromMetronome() + metroCricket = context.jamClient.getMetronomeCricketTestState(); + setMetronomePlaybackMode() } @@ -1394,7 +1410,7 @@ if(isOpener) { var oppositeMixer = getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL); - var mixerId = mixer.id + "," + oppositeMixer.uniqueId + var mixerId = mixer.id + "," + oppositeMixer.id } else { var mixerId = mixer.id; @@ -1455,7 +1471,6 @@ var mixer = $muteControl.data('mixer') var oppositeMixer = $muteControl.data('opposite-mixer') - logger.debug("muting tracks. current mixer id=" + mixer.id + ", opposite mixer id=" + oppositeMixer.id) var mixerPair = {} @@ -1656,14 +1671,14 @@ addNewGearDialog = new context.JK.AddNewGearDialog(app, self); } - function connectTrackToMixer(trackSelector, track, mixerId, gainPercent, groupId) { + function connectTrackToMixer(trackSelector, track, mixerId, gainPercent, groupId, mixer, oppositeMixer) { var vuOpts = $.extend({}, trackVuOpts); var faderOpts = $.extend({}, trackFaderOpts); faderOpts.faderId = mixerId; var vuLeftSelector = trackSelector + " .track-vu-left"; var vuRightSelector = trackSelector + " .track-vu-right"; var faderSelector = trackSelector + " .track-gain"; - var $fader = $(faderSelector).attr('mixer-id', mixerId).data('groupId', groupId) + var $fader = $(faderSelector).attr('mixer-id', mixerId).data('groupId', groupId).data('mixer', mixer).data('opposite-mixer', oppositeMixer); if(track.mediaControlsDisabled) { $fader.data('media-controls-disabled', true).data('media-track-opener', track.mediaTrackOpener) // this we be applied later to the fader handle $element } @@ -1675,9 +1690,9 @@ if (!track.hideVU) { context.JK.VuHelpers.renderVU(vuLeftSelector, vuOpts); - $track.find('.track-vu-left').attr('mixer-id', track.vuMixerId + '_vul').data('groupId', groupId) + $track.find('.track-vu-left').attr('mixer-id', track.vuMixerId + '_vul').data('groupId', groupId).data('mixer', mixer).data('opposite-mixer', oppositeMixer) context.JK.VuHelpers.renderVU(vuRightSelector, vuOpts); - $track.find('.track-vu-right').attr('mixer-id', track.vuMixerId + '_vur').data('groupId', groupId) + $track.find('.track-vu-right').attr('mixer-id', track.vuMixerId + '_vur').data('groupId', groupId).data('mixer', mixer).data('opposite-mixer', oppositeMixer) } if (track.showMetronomeControls) { @@ -1738,7 +1753,7 @@ mixer.range_low, mixer.range_high, mixer.volume_left); var trackSelector = 'div.track[track-id="' + track.id + '"]'; - connectTrackToMixer(trackSelector, track, mixer.id, gainPercent, mixer.group_id); + connectTrackToMixer(trackSelector, track, mixer.id, gainPercent, mixer.group_id, mixer, oppositeMixer); var $track = $('div.track[client-id="' + clientId + '"]'); var $trackIconMute = $track.find('.track-icon-mute') $trackIconMute.attr('mixer-id', muteMixer.id).data('mixer', mixer).data('opposite-mixer', oppositeMixer) @@ -1832,7 +1847,7 @@ // Render VU meters and gain fader var trackSelector = $destination.selector + ' .session-track[track-id="' + trackData.trackId + '"]'; var gainPercent = trackData.gainPercent || 0; - connectTrackToMixer(trackSelector, trackData, trackData.mixerId, gainPercent, trackData.group_id); + connectTrackToMixer(trackSelector, trackData, trackData.mixerId, gainPercent, trackData.group_id, mixer, oppositeMixer); var $closeButton = $('#div-track-close', 'div[track-id="' + trackData.trackId + '"]'); if (!allowDelete) { @@ -1907,9 +1922,18 @@ var faderId = $target.attr('mixer-id'); var groupId = $target.data('groupId'); var mixerIds = faderId.split(','); + + // media tracks are the only controls that sometimes set two mixers right now + var hasMasterAndPersonalControls = mixerIds.length == 2; + $.each(mixerIds, function(i,v) { var broadcast = !(data.dragging); // If fader is still dragging, don't broadcast - var mixer = fillTrackVolumeObject(v, broadcast); + + var mode = undefined; + if(hasMasterAndPersonalControls) { + mode = i == 0 ? MIX_MODES.MASTER : MIX_MODES.PERSONAL; + } + var mixer = fillTrackVolumeObject(v, mode, broadcast); setMixerVolume(mixer, data.percentage); @@ -1936,6 +1960,11 @@ function handleMetronomeCallback(args) { logger.debug("MetronomeCallback: ", args) metroTempo = args.bpm + metroCricket = args.cricket; + metroSound = METRO_SOUND_LOOKUP[args.sound]; + + setMetronomePlaybackMode(); + setFormFromMetronome(); // This isn't actually there, so we rely on the metroSound as set from select on form: // metroSound = args.sound @@ -2085,7 +2114,7 @@ } function _toggleAudioMute(mixerId, muting, mode) { - fillTrackVolumeObject(mixerId); + fillTrackVolumeObject(mixerId, mode); context.trackVolumeObject.mute = muting; if(mode === undefined) { @@ -2095,7 +2124,7 @@ } function _toggleAudioLoop(mixerId, loop, mode) { - fillTrackVolumeObject(mixerId); + fillTrackVolumeObject(mixerId, mode); context.trackVolumeObject.loop = loop; if(mode === undefined) { @@ -2135,8 +2164,6 @@ } } - - $.each(mixerIds, function(i,v) { var mixerId = v; // behavior: if this is the user's track in personal mode, then we mute the track globally @@ -2145,15 +2172,15 @@ var mixer = $control.data('mixer'); var oppositeMixer = $control.data('opposite-mixer') - if(mixer && oppositeMixer && (mixer.group_id == ChannelGroupIds.AudioInputMusicGroup || mediaTrackGroups.indexOf(mixer.group_id) > -1)) { + if(mixer && oppositeMixer && (muteBothMasterAndPersonalGroups.indexOf(mixer.group_id) > -1)) { // this is the user's local track; mute both personal and master mode logger.debug("muting both master and personal mode mixers") - _toggleAudioMute(mixer.id, muting, getMixer(mixer.id).mode) - _toggleAudioMute(oppositeMixer.id, muting, getMixer(oppositeMixer.uniqueId || oppositeMixer.id).mode) + _toggleAudioMute(mixer.id, muting, mixer.mode) + _toggleAudioMute(oppositeMixer.id, muting, oppositeMixer.mode) } else { logger.debug("muting mixer") - _toggleAudioMute(mixer.id, muting, getMixer(mixer.id).mode) + _toggleAudioMute(mixer.id, muting, mixer.mode) } // look for all controls matching this mixer id (important when it's personal mode + UserMusicInputGroup) @@ -2183,13 +2210,13 @@ } - function fillTrackVolumeObject(mixerId, broadcast) { + function fillTrackVolumeObject(mixerId, mode, broadcast) { _updateMixers(); var _broadcast = true; if (broadcast !== undefined) { _broadcast = broadcast; } - var mixer = getMixer(mixerId); + var mixer = getMixer(mixerId, mode); context.trackVolumeObject.clientID = mixer.client_id; context.trackVolumeObject.broadcast = _broadcast; context.trackVolumeObject.master = mixer.master; @@ -2536,23 +2563,30 @@ var unstable = [] // This should be handled in the below loop, actually: - // var map = context.jamClient.getMyNetworkState() - // if (!map.ntp_stable) { - // unstable.push("self"); - // } + var myState = context.jamClient.getMyNetworkState() var map; - $.each(sessionModel.participants(), function(index, participant) { - map = context.jamClient.getPeerState(participant.client_id) - if (!map.ntp_stable) { + $.each(sessionModel.participants(), function(index, participant) { + + var isSelf = participant.client_id == app.clientId; + + if(isSelf) { + var isStable = myState.ntp_stable; + } + else { + map = context.jamClient.getPeerState(participant.client_id) + var isStable = map.ntp_stable; + } + + if (!isStable) { var name = participant.user.name; if (!(name)) { name = participant.user.first_name + ' ' + participant.user.last_name; } - if (app.clientId == participant.client_id) { - name += " (This computer)" + if (isSelf) { + name += " (this computer)" } unstable.push(name) @@ -2574,18 +2608,17 @@ return false; } else { var unstable = unstableNTPClocks() - if (unstable.length > 0) { + if (sessionModel.participants().length > 1 && unstable.length > 0) { var names = unstable.join(", ") logger.debug("Unstable clocks: ", names, unstable) - app.notify({ - "title": "Couldn't open metronome", - "text": "The metronome feature requires that every user's computer in the session must agree on the current time. The computers of " + names + " have not successfully synchronized to the current time. The JamKazam service is trying to automatically correct this error condition. Please close this message, wait about 10 seconds, and then try opening the metronome again. If this problem persists after a couple of attempts, we recommend that the unsynchronized users restart the JamKazam application. If this error persists after a restart, please have the users with the issue contact support@jamkazam.com.", - "icon_url": "/assets/content/icon_alert_big.png" - }); + context.JK.Banner.showAlert("Couldn't open metronome", context._.template($('#template-help-metronome-unstable').html(), {names: names}, { variable: 'data' })); } else { + var bpm = 120; + logger.debug("opening the metronome with bpm: " + bpm + ", sound:" + metroSound) rest.openMetronome({id: sessionModel.id()}) .done(function() { - context.jamClient.SessionOpenMetronome(120, "Click", 1, 0) + context.jamClient.SessionStopPlay(); + context.jamClient.SessionOpenMetronome(bpm, metroSound, 1, 0); }) .fail(function(jqXHR) { logger.debug(jqXHR, jqXHR) @@ -2631,7 +2664,7 @@ else if(sessionModel.backingTrack() && sessionModel.backingTrack().path) { closeBackingTrack(); } - else if(sessionModel.metronomeActive()) { + else if(getMetronomeMasterMixers().length > 0) { closeMetronomeTrack(); } else { @@ -2730,9 +2763,12 @@ return false; } - function onPause() { - logger.debug("calling jamClient.SessionStopPlay"); - context.jamClient.SessionStopPlay(); + function onPause(e, data) { + logger.debug("calling jamClient.SessionStopPlay. endReached:", data.endReached); + + if(!data.endReached) { + context.jamClient.SessionStopPlay(); + } } function onPlay(e, data) { @@ -2743,7 +2779,7 @@ function onChangePlayPosition(e, data){ logger.debug("calling jamClient.SessionTrackSeekMs(" + data.positionMs + ")"); - if(data.jamTrackMode) { + if(data.playbackMonitorMode == context.JK.PLAYBACK_MONITOR_MODE.JAMTRACK) { context.jamClient.SessionJamTrackSeekMs(data.positionMs); } else { @@ -2784,6 +2820,10 @@ $("select.metro-sound").val(metroSound) } + function setMetronomePlaybackMode() { + $metronomePlaybackSelect.metronomeSetPlaybackMode(metroCricket ? 'cricket' : 'self') + } + function setMetronomeFromForm() { var tempo = $("select.metro-tempo:visible option:selected").val() var sound = $("select.metro-sound:visible option:selected").val() @@ -2795,7 +2835,7 @@ } if (sound==null || typeof(sound)=='undefined' || sound=="") { - s = "click" + s = "Beep" } else { s = sound } @@ -2803,13 +2843,23 @@ logger.debug("Setting tempo and sound:", t, s) metroTempo = t metroSound = s - context.jamClient.SessionSetMetronome(t, s, 1, 0) + context.jamClient.SessionSetMetronome(t, s, 1, 0); } function onMetronomeChanged(e, data) { setMetronomeFromForm() } + function metronomePlaybackModeChanged(e, data) { + + var mode = data.playbackMode; // will be either 'self' or 'cricket' + + logger.debug("setting metronome playback mode: ", mode) + + var isCricket = mode == 'cricket'; + context.jamClient.setMetronomeCricketTestState(isCricket); + } + function onMixerModeChanged(e, data) { $mixModeDropdown.easyDropDown('select', data.mode, true); setTimeout(renderSession, 1); @@ -2857,6 +2907,8 @@ $(document).on(EVENTS.MIXER_MODE_CHANGED, onMixerModeChanged) $mixModeDropdown.change(onUserChangeMixMode) $(document).on("change", ".metronome-select", onMetronomeChanged) + $metronomePlaybackSelect.metronomePlaybackMode().on(EVENTS.METRONOME_PLAYBACK_MODE_SELECTED, metronomePlaybackModeChanged) + context.JK.helpBubble($metronomePlaybackHelp, 'metromone-playback-modes', {} , {offsetParent: $screen, width:'400px'}); } this.initialize = function(localRecordingsDialogInstance, recordingFinishedDialogInstance, friendSelectorDialog) { @@ -2887,6 +2939,9 @@ $liveTracksContainer = $('#session-livetracks-container'); $closePlaybackRecording = $('#close-playback-recording') $openBackingTrack = $('#open-a-backingtrack'); + $metronomePlaybackSelect = $('#metronome-playback-select') + $metronomePlaybackHelp = $('#metronome-playback-help') + events(); diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index f1d7988ec..c23252017 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -131,15 +131,6 @@ } } - function metronomeActive() { - if(currentSession) { - return currentSession.metronome_active - } - else { - return null; - } - } - function creatorId() { if(!currentSession) { throw "creator is not known" @@ -806,8 +797,8 @@ // the way we know if backing tracks changes, or recordings are opened, is via this event. // but we want to report to the user when backing tracks change; so we need to detect change on our own - if(previousBackingTracks != backingTracks) { - logger.debug("backing tracks changed") + if(!(previousBackingTracks.length == 0 && backingTracks.length == 0) && previousBackingTracks != backingTracks) { + logger.debug("backing tracks changed", previousBackingTracks, backingTracks) syncTracks(backingTracks); } else { @@ -828,7 +819,6 @@ this.backingTrack = backingTrack; this.backingTracks = backingTracks; this.recordedBackingTracks = recordedBackingTracks; - this.metronomeActive = metronomeActive; this.setUserTracks = setUserTracks; this.recordedTracks = recordedTracks; this.jamTracks = jamTracks; diff --git a/web/app/assets/javascripts/trackHelpers.js b/web/app/assets/javascripts/trackHelpers.js index a08885af3..a77792a1c 100644 --- a/web/app/assets/javascripts/trackHelpers.js +++ b/web/app/assets/javascripts/trackHelpers.js @@ -33,8 +33,6 @@ getBackingTracks: function(jamClient) { var mediaTracks = context.JK.TrackHelpers.getTracks(jamClient, 4); - console.log("mediaTracks", mediaTracks) - var backingTracks = [] context._.each(mediaTracks, function(mediaTrack) { // the check for 'not managed' means this is not a track opened by a recording, basically diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css index 9e1899ce3..05239eac4 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -46,6 +46,7 @@ *= require dialogs/dialog *= require ./iconInstrumentSelect *= require ./muteSelect + *= require ./metronomePlaybackModeSelect *= require ./terms *= require ./createSession *= require ./feed diff --git a/web/app/assets/stylesheets/client/metronomePlaybackModeSelect.css.scss b/web/app/assets/stylesheets/client/metronomePlaybackModeSelect.css.scss new file mode 100644 index 000000000..776135e98 --- /dev/null +++ b/web/app/assets/stylesheets/client/metronomePlaybackModeSelect.css.scss @@ -0,0 +1,66 @@ +@import "client/common"; + +.metronome-playback-mode-selector-popup { + .bt-content { + width:160px; + background-color:#333; + overflow:auto; + border:1px solid #ED3618; + text-align:left; + font-family: Raleway, Arial, Helvetica, sans-serif; + ul { + height:100%; + margin-left:20px; + } + li { + font-size:12px; + margin-left:0; + list-style-type: none; + + margin-bottom:5px; + } + + p.please-select { + font-size:14px; + text-align:left; + margin-bottom:10px; + } + } +} + +#metronome-playback-select { + + margin-top:-10px; + span.metronome-state { + position:relative; + } + a { + color:#ffcc00 !important; + position:absolute; + top:4px; + right:-35px; + } + .down-arrow { + cursor:pointer; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 8px solid #fff; + position:absolute; + top:4px; + right:-20px; + } + + .up-arrow { + cursor:pointer; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid #fff; + position:absolute; + top:2px; + right:-20px; + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index 7f38661c6..116ba1e1a 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -23,6 +23,9 @@ margin-right:8px; position:relative; background-color:#242323; + -webkit-border-radius:4px; + -moz-border-radius:4px; + border-radius:4px; .disabled-track-overlay { width:100%; @@ -159,6 +162,27 @@ .download-jamtrack { margin-top:50px; } + + .metronome-playback-options { + height:100%; + line-height:100%; + vertical-align:middle; + + span.metronome-state { + cursor:pointer; + height:100%; + line-height:100%; + vertical-align:middle; + } + } + + #metronome-playback-help { + position:absolute; + right:5px; + top:5px; + color:#ffcc00; + cursor:help; + } } diff --git a/web/app/assets/stylesheets/easydropdown_jk.css.scss b/web/app/assets/stylesheets/easydropdown_jk.css.scss index 47e43c108..d3601b002 100644 --- a/web/app/assets/stylesheets/easydropdown_jk.css.scss +++ b/web/app/assets/stylesheets/easydropdown_jk.css.scss @@ -11,6 +11,54 @@ body.jam { } } + .dropdown-wrapper.black-flat { + li { + color:white; + + &.focus { + background-color: #ed3618; + color:white; + } + } + + ul { + background-color:#242323; + } + + div.dropdown-container { + border-width:0; + border-radius:0; + } + + div.dropdown { + background-color: #242323; + box-shadow: none; + border-radius:0; + border-width:0; + + .selected { + color:white; + } + + .carat { + border-top: 8px solid white; + } + li { + color:white; + + &.focus { + background-color: #ed3618; + color:white; + } + } + + div.after { + box-shadow:none; + } + } + + } + .dropdown-wrappper div.dropdown-container { width:auto; } @@ -174,4 +222,11 @@ body.jam div.dropdown { border-style:solid; border-width: 1px 0 0 1px; } + + .black-flat { + ul { + border-width:0; + background-color:black; + } + } } \ No newline at end of file diff --git a/web/app/assets/stylesheets/playbackControls.css.scss b/web/app/assets/stylesheets/playbackControls.css.scss index f0a0ab29a..a1e20c77f 100644 --- a/web/app/assets/stylesheets/playbackControls.css.scss +++ b/web/app/assets/stylesheets/playbackControls.css.scss @@ -1,5 +1,4 @@ .recording-controls { - .playback-mode-buttons { - display:none; - } + + } \ No newline at end of file diff --git a/web/app/assets/stylesheets/web/audioWidgets.css.scss b/web/app/assets/stylesheets/web/audioWidgets.css.scss index 55871e7ba..5a6fb3303 100644 --- a/web/app/assets/stylesheets/web/audioWidgets.css.scss +++ b/web/app/assets/stylesheets/web/audioWidgets.css.scss @@ -28,6 +28,19 @@ display:inline-block; white-space:nowrap; + &.metronome-mode { + width:200px; + .recording-position, .recording-current, .playback-mode-buttons { + display:none; + } + } + + &.jamtrack-mode, &.mediafile-mode { + .metronome-playback-options { + display:none; + } + } + .recording-status { font-size:15px; } diff --git a/web/app/views/clients/_help.html.slim b/web/app/views/clients/_help.html.slim index 60646b6ec..38fa7c4dd 100644 --- a/web/app/views/clients/_help.html.slim +++ b/web/app/views/clients/_help.html.slim @@ -211,3 +211,19 @@ script type="text/template" id="template-help-downloaded-jamtrack" p When a JamTrack is first purchased, a user-specific version of it is created on the server. Once it's ready, it's then downloaded to the client. p However, in some cases, you may need to download the JamTrack again (if you change machines, for instance). p If you do not currently have it and try to open it now, we will try to download it immediately. + +script type="text/template" id="template-help-metronome-unstable" + .metronome-unstable + span.definition Background + p The metronome feature requires that every user's computer in the session must agree on the current time. + span.definition The Problem + p The computers of {{data.names}} have not successfully synchronized to the current time. + span.definition Solution + p The JamKazam service is trying to automatically correct this error condition. Please close this message, wait about 10 seconds, and then try opening the metronome again. If this problem persists after a couple of attempts, we recommend that the unsynchronized users restart the JamKazam application. + p If this error persists after a restart, please have the users with the issue contact support@jamkazam.com. + +script type="text/template" id="template-help-metromone-playback-modes" + .metromone-playback-modes + p The metronome plays a local metronome back to each musician locally, with the local metronomes all synchronized to a global clock with high precision. + + p The cluster test mixes playback of your local metronome with the streamed audio of all other musician metronomes. This will give you a good sense of the audible latency in your session. If all the metronome sounds are tightly clustered, there is low latency. If not, it will be more difficult to play in sync. diff --git a/web/app/views/clients/_metronome_playback_mode.slim b/web/app/views/clients/_metronome_playback_mode.slim new file mode 100644 index 000000000..9bf737759 --- /dev/null +++ b/web/app/views/clients/_metronome_playback_mode.slim @@ -0,0 +1,8 @@ +script type='text/template' id='template-metronome-playback-mode' + p.please-select Please select one: + ul + li data-playback-option="self" + a href='#' - Play metronome + + li data-playback-option="cricket" + a href='#' - Play cluster test diff --git a/web/app/views/clients/_play_controls.html.erb b/web/app/views/clients/_play_controls.html.erb index 88e088018..535e25f07 100644 --- a/web/app/views/clients/_play_controls.html.erb +++ b/web/app/views/clients/_play_controls.html.erb @@ -31,5 +31,9 @@ +
\ No newline at end of file diff --git a/web/app/views/clients/_session.html.slim b/web/app/views/clients/_session.html.slim index 49a5b2cb0..a873900fa 100644 --- a/web/app/views/clients/_session.html.slim +++ b/web/app/views/clients/_session.html.slim @@ -127,9 +127,10 @@ script#template-session-track[type="text/template"] .disabled-track-overlay .metronome-selects.hidden select.metronome-select.metro-sound title="Metronome Sound" - option.label value="Beep" Bleep - option.label value="Click" Click - option.label value="Snare" Drum + option.label value="Beep" Knock + option.label value="Click" Tap + option.label value="Snare" Snare + option.label value="Kick" Kick br select.metronome-select.metro-tempo title="Metronome Tempo" - metronome_tempos.each do |t| diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 7586d0e9b..3f1ad8a1b 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -19,6 +19,7 @@ <%= render "jamServer" %> <%= render "iconInstrumentSelect" %> <%= render "muteSelect" %> +<%= render "metronome_playback_mode" %> <%= render "clients/wizard/buttons" %> <%= render "clients/wizard/gear/gear_wizard" %> <%= render "clients/wizard/loopback/loopback_wizard" %> diff --git a/web/spec/controllers/api_jam_tracks_controller_spec.rb b/web/spec/controllers/api_jam_tracks_controller_spec.rb index f22fb23f9..831b111c5 100644 --- a/web/spec/controllers/api_jam_tracks_controller_spec.rb +++ b/web/spec/controllers/api_jam_tracks_controller_spec.rb @@ -17,7 +17,7 @@ describe ApiJamTracksController do JamTrack.destroy_all @user = FactoryGirl.create(:user) @jam_track = FactoryGirl.create(:jam_track) - @jam_track_unavailable = FactoryGirl.create(:jam_track, :available=>false) + @jam_track_unavailable = FactoryGirl.create(:jam_track, :status=>'Staging') controller.current_user = @user end @@ -35,7 +35,7 @@ describe ApiJamTracksController do json["jamtracks"].length.should == 2 # Create another unavailable track and see: - jam_track2 = FactoryGirl.create(:jam_track, :available=>false) + jam_track2 = FactoryGirl.create(:jam_track, :status => 'Staging') get :index response.should be_success json = JSON.parse(response.body) diff --git a/web/vendor/assets/javascripts/jquery.easydropdown.js b/web/vendor/assets/javascripts/jquery.easydropdown.js index 4520917b1..1354daed8 100644 --- a/web/vendor/assets/javascripts/jquery.easydropdown.js +++ b/web/vendor/assets/javascripts/jquery.easydropdown.js @@ -90,9 +90,12 @@ self.$container = self.$select.wrap('