Squashed commit of the following:
* VRFS-2881 use backend audio source info in metronome * VRFS-2832 add metronome play controls with mode control * VRFS-2873 straighten out metronome names in frontend * VRFS-2782 use getMyNetworkState for ntp_stable determination * VRFS-2864 allow 'cluster' test, a new metronome mode * VRFS-2861 metronome sounds not always matching frontend * VRFS-2852 relax idea that only one person can open metronome * VRFS-2865 metronome sounds not always matching frontend(dup) * VRFS-2847 metronome volume now moves master and personal both * VRFS-2835 metronome 'prevent' message is now dialog, not notify * VRFS-2833 anyone can control volume/mute of metronome * VRFS-2850 don't prevent metronome if solo session
This commit is contained in:
parent
9415593244
commit
aba4546e44
|
|
@ -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
|
||||
|
|
@ -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' }
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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("<span class='metronome-state'>Play cluster test<span class='down-arrow'></span></span>")
|
||||
}
|
||||
else {
|
||||
$target.html("<span class='metronome-state'>Play metronome<span class='down-arrow'></span></span>")
|
||||
}
|
||||
}
|
||||
|
||||
$.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);
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@
|
|||
*= require dialogs/dialog
|
||||
*= require ./iconInstrumentSelect
|
||||
*= require ./muteSelect
|
||||
*= require ./metronomePlaybackModeSelect
|
||||
*= require ./terms
|
||||
*= require ./createSession
|
||||
*= require ./feed
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
.recording-controls {
|
||||
.playback-mode-buttons {
|
||||
display:none;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -31,5 +31,9 @@
|
|||
<input type="radio" name="playback-mode" value="preview-to-me" class="preview-to-me" /><label for="playback-mode-preview-me" class="radio-text">Preview Only to Me</label>
|
||||
</div>
|
||||
|
||||
<div class="metronome-playback-options">
|
||||
<span id="metronome-playback-select"></span>
|
||||
<span id="metronome-playback-help">?</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end recording play controls -->
|
||||
|
|
@ -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|
|
||||
|
|
|
|||
|
|
@ -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" %>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -90,9 +90,12 @@
|
|||
|
||||
self.$container = self.$select.wrap('<div class="'+self.wrapperClass+touchClass+disabledClass+'"><span class="old"/></div>').parent().parent();
|
||||
|
||||
self.$containerWrapper = self.$container.wrap('<div class="dropdown-wrapper ' + self.wrapperWrapperClass + '"></div>').parent();
|
||||
self.$containerWrapper = self.$container.wrap('<div class="dropdown-wrapper ' + self.wrapperWrapperClass + '"></div>').parent();
|
||||
|
||||
self.$active = $('<span class="selected">'+self.selected.title+'</span>').appendTo(self.$container);
|
||||
// copy forward special classes on the easydropdown wrapper
|
||||
self.$containerWrapper.addClass(self.$select.attr('data-style'))
|
||||
|
||||
self.$active = $('<span class="selected">'+self.selected.title+'</span>').appendTo(self.$container);
|
||||
self.$carat = $('<span class="carat"/>').appendTo(self.$container);
|
||||
//self.$scrollWrapper = $('<div><ul/></div>').appendTo(self.$container);
|
||||
self.$scrollWrapper = $('<div class="dropdown-container"><ul/></div>').appendTo(self.$containerWrapper);
|
||||
|
|
|
|||
Loading…
Reference in New Issue