@pro-gram/flux 1.6.1 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -3
- package/bin/flux.js +1 -1
- package/dependencies.json +15 -15
- package/package.json +61 -60
- package/payload/DrumBuddy-Setup.exe +2 -2
- package/source/AssetBootstrap.cs +1 -1
- package/source/Audio Decoders.txt +1 -1
- package/source/Audio.cs +27 -5
- package/source/Build Instructions.txt +9 -1
- package/source/Build.ps1 +7 -1
- package/source/EditorCompactLayout.cs +1 -1
- package/source/EditorMixer.cs +267 -0
- package/source/EditorMixerTests.cs +159 -0
- package/source/EditorPlaybackTests.cs +22 -0
- package/source/EditorRecording.cs +106 -0
- package/source/EditorView.cs +49 -25
- package/source/InterfaceSettings.cs +11 -4
- package/source/Main.cs +110 -53
- package/source/Main.xaml +2 -1
- package/source/MediaPlayerView.cs +1 -1
- package/source/Quick Start.txt +25 -4
- package/source/RecordingSession.cs +59 -0
- package/source/RecordingTests.cs +143 -0
- package/source/Setup.cs +6 -5
- package/source/ShellStyles.xaml +11 -5
- package/source/TempoGrid.cs +15 -1
- package/source/TempoGridTests.cs +40 -0
- package/source/Third Party Notices.txt +1 -1
- package/source/Timeline.cs +39 -13
- package/source/TimelineMixer.cs +44 -0
- package/source/references/R16 Format Research.txt +0 -16
package/source/EditorView.cs
CHANGED
|
@@ -110,6 +110,7 @@ namespace DrumBuddy
|
|
|
110
110
|
var editing = new WrapPanel(); tools.Children.Add(editing);
|
|
111
111
|
recordButton = WorkspaceUi.Button("Record", delegate { if (RecordRequested != null) RecordRequested(); }); recordButton.Background = WorkspaceUi.Color("#B52C36"); recordButton.Foreground = System.Windows.Media.Brushes.White; editing.Children.Add(recordButton);
|
|
112
112
|
recordButton.SetResourceReference(Control.ForegroundProperty, "ThemeOnAccent");
|
|
113
|
+
editing.Children.Add(WorkspaceUi.Button("Mixer", delegate { ToggleMixer(); }));
|
|
113
114
|
editing.Children.Add(WorkspaceUi.Button("Record options", delegate { ShowRecordingOptions(!recordingPane.IsPaneVisible); }));
|
|
114
115
|
undoButton = WorkspaceUi.Button("Undo", delegate { Undo(false); }); editing.Children.Add(undoButton);
|
|
115
116
|
redoButton = WorkspaceUi.Button("Redo", delegate { Undo(true); }); editing.Children.Add(redoButton);
|
|
@@ -119,6 +120,7 @@ namespace DrumBuddy
|
|
|
119
120
|
separate = new CheckBox { Content = "Import files on separate tracks", IsChecked = true, FontSize = 14, Margin = new Thickness(4, 0, 0, 8), VerticalAlignment = VerticalAlignment.Center }; editing.Children.Add(separate);
|
|
120
121
|
compactOptionsButton = WorkspaceUi.Button("Audio / export options", delegate { compactOptions = !compactOptions; UpdateCompactLayout(); }); editing.Children.Add(compactOptionsButton);
|
|
121
122
|
|
|
123
|
+
InitializeRecordingFeedback(tools);
|
|
122
124
|
gridControls = new WrapPanel { Margin = new Thickness(0, 0, 0, 3) }; tools.Children.Add(gridControls);
|
|
123
125
|
gridControls.Children.Add(WorkspaceUi.Text("BPM ", 12));
|
|
124
126
|
tempoBox = new TextBox { Width = 60, MinHeight = 28, Padding = new Thickness(5, 3, 5, 3), Margin = new Thickness(0, 0, 9, 5) }; gridControls.Children.Add(tempoBox);
|
|
@@ -202,10 +204,11 @@ namespace DrumBuddy
|
|
|
202
204
|
var transport = new WrapPanel(); bottom.Children.Add(transport);
|
|
203
205
|
play = WorkspaceUi.Button("Play", delegate { PlayPause(); }); play.MinWidth = 90; transport.Children.Add(play);
|
|
204
206
|
transport.Children.Add(WorkspaceUi.Button("Stop", delegate { if (recording && StopRequested != null) StopRequested(); else { Stop(); UpdateTransport(); } }));
|
|
205
|
-
transport.Children.Add(WorkspaceUi.Button("To start", delegate { Stop(); playhead = 0; DrawCanvas(); EnsurePlayheadVisible(); }));
|
|
207
|
+
transport.Children.Add(WorkspaceUi.Button("To start", delegate { if (IsBusy) return; Stop(); playhead = 0; DrawCanvas(); EnsurePlayheadVisible(); }));
|
|
206
208
|
positionText = WorkspaceUi.Text("00:00.00 / 00:00.00", 16); positionText.FontFamily = new FontFamily("Consolas"); positionText.Margin = new Thickness(0, 0, 14, 8); transport.Children.Add(positionText);
|
|
207
|
-
transport.Children.Add(WorkspaceUi.Text("
|
|
209
|
+
transport.Children.Add(WorkspaceUi.Text("Monitor volume ", 14));
|
|
208
210
|
volume = new Slider { Minimum = 0, Maximum = 100, Value = 75, Width = 120, Margin = new Thickness(0, 0, 16, 8), VerticalAlignment = VerticalAlignment.Center };
|
|
211
|
+
volume.ToolTip = "Listening volume only. Mixer track and master faders also affect exported audio.";
|
|
209
212
|
volume.ValueChanged += delegate { output.Volume((float)(volume.Value / 100)); }; transport.Children.Add(volume);
|
|
210
213
|
outputPicker = new AudioOutputPicker(delegate { Stop(); }, status); bottom.Children.Add(outputPicker);
|
|
211
214
|
var exports = new WrapPanel(); bottom.Children.Add(exports);
|
|
@@ -217,10 +220,11 @@ namespace DrumBuddy
|
|
|
217
220
|
AddFileButton(exports, "Export mix…", async delegate { await ExportMix(); });
|
|
218
221
|
ArrangeCompactChrome(top, editing, range, viewControls, zoomTools, bottom, transport, exports);
|
|
219
222
|
timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(80) };
|
|
220
|
-
timer.Tick += delegate { if (output.Playing || output.Paused) { playhead = (long)Math.Round(output.Position * Audio.Rate); UpdateTransport(); UpdatePlayLine(); if (output.Playing && DateTime.UtcNow >= manualViewUntil) EnsurePlayheadVisible(); } }; timer.Start();
|
|
223
|
+
timer.Tick += delegate { if (output.Playing || output.Paused) { playhead = (long)Math.Round(output.Position * Audio.Rate); UpdateTransport(); UpdatePlayLine(); if (output.Playing && DateTime.UtcNow >= manualViewUntil) EnsurePlayheadVisible(); } TickMixerMeters(); }; timer.Start();
|
|
221
224
|
waveformDetail = new WaveformDetail(Dispatcher, delegate { if (!disposed) DrawCanvas(); });
|
|
222
225
|
UpdateSelectionText(); BuildHeaders(); DrawCanvas();
|
|
223
|
-
|
|
226
|
+
mixerPane = new DockablePane(editorBody, CreateMixer(), "Mixer", true, false, 360) { MinimumMainHeight = 220 };
|
|
227
|
+
recordingPane = new DockablePane(mixerPane, null, "Record options", false, false, 260) { MinimumMainHeight = 300 }; Children.Add(recordingPane);
|
|
224
228
|
editorBody.SizeChanged += delegate { UpdateCompactLayout(); };
|
|
225
229
|
}
|
|
226
230
|
|
|
@@ -413,21 +417,23 @@ namespace DrumBuddy
|
|
|
413
417
|
for (int i = 0; i < 16; i++) {
|
|
414
418
|
int index = i; var state = session.Tracks[i];
|
|
415
419
|
var panel = new StackPanel { Margin = new Thickness(7, 3, 7, 3) };
|
|
416
|
-
var name = WorkspaceUi.Text((i + 1).ToString("00") + " " + state.Name, 14); name.TextWrapping = TextWrapping.NoWrap; name.TextTrimming = TextTrimming.CharacterEllipsis; name.FontWeight = FontWeights.SemiBold; panel.Children.Add(name);
|
|
420
|
+
var name = WorkspaceUi.Text((i + 1).ToString("00") + (RecordingTrack(i) ? (captureRunning ? " REC " : " ARM ") : " ") + state.Name, 14); name.TextWrapping = TextWrapping.NoWrap; name.TextTrimming = TextTrimming.CharacterEllipsis; name.FontWeight = FontWeights.SemiBold; panel.Children.Add(name);
|
|
421
|
+
if (RecordingTrack(i)) name.Foreground = WorkspaceUi.Color("#B52C36");
|
|
417
422
|
var strip = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 0) }; panel.Children.Add(strip);
|
|
418
423
|
var mute = new ToggleButton { Content = "M", IsChecked = state.Mute, Width = 28, Height = 26, FontSize = 12, Margin = new Thickness(0, 0, 4, 0), ToolTip = "Mute track " + (i + 1) };
|
|
419
|
-
mute.Click += delegate { if (!updating)
|
|
424
|
+
mute.Click += delegate { if (!updating) SetMixerMute(index, mute.IsChecked == true); }; strip.Children.Add(mute); headerMutes[i] = mute;
|
|
420
425
|
var solo = new ToggleButton { Content = "S", IsChecked = state.Solo, Width = 28, Height = 26, FontSize = 12, Margin = new Thickness(0, 0, 6, 0), ToolTip = "Solo track " + (i + 1) };
|
|
421
|
-
solo.Click += delegate { if (!updating)
|
|
422
|
-
var levels = new[] { -60.0, -24, -18, -12, -6, 0, 3, 6, 12, state.GainDb }.Distinct().OrderBy(v => v).ToArray();
|
|
426
|
+
solo.Click += delegate { if (!updating) SetMixerSolo(index, solo.IsChecked == true); }; strip.Children.Add(solo); headerSolos[i] = solo;
|
|
427
|
+
var levels = new[] { -60.0, -24, -18, -12, -6, 0, 3, 6, 12, 18, 24, state.GainDb }.Distinct().OrderBy(v => v).ToArray();
|
|
423
428
|
var level = new ComboBox { ItemsSource = levels.Select(v => v.ToString("0.#", CultureInfo.InvariantCulture) + " dB").ToArray(), SelectedIndex = Array.IndexOf(levels, state.GainDb), Width = 102, MinHeight = 26, Height = 26, FontSize = 12, Padding = new Thickness(3, 0, 3, 0), ToolTip = "Track playback / export level" };
|
|
424
|
-
level.SelectionChanged += delegate { if (!updating && level.
|
|
425
|
-
var border = new Border { Height = RowHeight, BorderThickness = new Thickness(0, 0, 1, 1), BorderBrush = WorkspaceUi.Color("#D6E1DF"), Background = WorkspaceUi.Color(i == track ? "#DCEFEB" : "#EDF3F1"), Child = panel };
|
|
429
|
+
level.SelectionChanged += delegate { if (!updating && level.SelectedItem != null) { double value; if (TempoGrid.TryTempo(level.SelectedItem.ToString().Replace(" dB", ""), out value)) SetMixerGain(index, value); } }; strip.Children.Add(level); headerLevels[i] = level;
|
|
430
|
+
var border = new Border { Height = RowHeight, BorderThickness = new Thickness(RecordingTrack(i) ? 3 : 0, 0, 1, 1), BorderBrush = WorkspaceUi.Color(RecordingTrack(i) ? "#B52C36" : "#D6E1DF"), Background = WorkspaceUi.Color(i == track ? "#DCEFEB" : "#EDF3F1"), Child = panel };
|
|
426
431
|
border.PreviewMouseLeftButtonDown += delegate { if (!IsBusy && track != index) { track = index; UpdateSelectionText(); } };
|
|
427
432
|
border.MouseLeftButtonUp += delegate { BuildHeaders(); DrawCanvas(); };
|
|
428
433
|
headers.Children.Add(border);
|
|
429
434
|
}
|
|
430
435
|
updating = false;
|
|
436
|
+
RefreshMixerControls();
|
|
431
437
|
}
|
|
432
438
|
private void DrawCanvas()
|
|
433
439
|
{
|
|
@@ -451,10 +457,12 @@ namespace DrumBuddy
|
|
|
451
457
|
if (tick.Label != null) { if (x >= left && x < left + ViewportWidth) visibleLabel = true; var label = WorkspaceUi.Text(tick.Label, 11); label.IsHitTestVisible = false; Canvas.SetLeft(label, x + 4); Canvas.SetTop(label, 3); timeline.Children.Add(label); }
|
|
452
458
|
}
|
|
453
459
|
if (!session.Grid.MusicalRuler || !visibleLabel) DrawTimeRuler(left, right);
|
|
460
|
+
DrawRecordingOverlays();
|
|
454
461
|
timeline.Children.Add(selectionOverlay); timeline.Children.Add(selectionStartLine); timeline.Children.Add(selectionEndLine); timeline.Children.Add(selectionStartHandle); timeline.Children.Add(selectionEndHandle); UpdateSelectionOverlay();
|
|
455
462
|
playLine.Y1 = 0; playLine.Y2 = timeline.Height; timeline.Children.Add(playLine); UpdatePlayLine(); UpdateTransport();
|
|
456
463
|
undoButton.IsEnabled = !IsBusy && undo.Count > 0; redoButton.IsEnabled = !IsBusy && redo.Count > 0;
|
|
457
464
|
title.Text = session.Name + (dirty ? " *" : "");
|
|
465
|
+
RefreshMixerControls();
|
|
458
466
|
}
|
|
459
467
|
private void UpdateSelectionOverlay()
|
|
460
468
|
{
|
|
@@ -517,13 +525,13 @@ namespace DrumBuddy
|
|
|
517
525
|
}
|
|
518
526
|
private void UpdatePlayLine() { playLine.X1 = playLine.X2 = X(playhead); }
|
|
519
527
|
private void UpdateTransport()
|
|
520
|
-
{ positionText.Text = WorkspaceUi.Clock(playhead / (double)Audio.Rate) + " / " + WorkspaceUi.Clock(session.End / (double)Audio.Rate); }
|
|
528
|
+
{ positionText.Text = WorkspaceUi.Clock(playhead / (double)Audio.Rate) + " / " + WorkspaceUi.Clock(Math.Max(session.End, recording ? playhead : 0) / (double)Audio.Rate); }
|
|
521
529
|
private void EnsurePlayheadVisible()
|
|
522
530
|
{ double x = X(playhead); if (x < scroll.HorizontalOffset || x > scroll.HorizontalOffset + scroll.ViewportWidth - 15) scroll.ScrollToHorizontalOffset(Math.Max(0, x - 30)); }
|
|
523
531
|
|
|
524
532
|
private void Change(Func<bool> action, string message)
|
|
525
533
|
{
|
|
526
|
-
if (IsBusy) return; Stop(); var before = session.Copy();
|
|
534
|
+
if (IsBusy) return; FinishMixerGesture(); Stop(); var before = session.Copy();
|
|
527
535
|
var oldIds = selectedIds.ToArray(); bool oldMode = clipSelection; long oldFrom = from, oldTo = to; int oldTrack = track;
|
|
528
536
|
Action restore = delegate { session = before; selectedIds.Clear(); foreach (string id in oldIds) selectedIds.Add(id); clipSelection = oldMode; from = oldFrom; to = oldTo; track = oldTrack; UpdateSelectionText(); BuildHeaders(); DrawCanvas(); };
|
|
529
537
|
try {
|
|
@@ -535,7 +543,7 @@ namespace DrumBuddy
|
|
|
535
543
|
private void Undo(bool forward)
|
|
536
544
|
{
|
|
537
545
|
if (IsBusy) return; var source = forward ? redo : undo; var target = forward ? undo : redo;
|
|
538
|
-
if (source.Count == 0) return; Stop(); target.Push(session.Copy()); session = source.Pop(); dirty = true; RefreshClipBounds(); BuildHeaders(); DrawCanvas();
|
|
546
|
+
if (source.Count == 0) return; FinishMixerGesture(); Stop(); target.Push(session.Copy()); session = source.Pop(); dirty = true; RefreshClipBounds(); BuildHeaders(); DrawCanvas();
|
|
539
547
|
}
|
|
540
548
|
private void Split() { Change(() => TimelineEdits.Split(session, track, AllTracks, playhead, clipSelection ? selectedIds : null), "Split at the playhead."); }
|
|
541
549
|
private void Copy(bool cut)
|
|
@@ -594,16 +602,16 @@ namespace DrumBuddy
|
|
|
594
602
|
finally { EndWork(); BuildHeaders(); DrawCanvas(); timeline.Focus(); }
|
|
595
603
|
}
|
|
596
604
|
private void BeginWork()
|
|
597
|
-
{ operation = new CancellationTokenSource(); foreach (var button in fileButtons) button.IsEnabled = false; play.IsEnabled = false; cancel.Visibility = Visibility.Visible; RefreshGridToolbar(); }
|
|
605
|
+
{ operation = new CancellationTokenSource(); FinishMixerGesture(); foreach (var button in fileButtons) button.IsEnabled = false; play.IsEnabled = false; cancel.Visibility = Visibility.Visible; RefreshGridToolbar(); RefreshMixerControls(); }
|
|
598
606
|
private void EndWork()
|
|
599
|
-
{ operation.Dispose(); operation = null; foreach (var button in fileButtons) button.IsEnabled = true; play.IsEnabled = true; cancel.Visibility = Visibility.Collapsed; RefreshGridToolbar(); }
|
|
607
|
+
{ operation.Dispose(); operation = null; foreach (var button in fileButtons) button.IsEnabled = true; play.IsEnabled = true; cancel.Visibility = Visibility.Collapsed; RefreshGridToolbar(); RefreshMixerControls(); }
|
|
600
608
|
private void NewSession()
|
|
601
609
|
{
|
|
602
|
-
if (IsBusy || !ConfirmClose()) return; Stop(); session = new EditorSession { Grid = InterfacePreferences.Current.Grid.Copy() }; sessionPath = null; assets.Clear(); undo.Clear(); redo.Clear(); dirty = false; selectedIds.Clear(); clipSelection = false; from = to = playhead = 0; UpdateSelectionText(); BuildHeaders(); DrawCanvas();
|
|
610
|
+
if (IsBusy || !ConfirmClose()) return; FinishMixerGesture(); Stop(); session = new EditorSession { Grid = InterfacePreferences.Current.Grid.Copy() }; ResetMixerMeters(); sessionPath = null; assets.Clear(); undo.Clear(); redo.Clear(); dirty = false; selectedIds.Clear(); clipSelection = false; from = to = playhead = 0; UpdateSelectionText(); BuildHeaders(); DrawCanvas();
|
|
603
611
|
}
|
|
604
612
|
public bool SaveSession(bool saveAs)
|
|
605
613
|
{
|
|
606
|
-
if (IsBusy) return false;
|
|
614
|
+
if (IsBusy) return false; FinishMixerGesture();
|
|
607
615
|
string path = sessionPath;
|
|
608
616
|
if (saveAs || string.IsNullOrEmpty(path)) {
|
|
609
617
|
var dialog = new Microsoft.Win32.SaveFileDialog { Title = "Save the editor session", Filter = "Flux editor session|*.dbedit", DefaultExt = ".dbedit", FileName = session.Name == "Untitled session" ? "Practice.dbedit" : session.Name + ".dbedit" };
|
|
@@ -627,7 +635,7 @@ namespace DrumBuddy
|
|
|
627
635
|
decoded.Add(path, clip);
|
|
628
636
|
}
|
|
629
637
|
using (var verify = new TimelineMix(loaded, decoded)) { }
|
|
630
|
-
operation.Token.ThrowIfCancellationRequested(); session = loaded; assets.Clear(); foreach (var pair in decoded) assets.Add(pair.Key, pair.Value);
|
|
638
|
+
operation.Token.ThrowIfCancellationRequested(); session = loaded; ResetMixerMeters(); assets.Clear(); foreach (var pair in decoded) assets.Add(pair.Key, pair.Value);
|
|
631
639
|
sessionPath = dialog.FileName; undo.Clear(); redo.Clear(); dirty = false; selectedIds.Clear(); clipSelection = false; from = to = playhead = 0; UpdateSelectionText(); status("Editor session opened.");
|
|
632
640
|
}
|
|
633
641
|
catch (OperationCanceledException) { status("Opening cancelled. Your current timeline is kept."); }
|
|
@@ -671,6 +679,15 @@ namespace DrumBuddy
|
|
|
671
679
|
if (output.Playing || output.Paused) playhead = (long)Math.Round(output.Position * Audio.Rate);
|
|
672
680
|
output.Stop(); if (mix != null) { mix.Dispose(); mix = null; } play.Content = "Play";
|
|
673
681
|
}
|
|
682
|
+
private void JumpBar(int direction)
|
|
683
|
+
{
|
|
684
|
+
if (!CommitTempo()) return;
|
|
685
|
+
bool playing = output.Playing, paused = output.Paused;
|
|
686
|
+
Stop(); playhead = TempoGrid.AdjacentBar(playhead, session.Grid, direction);
|
|
687
|
+
DrawCanvas(); EnsurePlayheadVisible();
|
|
688
|
+
if (playing && playhead < session.End) PlayPause();
|
|
689
|
+
else if (paused) play.Content = "Resume";
|
|
690
|
+
}
|
|
674
691
|
public void AttachRecordingPanel(FrameworkElement panel)
|
|
675
692
|
{
|
|
676
693
|
panel.Margin = new Thickness(10); recordingPane.SetContent(panel);
|
|
@@ -685,12 +702,15 @@ namespace DrumBuddy
|
|
|
685
702
|
internal DockablePane RecordingPaneForTest { get { return recordingPane; } }
|
|
686
703
|
public void SetRecordingState(bool active)
|
|
687
704
|
{
|
|
688
|
-
recording = active; recordButton.Content = active ? "Stop recording" : "Record";
|
|
705
|
+
recording = active; recordButton.Content = active ? "Stop recording" : "Record"; recordButton.IsEnabled = true; play.IsEnabled = !active;
|
|
706
|
+
playLine.Stroke = WorkspaceUi.Color(active ? "#B52C36" : "#D0772C");
|
|
707
|
+
FinishMixerGesture(); RefreshMixerControls();
|
|
689
708
|
RefreshGridToolbar();
|
|
690
709
|
foreach (var button in fileButtons) button.IsEnabled = !active && operation == null;
|
|
691
|
-
if (active) { Stop(); ShowRecordingOptions(true); }
|
|
710
|
+
if (active) { Stop(); ResetMixerMeters(); UpdateRecordingInputMeters(new float[8]); ShowRecordingOptions(true); }
|
|
711
|
+
else { recordingPlan = null; captureRunning = false; recordingBanner.Visibility = Visibility.Collapsed; BuildHeaders(); DrawCanvas(); }
|
|
692
712
|
}
|
|
693
|
-
public async Task AddRecordedFiles(IEnumerable<string> paths, long at, int firstTrack)
|
|
713
|
+
public async Task AddRecordedFiles(IEnumerable<string> paths, long at, int firstTrack, bool keepPlayhead = false)
|
|
694
714
|
{
|
|
695
715
|
if (IsBusy) throw new IOException("Finish the editor operation before adding the take.");
|
|
696
716
|
var files = paths.Where(File.Exists).ToArray();
|
|
@@ -706,17 +726,19 @@ namespace DrumBuddy
|
|
|
706
726
|
if (session.Tracks[index].Name == "Track " + (index + 1)) session.Tracks[index].Name = Path.GetFileNameWithoutExtension(clip.Name);
|
|
707
727
|
}
|
|
708
728
|
try { session.Validate(); } catch { session = before; throw; }
|
|
709
|
-
undo.Push(before); redo.Clear(); dirty = true; track = firstTrack;
|
|
729
|
+
undo.Push(before); redo.Clear(); dirty = true; track = firstTrack;
|
|
730
|
+
playhead = keepPlayhead ? Math.Min(EditorSession.MaxFrame, at + decoded.Max(c => (long)Math.Round(c.Duration * Audio.Rate))) : at;
|
|
710
731
|
}
|
|
711
732
|
finally { EndWork(); BuildHeaders(); DrawCanvas(); }
|
|
712
733
|
}
|
|
713
734
|
public async Task<bool> ExecuteAction(string action)
|
|
714
735
|
{
|
|
715
736
|
if (action == "selectTool" && clipDrag != ClipDrag.None) CancelClipDrag();
|
|
716
|
-
if (IsBusy && action != "stop" && !action.StartsWith("zoom", StringComparison.Ordinal)) return false;
|
|
737
|
+
if (IsBusy && action != "stop" && action != "mixer" && !action.StartsWith("zoom", StringComparison.Ordinal)) return false;
|
|
717
738
|
if (ExecuteClipAction(action)) return true;
|
|
718
739
|
switch (action) {
|
|
719
740
|
case "play": PlayPause(); break;
|
|
741
|
+
case "mixer": ToggleMixer(); break;
|
|
720
742
|
case "stop": Stop(); UpdateTransport(); break;
|
|
721
743
|
case "split": Split(); break;
|
|
722
744
|
case "selectTool": SetPointerTool("select"); break;
|
|
@@ -733,7 +755,9 @@ namespace DrumBuddy
|
|
|
733
755
|
case "new": NewSession(); break;
|
|
734
756
|
case "import": await ChooseAudio(); break;
|
|
735
757
|
case "export": await ExportMix(); break;
|
|
736
|
-
case "start": Stop(); playhead = 0; DrawCanvas(); EnsurePlayheadVisible(); break;
|
|
758
|
+
case "start": case "startAlternate": Stop(); playhead = 0; DrawCanvas(); EnsurePlayheadVisible(); break;
|
|
759
|
+
case "previousBar": JumpBar(-1); break;
|
|
760
|
+
case "nextBar": JumpBar(1); break;
|
|
737
761
|
case "end": Stop(); playhead = session.End; DrawCanvas(); EnsurePlayheadVisible(); break;
|
|
738
762
|
case "zoomIn": case "zoomInPlus": case "zoomInKeypad": ZoomStep(2); break;
|
|
739
763
|
case "zoomOut": case "zoomOutKeypad": ZoomStep(.5); break;
|
|
@@ -762,7 +786,7 @@ namespace DrumBuddy
|
|
|
762
786
|
return choice == MessageBoxResult.No || (choice == MessageBoxResult.Yes && SaveSession(false));
|
|
763
787
|
}
|
|
764
788
|
internal void SetForTest(EditorSession state, IDictionary<string, Clip> decoded)
|
|
765
|
-
{ session = state.Copy(); assets.Clear(); foreach (var p in decoded) assets.Add(p.Key, p.Value); undo.Clear(); redo.Clear(); dirty = false; selectedIds.Clear(); clipSelection = false; from = to = playhead = 0; UpdateSelectionText(); BuildHeaders(); DrawCanvas(); }
|
|
789
|
+
{ session = state.Copy(); ResetMixerMeters(); assets.Clear(); foreach (var p in decoded) assets.Add(p.Key, p.Value); undo.Clear(); redo.Clear(); dirty = false; selectedIds.Clear(); clipSelection = false; from = to = playhead = 0; UpdateSelectionText(); BuildHeaders(); DrawCanvas(); }
|
|
766
790
|
internal EditorSession SessionForTest { get { return session.Copy(); } }
|
|
767
791
|
internal void ListenVolumeForTest(double value) { volume.Value = value; }
|
|
768
792
|
internal bool PlayingForTest { get { return output.Playing; } }
|
|
@@ -29,7 +29,7 @@ namespace DrumBuddy
|
|
|
29
29
|
if ((mods & ModifierKeys.Alt) != 0) text += "Alt+";
|
|
30
30
|
if ((mods & ModifierKeys.Shift) != 0) text += "Shift+";
|
|
31
31
|
if ((mods & ModifierKeys.Windows) != 0) text += "Win+";
|
|
32
|
-
return text + (Key == "Back" ? "Backspace" : Key == "OemComma" ? "," : Key == "OemPlus" ? "=" : Key == "OemMinus" ? "-" : Key.StartsWith("D") && Key.Length == 2 && char.IsDigit(Key[1]) ? Key.Substring(1) : Key);
|
|
32
|
+
return text + (Key == "Return" || Key == "Enter" ? "Enter" : Key == "Back" ? "Backspace" : Key == "OemComma" ? "," : Key == "OemPlus" ? "=" : Key == "OemMinus" ? "-" : Key.StartsWith("D") && Key.Length == 2 && char.IsDigit(Key[1]) ? Key.Substring(1) : Key);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -60,6 +60,8 @@ namespace DrumBuddy
|
|
|
60
60
|
{ "undo", "Undo" }, { "redo", "Redo" }, { "save", "Save session" }, { "saveAs", "Save session as" },
|
|
61
61
|
{ "open", "Open session" }, { "new", "New session" }, { "import", "Import audio" }, { "export", "Export mix" },
|
|
62
62
|
{ "start", "Go to start" }, { "end", "Go to end" }, { "home", "Open Home" }, { "editor", "Open Editor" },
|
|
63
|
+
{ "startAlternate", "Go to start (alternate)" }, { "previousBar", "Go to previous bar" }, { "nextBar", "Go to next bar" },
|
|
64
|
+
{ "mixer", "Show / hide Editor mixer" },
|
|
63
65
|
{ "converter", "Open Converter" }, { "player", "Open Media Player" }, { "settings", "Open Settings" }
|
|
64
66
|
, { "timelineSettings", "Open Timeline settings" }, { "zoomIn", "Editor zoom in" }, { "zoomInPlus", "Editor zoom in (+ key)" }, { "zoomInKeypad", "Editor zoom in (keypad)" }, { "zoomOut", "Editor zoom out" }, { "zoomOutKeypad", "Editor zoom out (keypad)" }, { "zoomFit", "Fit project in Editor" }, { "zoomSelection", "Fit selection in Editor" }
|
|
65
67
|
};
|
|
@@ -69,6 +71,8 @@ namespace DrumBuddy
|
|
|
69
71
|
string[] simple = { "play:Space", "stop:Space:Shift", "record:R", "split:S", "scissors:None", "copy:C:Control", "cut:X:Control", "paste:V:Control", "delete:Back", "deleteAlternate:Delete", "undo:Z:Control", "redo:Y:Control", "save:S:Control", "saveAs:S:Control,Shift", "open:O:Control", "new:N:Control", "import:I:Control", "export:E:Control", "start:Home", "end:End", "home:D1:Control", "editor:D2:Control", "converter:D3:Control", "player:D4:Control", "settings:OemComma:Control", "timelineSettings:None" };
|
|
70
72
|
simple = simple.Concat(new[] { "selectTool:V", "zoomIn:OemPlus:Control", "zoomInPlus:OemPlus:Control,Shift", "zoomInKeypad:Add:Control", "zoomOut:OemMinus:Control", "zoomOutKeypad:Subtract:Control", "zoomFit:D0:Control", "zoomSelection:D0:Control,Shift" }).ToArray();
|
|
71
73
|
simple = simple.Concat(new[] { "slipTool:Y", "fadeTool:F", "duplicate:D:Control", "repeatClips:L:Control", "muteClips:M", "crossfade:X", "clearFades:None" }).ToArray();
|
|
74
|
+
simple = simple.Concat(new[] { "startAlternate:Return", "previousBar:Left:Control", "nextBar:Right:Control" }).ToArray();
|
|
75
|
+
simple = simple.Concat(new[] { "mixer:None" }).ToArray();
|
|
72
76
|
foreach (string text in simple) {
|
|
73
77
|
string[] parts = text.Split(':');
|
|
74
78
|
settings.Keybinds.Add(new ShortcutBinding { Action = parts[0], Key = parts[1], Modifiers = parts.Length > 2 ? (int)(ModifierKeys)Enum.Parse(typeof(ModifierKeys), parts[2]) : 0 });
|
|
@@ -99,7 +103,7 @@ namespace DrumBuddy
|
|
|
99
103
|
Key key;
|
|
100
104
|
if (string.IsNullOrEmpty(binding.Key) || binding.Key == "None") continue;
|
|
101
105
|
if (!Enum.TryParse<Key>(binding.Key, out key) || IsModifier(key) || binding.Modifiers < 0 || binding.Modifiers > 15) throw new IOException("Invalid shortcut for " + Actions[binding.Action] + ".");
|
|
102
|
-
string signature =
|
|
106
|
+
string signature = (int)key + ":" + binding.Modifiers;
|
|
103
107
|
if (used.ContainsKey(signature)) throw new IOException(binding.Gesture + " is assigned to both " + Actions[used[signature]] + " and " + Actions[binding.Action] + ". Change or clear one binding.");
|
|
104
108
|
used[signature] = binding.Action;
|
|
105
109
|
}
|
|
@@ -115,7 +119,8 @@ namespace DrumBuddy
|
|
|
115
119
|
settings.KeymapRevision = 1;
|
|
116
120
|
}
|
|
117
121
|
foreach (var binding in Defaults().Keybinds) if (!settings.Keybinds.Any(b => b.Action == binding.Action)) {
|
|
118
|
-
|
|
122
|
+
Key defaultKey = (Key)Enum.Parse(typeof(Key), binding.Key);
|
|
123
|
+
if (settings.Keybinds.Any(b => SameKey(b.Key, defaultKey) && b.Modifiers == binding.Modifiers)) { binding.Key = "None"; binding.Modifiers = 0; }
|
|
119
124
|
settings.Keybinds.Add(binding);
|
|
120
125
|
}
|
|
121
126
|
}
|
|
@@ -137,9 +142,11 @@ namespace DrumBuddy
|
|
|
137
142
|
}
|
|
138
143
|
public static bool IsModifier(Key key)
|
|
139
144
|
{ return key == Key.LeftCtrl || key == Key.RightCtrl || key == Key.LeftAlt || key == Key.RightAlt || key == Key.LeftShift || key == Key.RightShift || key == Key.LWin || key == Key.RWin || key == Key.None; }
|
|
145
|
+
private static bool SameKey(string name, Key key)
|
|
146
|
+
{ Key parsed; return Enum.TryParse<Key>(name, out parsed) && parsed == key; }
|
|
140
147
|
public static string Resolve(Key key, ModifierKeys modifiers)
|
|
141
148
|
{
|
|
142
|
-
var binding = Current.Keybinds.FirstOrDefault(b => b.Key
|
|
149
|
+
var binding = Current.Keybinds.FirstOrDefault(b => SameKey(b.Key, key) && b.Modifiers == (int)modifiers);
|
|
143
150
|
return binding == null ? null : binding.Action;
|
|
144
151
|
}
|
|
145
152
|
public static string Gesture(string action)
|