@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.
@@ -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("Listen volume ", 14));
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
- recordingPane = new DockablePane(editorBody, null, "Record options", false, false, 260) { MinimumMainHeight = 300 }; Children.Add(recordingPane);
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) Change(delegate { session.Tracks[index].Mute = mute.IsChecked == true; return true; }, "Track mute changed."); }; strip.Children.Add(mute);
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) Change(delegate { session.Tracks[index].Solo = solo.IsChecked == true; return true; }, "Track solo changed."); }; strip.Children.Add(solo);
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.SelectedIndex >= 0) { double value = levels[level.SelectedIndex]; Change(delegate { session.Tracks[index].GainDb = value; return true; }, "Track level changed."); } }; strip.Children.Add(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; playhead = at;
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 = binding.Key + ":" + binding.Modifiers;
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
- if (settings.Keybinds.Any(b => b.Key == binding.Key && b.Modifiers == binding.Modifiers)) { binding.Key = "None"; binding.Modifiers = 0; }
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 == key.ToString() && b.Modifiers == (int)modifiers);
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)