@pro-gram/flux 1.5.0 → 1.6.0

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.
@@ -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.StartsWith("D") && Key.Length == 2 && char.IsDigit(Key[1]) ? Key.Substring(1) : Key);
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);
33
33
  }
34
34
  }
35
35
  }
@@ -53,19 +53,22 @@ namespace DrumBuddy
53
53
  public static readonly string SettingsFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DrumBuddy", "interface-settings.xml");
54
54
  public static readonly Dictionary<string, string> Actions = new Dictionary<string, string> {
55
55
  { "play", "Play / pause" }, { "stop", "Stop playback" }, { "record", "Start / stop recording" },
56
- { "split", "Split at playhead" }, { "scissors", "Toggle scissors tool" }, { "copy", "Copy selection" },
56
+ { "split", "Split at playhead" }, { "scissors", "Toggle slice cursor (legacy)" }, { "selectTool", "Selection cursor" }, { "copy", "Copy selection" },
57
+ { "slipTool", "Slip source contents tool" }, { "fadeTool", "Fade tool" }, { "duplicate", "Duplicate selected audio" }, { "repeatClips", "Repeat selected audio" }, { "muteClips", "Mute / unmute selected clips" }, { "crossfade", "Crossfade selected clips" }, { "clearFades", "Clear clip fades" },
57
58
  { "cut", "Cut selection" }, { "paste", "Paste at playhead" }, { "delete", "Delete selection" },
58
59
  { "deleteAlternate", "Delete selection (alternate)" },
59
60
  { "undo", "Undo" }, { "redo", "Redo" }, { "save", "Save session" }, { "saveAs", "Save session as" },
60
61
  { "open", "Open session" }, { "new", "New session" }, { "import", "Import audio" }, { "export", "Export mix" },
61
62
  { "start", "Go to start" }, { "end", "Go to end" }, { "home", "Open Home" }, { "editor", "Open Editor" },
62
63
  { "converter", "Open Converter" }, { "player", "Open Media Player" }, { "settings", "Open Settings" }
63
- , { "timelineSettings", "Open Timeline settings" }
64
+ , { "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" }
64
65
  };
65
66
  public static InterfaceSettings Defaults()
66
67
  {
67
68
  var settings = new InterfaceSettings { KeymapRevision = 1 };
68
- string[] simple = { "play:Space", "stop:Space:Shift", "record:R", "split:S", "scissors:K", "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" };
69
+ 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
+ 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
+ simple = simple.Concat(new[] { "slipTool:Y", "fadeTool:F", "duplicate:D:Control", "repeatClips:L:Control", "muteClips:M", "crossfade:X", "clearFades:None" }).ToArray();
69
72
  foreach (string text in simple) {
70
73
  string[] parts = text.Split(':');
71
74
  settings.Keybinds.Add(new ShortcutBinding { Action = parts[0], Key = parts[1], Modifiers = parts.Length > 2 ? (int)(ModifierKeys)Enum.Parse(typeof(ModifierKeys), parts[2]) : 0 });
@@ -81,6 +84,7 @@ namespace DrumBuddy
81
84
  Validate(settings);
82
85
  }
83
86
  catch { settings = Defaults(); settings.AudioOutputKey = legacyOutput ?? "default"; settings.AudioInputKey = legacyInput; }
87
+ settings.AudioOutputKey = AudioOutputs.NormalizeKey(settings.AudioOutputKey);
84
88
  Current = settings; ThemePalette.Apply(settings.Theme == "Dark"); AudioOutputs.LatencyMilliseconds = settings.PlaybackLatency;
85
89
  }
86
90
  public static void Validate(InterfaceSettings settings)
@@ -102,6 +106,7 @@ namespace DrumBuddy
102
106
  }
103
107
  public static void Upgrade(InterfaceSettings settings)
104
108
  {
109
+ settings.AudioOutputKey = AudioOutputs.NormalizeKey(settings.AudioOutputKey);
105
110
  if (settings.Grid == null) settings.Grid = new TempoGridOptions();
106
111
  if (settings.Keybinds == null) settings.Keybinds = new List<ShortcutBinding>();
107
112
  if (settings.KeymapRevision < 1) {
@@ -162,6 +167,7 @@ namespace DrumBuddy
162
167
  { "#F0F8F5", "#192139" }, { "#E4ECEA", "#202536" }, { "#DCEFEB", "#1C2943" }, { "#EDF3F1", "#171B26" },
163
168
  { "#D6E1DF", "#2C3349" }, { "#E0E8E5", "#242B3C" }, { "#DAE5E0", "#2A2B43" }, { "#B9DDD2", "#334567" },
164
169
  { "#438B77", "#59CCFF" }, { "#5E9D89", "#7D9CFA" }, { "#CBD8D4", "#353951" }, { "#D0772C", "#FFB55E" },
170
+ { "#DAD5FA", "#3A315D" }, { "#7359C7", "#B497FF" },
165
171
  { "#D17626", "#FFB55E" }, { "#277FA5", "#64CBFF" }, { "#386BA8", "#A28CFF" }, { "#5596C8", "#6BAAFF" },
166
172
  { "#15877C", "#45B8F0" }, { "#16887D", "#45B8F0" }, { "#B52C36", "#EE657D" }, { "#D16A19", "#B3A0FF" }
167
173
  };
@@ -74,6 +74,7 @@ namespace DrumBuddy
74
74
  Picture(new Image { Source = BrandArtwork.Load(), Stretch = Stretch.Uniform }, Path.Combine(root, "refined-vector-logo.png"), 840, 640);
75
75
  var main = new MainWindow(Path.Combine(root, "legacy-preferences.xml"), true);
76
76
  Check(main.NativeWindow.Title == "Flux", "Use exactly Flux as the window title");
77
+ Check(object.ReferenceEquals(main.NativeWindow.Icon, BrandArtwork.Raster()), "Use the current Flux mark directly for the window and taskbar icon");
77
78
  main.OpenEditorForTest(); Check(main.IntegratedRecorderForTest, "Mount recording controls inside the main Editor while retaining named input controls");
78
79
  string demoPath = Path.Combine(root, "Generated demo.wav"); var demo = new float[Audio.Rate * 8];
79
80
  for (int i = 0; i < demo.Length; i++) demo[i] = (float)(.7 * Math.Exp(-(i % (Audio.Rate / 2)) / 3000.0) * Math.Sin(i * .06));
@@ -84,6 +85,7 @@ namespace DrumBuddy
84
85
  for (int i = 0; i < names.Length; i++) { demoSession.Tracks[i].Name = names[i]; demoSession.Regions.Add(new TimelineRegion { Source = demoClip.Source, Name = names[i], Track = i, Start = (i % 3) * Audio.Rate / 2, Length = (6 - i % 3) * Audio.Rate }); }
85
86
  main.EditorFixtureForTest(demoSession, new Dictionary<string, Clip> { { demoClip.Source, demoClip } });
86
87
  Check(!Buttons(main.RecordingPaneForTest.MainContent).Any(b => new[] { "Cut", "Copy", "Paste" }.Contains(b.Content as string)), "Remove Cut, Copy and Paste buttons from the editor toolbar");
88
+ Check(!Buttons(main.RecordingPaneForTest.MainContent).Any(b => ((b.Content as string) ?? "").Contains("Split") || ((b.Content as string) ?? "").Contains("Scissors")), "Keep split on the S action and remove redundant scissors/split toolbar buttons");
87
89
  Check(main.NativeWindow.FindName("SettingsTimeline") != null, "Expose Timeline / grid in Settings");
88
90
  var initialGrid = InterfacePreferences.Current.Copy(); initialGrid.Grid = main.GridForTest;
89
91
  string gridSettingsFile = Path.Combine(root, "saved-grid-settings.xml");
@@ -105,7 +107,11 @@ namespace DrumBuddy
105
107
  main.SetGridForTest(new TempoGridOptions { Division = 128, Triplet = true }, 1280); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "editor-grid-128-triplet-dark.png"), 1280, 800);
106
108
  main.EditorFixtureForTest(demoSession, new Dictionary<string, Clip> { { demoClip.Source, demoClip } }); main.SetGridForTest(demoSession.Grid, 40); main.OpenEditorForTest();
107
109
  Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "editor-shell-dark.png"), 1280, 800);
110
+ Check(main.EditorTimelineHeightForTest >= 550, "Reserve at least 550 vertical pixels for the timeline at 1280 x 800");
111
+ log.Add("Timeline viewport at 1280 x 800: " + main.EditorTimelineHeightForTest.ToString("0") + " px.");
108
112
  Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "editor-compact-dark.png"), 850, 560);
113
+ Check(main.EditorTimelineHeightForTest >= 250, "Keep a usable timeline after packing controls at 850 x 560");
114
+ log.Add("Timeline viewport at 850 x 560: " + main.EditorTimelineHeightForTest.ToString("0") + " px.");
109
115
  main.RecordingPanelForTest(); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "editor-record-dark.png"), 1280, 800);
110
116
  Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "editor-record-compact-dark.png"), 850, 560);
111
117
  main.RecordingPaneForTest.SetBottom(true); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "editor-record-bottom-compact-dark.png"), 850, 560);
package/source/Main.cs CHANGED
@@ -20,7 +20,7 @@ using NAudio.Wave.SampleProviders;
20
20
 
21
21
  [assembly: AssemblyTitle("Flux")]
22
22
  [assembly: AssemblyDescription("PCM/float media player, 16-track audio editor, H5studio recording and R16 control")]
23
- [assembly: AssemblyVersion("1.5.0.0")]
23
+ [assembly: AssemblyVersion("1.6.0.0")]
24
24
 
25
25
  namespace DrumBuddy
26
26
  {
@@ -34,9 +34,8 @@ namespace DrumBuddy
34
34
  public sealed class OutputChoice
35
35
  {
36
36
  public string Name, Driver, EndpointId;
37
- public int Device = -1;
38
- public bool Compatibility;
39
- public string Key { get { return Compatibility ? "compat:" + (EndpointId ?? "default") : Driver != null ? "asio:" + Driver : EndpointId != null ? "windows:" + EndpointId : "default"; } }
37
+ public string DefaultEndpointId;
38
+ public string Key { get { return Driver != null ? "asio:" + Driver : EndpointId != null ? "windows:" + EndpointId : "default"; } }
40
39
  public override string ToString() { return Name; }
41
40
  }
42
41
  public sealed class ModeChoice
@@ -102,8 +101,7 @@ namespace DrumBuddy
102
101
  window.Height = Math.Min(window.Height, SystemParameters.WorkArea.Height - 30);
103
102
  window.Width = Math.Min(window.Width, SystemParameters.WorkArea.Width - 30);
104
103
  C<Image>("BrandLogo").Source = BrandArtwork.Raster();
105
- window.Icon = System.Windows.Interop.Imaging.CreateBitmapSourceFromHIcon(System.Drawing.Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location).Handle,
106
- Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
104
+ window.Icon = BrandArtwork.Raster();
107
105
  try { using (var s = File.OpenRead(settings)) prefs = (Preferences)new XmlSerializer(typeof(Preferences)).Deserialize(s); }
108
106
  catch { prefs = new Preferences(); }
109
107
  if (!testMode) InterfacePreferences.Load(prefs.AudioOutputKey, prefs.AudioInputKey);
@@ -253,7 +251,7 @@ namespace DrumBuddy
253
251
  var inputs = AudioInputs.Choices(); configuringCapture = true; C<ComboBox>("InputDevice").ItemsSource = inputs; C<ComboBox>("InputDevice").SelectedItem = inputs.FirstOrDefault(c => c.Driver == null); configuringCapture = false;
254
252
  ConfigureRecordingDevice(); editorView.ShowRecordingOptions(true);
255
253
  }
256
- internal void SetThemeForTest(bool dark) { ThemePalette.Apply(dark); ThemePalette.Install(window); C<Button>("ThemeToggle").Content = dark ? "Light theme" : "Dark theme"; }
254
+ internal void SetThemeForTest(bool dark) { ThemePalette.Apply(dark); ThemePalette.Install(window); C<Button>("ThemeToggle").Content = dark ? "Light" : "Dark"; }
257
255
  internal void OpenConverterForTest() { SwitchPage(false); }
258
256
  internal DockablePane ConverterPaneForTest { get { return converterPane; } }
259
257
  internal DockablePane RecordingPaneForTest { get { return editorView.RecordingPaneForTest; } }
@@ -262,6 +260,7 @@ namespace DrumBuddy
262
260
  internal void ApplySettingsForTest(InterfaceSettings value) { InterfacePreferences.Current = value.Copy(); ThemePalette.Apply(value.Theme == "Dark"); ApplyInterfaceSettings(); }
263
261
  internal TempoGridOptions GridForTest { get { return editorView.GridOptions; } }
264
262
  internal void SetGridForTest(TempoGridOptions value, double zoom) { editorView.ApplyGridOptions(value); editorView.ZoomForTest(zoom); }
263
+ internal double EditorTimelineHeightForTest { get { return editorView.TimelineHeightForTest; } }
265
264
  internal bool IntegratedRecorderForTest { get { return editorView != null && C<StackPanel>("RecordPage").Parent != null && C<ComboBox>("InputDevice") != null; } }
266
265
  private static Brush Brush(string hex) { return ThemePalette.Brush(hex); }
267
266
  private void Status(string text) { C<TextBlock>("StatusText").Text = text; C<Border>("StatusHost").Visibility = string.IsNullOrWhiteSpace(text) ? Visibility.Collapsed : Visibility.Visible; }
@@ -411,7 +410,7 @@ namespace DrumBuddy
411
410
  }
412
411
  private void ApplyInterfaceSettings()
413
412
  {
414
- ThemePalette.Install(window); C<Button>("ThemeToggle").Content = ThemePalette.Dark ? "Light theme" : "Dark theme";
413
+ ThemePalette.Install(window); C<Button>("ThemeToggle").Content = ThemePalette.Dark ? "Light" : "Dark";
415
414
  var settings = InterfacePreferences.Current;
416
415
  string gridSignature = settings.Grid.Signature();
417
416
  if (appliedGridDefaults != gridSignature) { appliedGridDefaults = gridSignature; if (editorView != null) editorView.ApplyGridOptions(settings.Grid); }
@@ -455,8 +454,8 @@ namespace DrumBuddy
455
454
  string action = InterfacePreferences.Resolve(key, System.Windows.Input.Keyboard.Modifiers);
456
455
  if (action == null) return;
457
456
  bool text = System.Windows.Input.Keyboard.FocusedElement is TextBox || System.Windows.Input.Keyboard.FocusedElement is ComboBox;
458
- if (text && !new[] { "save", "saveAs", "open", "new", "settings" }.Contains(action)) return;
459
- if (e.IsRepeat) { e.Handled = true; return; }
457
+ if (text && !action.StartsWith("zoom", StringComparison.Ordinal) && !new[] { "save", "saveAs", "open", "new", "settings" }.Contains(action)) return;
458
+ if (e.IsRepeat && !action.StartsWith("zoomIn", StringComparison.Ordinal) && !action.StartsWith("zoomOut", StringComparison.Ordinal)) { e.Handled = true; return; }
460
459
  if (workspace != 3 && !(workspace == 2 && new[] { "play", "stop", "start", "end" }.Contains(action)) && !new[] { "home", "editor", "converter", "player", "settings", "timelineSettings", "new", "open", "save", "saveAs", "import", "export" }.Contains(action)) return;
461
460
  e.Handled = true; await ExecuteInterfaceAction(action);
462
461
  }
@@ -708,7 +707,7 @@ namespace DrumBuddy
708
707
  private void ShowHelp()
709
708
  {
710
709
  MessageBox.Show(window,
711
- "AUDIO DEVICES\nUse Audio devices at the top of the app to choose playback output and recording input. Test speakers plays a short quiet tone. If Windows/Realtek playback fails, select that device's Compatibility (16-bit PCM) output. Save diagnostics exports device formats and recent playback errors. Windows microphones capture 16-bit PCM; use H5studio ASIO for native 32-bit float.\n\n" +
710
+ "AUDIO DEVICES\nUse Audio devices at the top of the app to choose playback output and recording input. Test speakers plays a short quiet tone. Windows default follows the current Windows speakers/headphones; named outputs select a specific device. Save diagnostics exports device formats and recent playback errors. Windows microphones capture 16-bit PCM; use H5studio ASIO for native 32-bit float.\n\n" +
712
711
  "HOME AND CONVERTER\nOpen Converter from View or Home. Drop audio or video, choose an output type and click Convert files. WAV PCM, WAV float, AIFF, FLAC, MP3 and AAC copies are supported. Output settings can be resized, collapsed, or displayed on the side or bottom. The source is preserved.\n\n" +
713
712
  "Set safe level brings the loudest peak to −1 dBFS. Prevent clipping caps excessive gain evenly; it preserves the dynamics of your drums.\n\n" +
714
713
  "ZOOM R16\nExports are 44.1 kHz PCM WAV, mono or stereo, with names up to 8 uppercase characters. Use the bit depth of your R16 project. Copy WAVs to that project's AUDIO folder, then assign them to tracks on the R16.\n\n" +
@@ -721,26 +720,33 @@ namespace DrumBuddy
721
720
  "Install Drivers opens a short guide and links to Zoom's official driver download pages.\n\n" +
722
721
  "MEDIA PLAYER\nOpen audio or video to listen. Gain / Save amplifies or reduces playback and saves adjusted WAV copies; Playback volume changes listening level only. Reset, Set safe level and Prevent clipping are available. Choose speakers/headphones under Audio output. Gain / Save can be resized, collapsed, or displayed on the side or bottom.\n\n" +
723
722
  "EDITOR\nA 16-track audio timeline. Drag the upper ruler and release to place the playhead. The left edge snaps to zero; Home/To start goes to zero and End goes to the timeline end. Hold Alt to bypass snapping. Drag across a track to select time; two thinner blue sticks mark the selection start/end. Drag their lower-ruler handles to adjust the boundaries. Cut/copy/paste, split, undo/redo, Space to play/pause and Ctrl+S to save. Cuts leave a gap. Choose Selected track or All 16 tracks for edits. Save .dbedit sessions and export PCM or float WAV mixes. Original source files are preserved.\n\n" +
724
- "THEME AND SETTINGS\nUse the top-right switch for dark or light theme. Settings has categories for General, Audio and Keybinds. Click Edit on a keybind and press the new shortcut immediately. Save writes the changes; leaving with pending changes asks whether to save them. S splits at the playhead; K toggles the scissors pointer tool by default.\n\n" +
725
- "Basic tools run locally without an account. This release is for Windows; macOS is planned.\n\nFlux 1.5.0 · FFmpeg + NAudio",
723
+ "THEME AND SETTINGS\nUse the top-right switch for dark or light theme. Settings has categories for General, Audio and Keybinds. Click Edit on a keybind and press the new shortcut immediately. Save writes the changes; leaving with pending changes asks whether to save them. S splits at the playhead; V returns to the selection cursor.\n\n" +
724
+ "Basic tools run locally without an account. This release is for Windows; macOS is planned.\n\nFlux 1.6.0 · FFmpeg + NAudio",
726
725
  "Flux — quick help",MessageBoxButton.OK,MessageBoxImage.Information);
727
726
  }
728
727
  }
729
728
 
730
729
  public static class Program
731
730
  {
731
+ [System.Runtime.InteropServices.DllImport("shell32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
732
+ private static extern int SetCurrentProcessExplicitAppUserModelID(string appId);
732
733
  [STAThread]
733
734
  public static int Main(string[] args)
734
735
  {
735
736
  if (args.Length > 0 && args[0] == "--self-test") return SelfTest.Run(args.Skip(1).ToArray());
736
737
  if (args.Length > 0 && args[0] == "--float-self-test") return FloatRecordingTests.Run(args.Skip(1).ToArray());
737
738
  if (args.Length > 0 && args[0] == "--grid-self-test") return TempoGridTests.Run(args.Skip(1).ToArray());
739
+ if (args.Length > 0 && args[0] == "--zoom-self-test") return ZoomTests.Run(args.Skip(1).ToArray());
740
+ if (args.Length > 0 && args[0] == "--selection-self-test") return EditorSelectionTests.Run(args.Skip(1).ToArray());
741
+ if (args.Length > 0 && args[0] == "--tools-self-test") return EditorToolsTests.Run(args.Skip(1).ToArray());
738
742
  if (args.Length > 0 && args[0] == "--workspace-self-test") return WorkspaceTests.Run(args.Skip(1).ToArray());
739
743
  if (args.Length > 0 && args[0] == "--interface-self-test") return InterfaceTests.Run(args.Skip(1).ToArray());
740
744
  if (args.Length > 0 && args[0] == "--conversion-self-test") return ConversionTests.Run(args.Skip(1).ToArray());
741
745
  if (args.Length > 0 && args[0] == "--output-self-test") return PlaybackTests.Run(args.Skip(1).ToArray());
746
+ if (args.Length > 1 && args[0] == "--editor-playback-self-test") return EditorPlaybackTests.Run(args.Skip(1).ToArray());
742
747
  if (args.Length > 1 && args[0] == "--audio-diagnostics") { File.WriteAllText(args[1], AudioDiagnostics.Report(null), System.Text.Encoding.UTF8); return 0; }
743
748
  if (args.Length > 0 && args[0] == "--control-self-test") return ControlTests.Run(args.Skip(1).ToArray());
749
+ SetCurrentProcessExplicitAppUserModelID("ca.fluxtempo.Flux");
744
750
  var app = new Application { ShutdownMode = ShutdownMode.OnExplicitShutdown };
745
751
  app.DispatcherUnhandledException += delegate(object sender, DispatcherUnhandledExceptionEventArgs e) {
746
752
  try { string folder = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"DrumBuddy"); Directory.CreateDirectory(folder); File.AppendAllText(System.IO.Path.Combine(folder,"error.log"),DateTime.Now + " " + e.Exception + Environment.NewLine); } catch { }
package/source/Main.xaml CHANGED
@@ -66,7 +66,9 @@
66
66
  </Style>
67
67
  </Window.Resources>
68
68
  <DockPanel Margin="16,10,16,10">
69
- <Menu DockPanel.Dock="Top" Margin="-5,0,0,10" FontSize="13" Background="{DynamicResource ThemeF3F5F5}" Foreground="{DynamicResource Theme182C37}">
69
+ <Grid DockPanel.Dock="Top" Margin="0,0,0,6">
70
+ <Grid.RowDefinitions><RowDefinition Height="26" /><RowDefinition Height="36" /></Grid.RowDefinitions>
71
+ <Menu Margin="-5,0,0,0" FontSize="12" Background="{DynamicResource ThemeF3F5F5}" Foreground="{DynamicResource Theme182C37}">
70
72
  <MenuItem Header="_File">
71
73
  <MenuItem x:Name="FileNew" Header="New session" />
72
74
  <MenuItem x:Name="FileOpen" Header="Open session..." />
@@ -99,33 +101,28 @@
99
101
  <MenuItem x:Name="HelpGuide" Header="Quick help..." />
100
102
  <MenuItem x:Name="HelpAbout" Header="About Flux..." />
101
103
  </MenuItem>
102
- </Menu>
103
- <Grid DockPanel.Dock="Top" Margin="0,0,0,12">
104
- <Grid.ColumnDefinitions>
105
- <ColumnDefinition />
106
- <ColumnDefinition Width="Auto" />
107
- </Grid.ColumnDefinitions>
108
- <StackPanel Orientation="Horizontal">
109
- <Image x:Name="BrandLogo" Width="76" Height="58" Stretch="Uniform" Margin="0,0,12,0" />
110
- <StackPanel VerticalAlignment="Center">
111
- <TextBlock Text="Flux" FontSize="18" FontWeight="SemiBold" />
112
- <TextBlock Text="Record / Edit / Convert" Foreground="{DynamicResource Theme526771}" FontSize="11" Margin="1,2,0,0" />
113
- </StackPanel>
114
- </StackPanel>
115
- <StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
116
- <Button x:Name="ThemeToggle" Content="Light theme" MinWidth="92" Margin="0,0,8,0" ToolTip="Switch between Dark and Light themes" />
117
- <Button x:Name="AudioDevicesButton" Content="Audio devices" MinWidth="108" Margin="0,0,10,0" ToolTip="Choose speakers/headphones, recording input, and PCM compatibility playback." />
118
- <Button x:Name="InstallDriversButton" Content="Install Drivers" MinWidth="112" Margin="0,0,10,0" ToolTip="Official Zoom H5studio and R16/R24 Windows driver setup." />
119
- <Button x:Name="HelpButton" Content="Help" MinWidth="62" />
120
- </StackPanel>
121
- </Grid>
122
- <WrapPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,0,0,10">
123
- <Button x:Name="HomeTab" Content="Home" Width="88" Margin="0,0,8,6" />
124
- <Button x:Name="RecordTab" Content="Record" Visibility="Collapsed" Width="90" Margin="0,0,12,0" />
125
- <Button x:Name="MediaPlayerTab" Content="Media Player" Width="122" Margin="0,0,12,8" />
126
- <Button x:Name="EditorTab" Content="Editor + Record" Width="140" Margin="0,0,12,8" />
127
- <Button x:Name="ControlTab" Content="R16 Control" Width="115" Margin="0,0,0,8" />
128
- </WrapPanel>
104
+ </Menu>
105
+ <Grid Grid.Row="1">
106
+ <Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions>
107
+ <StackPanel Orientation="Horizontal" Margin="0,0,14,0">
108
+ <Image x:Name="BrandLogo" Width="44" Height="34" Stretch="Uniform" Margin="0,0,5,0" />
109
+ <TextBlock Text="Flux" FontSize="15" FontWeight="SemiBold" VerticalAlignment="Center" />
110
+ </StackPanel>
111
+ <StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
112
+ <Button x:Name="HomeTab" Content="Home" MinHeight="26" Width="60" FontSize="12" Padding="7,2" Margin="0,0,4,0" />
113
+ <Button x:Name="RecordTab" Content="Record" Visibility="Collapsed" />
114
+ <Button x:Name="MediaPlayerTab" Content="Player" MinHeight="26" Width="64" FontSize="12" Padding="7,2" Margin="0,0,4,0" />
115
+ <Button x:Name="EditorTab" Content="Editor + Record" MinHeight="26" Width="116" FontSize="12" Padding="7,2" Margin="0,0,4,0" />
116
+ <Button x:Name="ControlTab" Content="R16 Control" MinHeight="26" Width="92" FontSize="12" Padding="7,2" />
117
+ </StackPanel>
118
+ <StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
119
+ <Button x:Name="ThemeToggle" Content="Light" MinHeight="26" FontSize="12" Padding="8,2" Margin="0,0,5,0" ToolTip="Switch between dark and light themes" />
120
+ <Button x:Name="AudioDevicesButton" Content="Audio devices" MinHeight="26" FontSize="12" Padding="8,2" ToolTip="Select playback and recording devices" />
121
+ <Button x:Name="InstallDriversButton" Content="Install Drivers" Visibility="Collapsed" />
122
+ <Button x:Name="HelpButton" Content="Help" Visibility="Collapsed" />
123
+ </StackPanel>
124
+ </Grid>
125
+ </Grid>
129
126
  <Border x:Name="StatusHost" DockPanel.Dock="Bottom" Background="{DynamicResource ThemeF3F5F5}" BorderBrush="{DynamicResource ThemeDFE5E6}" BorderThickness="0,1,0,0" Padding="6,4" Margin="0,6,0,0" Visibility="Collapsed">
130
127
  <Grid>
131
128
  <Grid.ColumnDefinitions>
@@ -44,8 +44,10 @@ namespace DrumBuddy
44
44
  var nativeFormat = new WaveFormatExtensible(48000, 32, 6);
45
45
  var exact = new EndpointWaveProvider(new Samples(new float[6], 48000, 6), nativeFormat);
46
46
  check(object.ReferenceEquals(exact.WaveFormat, nativeFormat), "Keep the exact native endpoint descriptor, including its speaker mask");
47
- check(new OutputChoice { Compatibility = true }.Key == "compat:default" && new OutputChoice { Compatibility = true, EndpointId = "endpoint" }.Key == "compat:endpoint",
48
- "Persist compatibility outputs separately from shared Windows and ASIO selections");
47
+ check(AudioOutputs.NormalizeKey("compat:default") == "default" && AudioOutputs.NormalizeKey("compat:endpoint") == "windows:endpoint",
48
+ "Migrate retired compatibility selections to the same real Windows endpoint");
49
+ check(WindowsAudioPlayer.Flags(true) == NAudio.CoreAudioApi.AudioClientStreamFlags.EventCallback && WindowsAudioPlayer.Flags(false) == NAudio.CoreAudioApi.AudioClientStreamFlags.None,
50
+ "Open an exact-mix shared stream without redundant Windows conversion flags");
49
51
  var mono = ReadAll(AudioOutputs.Adapt(new Samples(new[] { .25f, -.75f, 1.5f }, 44100, 1), 44100, 2));
50
52
  check(mono.SequenceEqual(new[] { .25f, .25f, -.75f, -.75f, 1f, 1f }), "Duplicate mono into both headphones and bound playback headroom");
51
53
  var stereo = ReadAll(AudioOutputs.Adapt(new Samples(new[] { .5f, -.25f, -1f, .5f }, 44100, 2), 44100, 2));
@@ -97,16 +99,20 @@ namespace DrumBuddy
97
99
  {
98
100
  string root = Path.GetFullPath(args.Length > 0 ? args[0] : Path.Combine(Path.GetTempPath(), "DrumBuddyOutput_" + Guid.NewGuid().ToString("N")));
99
101
  Directory.CreateDirectory(root); var log = new List<string>();
102
+ string oldLog = AudioOutputs.TestErrorLog; AudioOutputs.TestErrorLog = Path.Combine(root, "output-errors.txt");
100
103
  try {
101
- var choices = new List<OutputChoice> { new OutputChoice { Name = "Windows default" }, new OutputChoice { Name = "Windows default compatibility", Compatibility = true } };
102
- var named = AudioOutputs.Choices().FirstOrDefault(c => c.Compatibility && c.EndpointId != null);
104
+ var listed = AudioOutputs.Choices();
105
+ if (listed.Any(c => c.Name.IndexOf("Compatibility", StringComparison.OrdinalIgnoreCase) >= 0) || listed.Select(c => c.Key).Distinct().Count() != listed.Count) throw new Exception("Output choices contain synthetic or duplicate backend entries.");
106
+ log.Add("PASS List Windows default and real endpoints without compatibility devices");
107
+ var choices = new List<OutputChoice> { listed.First(c => c.Key == "default") };
108
+ var named = listed.FirstOrDefault(c => c.EndpointId == choices[0].DefaultEndpointId);
103
109
  if (named != null) choices.Add(named);
104
110
  foreach (var choice in choices) foreach (int channels in new[] { 1, 2 }) {
105
111
  using (var completed = new ManualResetEvent(false))
106
112
  using (var route = AudioOutputs.Open(new Samples(new float[4410 * channels], 44100, channels), choice)) {
107
113
  Exception error = null;
108
114
  route.Player.PlaybackStopped += delegate(object sender, StoppedEventArgs e) { error = e.Exception; completed.Set(); };
109
- if (choice.Compatibility && !(route.Player is WaveOutEvent)) throw new Exception("Expected PCM compatibility playback.");
115
+ if (!(route.Player is WindowsAudioPlayer)) throw new Exception("Expected native Windows shared playback.");
110
116
  log.Add("Output: " + route.Description + ", " + route.Counter.WaveFormat.Channels + " channels");
111
117
  route.Player.Play();
112
118
  if (!completed.WaitOne(5000)) throw new Exception("Timed out while playing silent samples.");
@@ -116,7 +122,7 @@ namespace DrumBuddy
116
122
  }
117
123
  }
118
124
  bool unavailable = false;
119
- try { using (AudioOutputs.OpenCompatibility(new Samples(new float[4410], 44100, 1), new OutputChoice { Compatibility = true, EndpointId = "missing-endpoint-for-test" })) { } }
125
+ try { using (AudioOutputs.Open(new Samples(new float[4410], 44100, 1), new OutputChoice { EndpointId = "missing-endpoint-for-test" })) { } }
120
126
  catch (IOException) { unavailable = true; }
121
127
  if (!unavailable) throw new Exception("A missing selected endpoint was replaced by another output.");
122
128
  log.Add("PASS Refuse to route a missing explicitly selected endpoint to a different device");
@@ -129,6 +135,7 @@ namespace DrumBuddy
129
135
  File.WriteAllLines(Path.Combine(root, "output-test-results.txt"), log, Encoding.UTF8); return 0;
130
136
  }
131
137
  catch (Exception ex) { log.Add("FAIL " + ex); File.WriteAllLines(Path.Combine(root, "output-test-results.txt"), log, Encoding.UTF8); return 1; }
138
+ finally { AudioOutputs.TestErrorLog = oldLog; }
132
139
  }
133
140
  }
134
141
  }
@@ -1,4 +1,4 @@
1
- FLUX 1.5.0
1
+ FLUX 1.6.0
2
2
 
3
3
  INSTALL / UPDATE
4
4
  Close the app. Run npm.cmd install -g @pro-gram/flux@latest
@@ -10,12 +10,24 @@ Macintosh support is planned.
10
10
  EDITOR + RECORD
11
11
  The Editor opens by default. Home contains the separate mini-app sections.
12
12
  File contains New, Open session, Save, Save as and Add audio.
13
+ The compact header keeps navigation beside the logo. Grid and zoom share
14
+ one toolbar; playback and the actual output device share the bottom strip.
15
+ Export settings and extra range/import options expand when needed.
13
16
  Resize recording options by dragging the divider. Collapse hides its content;
14
17
  Show restores it. Its Side/Bottom selector changes the panel position.
15
18
  Add/drop audio, select time by dragging, then cut/copy/paste or split.
19
+ V selects the normal pointer. Click a clip to select it; Shift-click selects
20
+ additional clips. Selected items have purple fill and a bright border.
21
+ Drag within audio to select a time range automatically. S splits at the playhead.
22
+ Ctrl+Plus/Minus and Ctrl+mouse wheel zoom. Numeric-keypad +/- also work.
16
23
  Drag the ruler to place the playhead. The left edge snaps to exact zero.
17
24
  The thinner blue sticks mark selection start/end. Alt bypasses snapping.
18
- Range / zoom opens numeric selection and zoom controls.
25
+ Range opens numeric selection controls.
26
+ Zoom: use the visible - / + buttons and slider, or Ctrl+mouse wheel over audio.
27
+ Wheel zoom keeps the time under the pointer in place. Shift+wheel pans sideways.
28
+ Fit shows the full project; Selection fills the view with the selected range.
29
+ Ctrl+= / Ctrl+- zoom; Ctrl+0 fits the project; Ctrl+Shift+0 fits the selection.
30
+ Zoom can reveal individual samples. Close-up waveforms load in the background.
19
31
  Record options opens the sidebar. Select input, channels, rate and format.
20
32
  The take is saved at the playhead on the selected track; separate inputs use
21
33
  consecutive tracks. Stop saves and inserts it. Source files are preserved.
@@ -23,8 +35,9 @@ Use direct headphone monitoring. Software overdub synchronization is not include
23
35
 
24
36
  AUDIO DEVICES
25
37
  Audio devices at the top right opens Settings > Audio. Save device changes.
26
- For Realtek/shared-audio errors, select the same device's Compatibility
27
- (16-bit PCM) output. Test speakers plays a short quiet tone.
38
+ Windows default shows and follows the current Windows speakers/headphones.
39
+ Named outputs select the actual active device; there are no compatibility
40
+ device entries. Test speakers plays a short quiet tone.
28
41
  Save diagnostics exports device information and recent playback errors.
29
42
  Playback buffer: 40-250 ms; increase it if audio breaks up.
30
43
  Windows microphones/Realtek/USB headsets receive 16-bit PCM. Saving that
@@ -39,6 +52,8 @@ General formats: PCM WAV, float WAV, AIFF, FLAC, MP3 and AAC/M4A.
39
52
  MP3 -> AIFF and AIFF -> WAV are supported. Gain tools are in Media Player.
40
53
  Choose source/44.1/48/96 kHz; MP3 uses at most 48 kHz.
41
54
  Conversion options resize, collapse and dock on the side or bottom.
55
+ The installer includes FFmpeg with 227 audio decoders, including float/PCM WAV;
56
+ import and conversion do not require Windows Media Player codec packs.
42
57
  The Zoom R16 preset writes classic 44.1 kHz PCM with compatible filenames.
43
58
  Existing sources and exports are never overwritten.
44
59
 
@@ -50,7 +65,11 @@ No discards changes. Duplicate bindings prevent saving until corrected.
50
65
 
51
66
  DEFAULT KEYS
52
67
  Space: play/pause or stop active recording. R: start/stop recording.
53
- Shift+Space: stop. S: split at playhead. K: click-to-split scissors tool.
68
+ Shift+Space: stop. V: select pointer. S: split at playhead.
69
+ Drag a clip header to move it; drag its left/right edges to trim it.
70
+ Y: slip source contents. F: fade tool; selected clips also show fade handles.
71
+ Ctrl+D: duplicate. Ctrl+L: repeat selected clips/range. M: clip mute. X: crossfade.
72
+ Right-click audio for the editing menu. Edits, fades and mute support Undo.
54
73
  Ctrl+X/C/V: cut/copy/paste. Backspace/Delete: remove selection. Ctrl+Z/Y: undo/redo.
55
74
  Ctrl+S: save session. Ctrl+Shift+S: save as. Ctrl+O/N: open/new.
56
75
  Ctrl+I/E: import/export. Home/End: timeline beginning/end.
@@ -74,4 +93,4 @@ Editor BPM/division controls or Settings > Timeline / grid.
74
93
  Grid and clip-edge snapping are independent. Alt bypasses both.
75
94
  Tempo changes grid spacing without stretching audio. Save .dbedit to keep it.
76
95
  Ctrl+C, Backspace, position playhead, Ctrl+V. Copies survive deletion.
77
- Zoom to 2560 px/s for fine subdivisions; disable snap for free chops.
96
+ Zoom into fine subdivisions or individual samples; disable snap for free chops.
@@ -26,7 +26,7 @@ namespace DrumBuddy
26
26
  public SettingsWindow(Window owner, string category, bool audioLocked, Action<InterfaceSettings> persist = null, InterfaceSettings initial = null)
27
27
  {
28
28
  this.persist = persist ?? InterfacePreferences.Save;
29
- Owner = owner; this.audioLocked = audioLocked; Title = "Flux - Settings";
29
+ Owner = owner; this.audioLocked = audioLocked; Title = "Flux - Settings"; Icon = BrandArtwork.Raster();
30
30
  Width = 930; Height = Math.Min(700, SystemParameters.WorkArea.Height - 40); MinWidth = 700; MinHeight = 460;
31
31
  FontFamily = owner == null ? new System.Windows.Media.FontFamily("Segoe UI") : owner.FontFamily; FontSize = 13; WindowStartupLocation = WindowStartupLocation.CenterOwner;
32
32
  ThemePalette.Install(this); draft = (initial ?? InterfacePreferences.Current).Copy(); saved = draft.Signature();
@@ -109,10 +109,11 @@ namespace DrumBuddy
109
109
  }
110
110
  private void ShowAudio()
111
111
  {
112
- var panel = Page("Audio", "Choose output and recording input. Compatibility mode helps with Realtek or shared-audio API errors.");
112
+ var panel = Page("Audio", "Choose your actual speakers/headphones and recording input. Windows default follows your current Windows output.");
113
113
  panel.Children.Add(WorkspaceUi.Text("Speakers / headphones", 15));
114
114
  var outputs = AudioOutputs.Choices();
115
- var output = new ComboBox { ItemsSource = outputs, SelectedItem = outputs.FirstOrDefault(c => c.Key == draft.AudioOutputKey), MinHeight = 34, FontSize = 13, Margin = new Thickness(0, 7, 0, 14), IsEnabled = !audioLocked };
115
+ var output = new ComboBox { ItemsSource = outputs, SelectedItem = outputs.FirstOrDefault(c => c.Key == draft.AudioOutputKey) ?? outputs[0], MinHeight = 34, FontSize = 13, Margin = new Thickness(0, 7, 0, 14), IsEnabled = !audioLocked };
116
+ draft.AudioOutputKey = ((OutputChoice)output.SelectedItem).Key;
116
117
  panel.Children.Add(output); output.SelectionChanged += delegate { var item = output.SelectedItem as OutputChoice; if (item != null) { draft.AudioOutputKey = item.Key; UpdateDirty(); } };
117
118
  panel.Children.Add(WorkspaceUi.Text("Recording input", 15)); var inputs = AudioInputs.Choices();
118
119
  var input = new ComboBox { ItemsSource = inputs, SelectedItem = inputs.FirstOrDefault(c => c.Key == draft.AudioInputKey), MinHeight = 34, FontSize = 13, Margin = new Thickness(0, 7, 0, 14), IsEnabled = !audioLocked };
package/source/Setup.cs CHANGED
@@ -14,7 +14,7 @@ using System.Windows.Markup;
14
14
  using Microsoft.Win32;
15
15
 
16
16
  [assembly: AssemblyTitle("Flux Setup")]
17
- [assembly: AssemblyVersion("1.5.0.0")]
17
+ [assembly: AssemblyVersion("1.6.0.0")]
18
18
 
19
19
  namespace DrumBuddySetup
20
20
  {
@@ -22,6 +22,8 @@ namespace DrumBuddySetup
22
22
  {
23
23
  public static readonly string InstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"Programs","DrumBuddy");
24
24
  public const string RegistryKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\DrumBuddy";
25
+ private static string BrandIcon { get { return Path.Combine(InstallPath,"Flux-" + Assembly.GetExecutingAssembly().GetName().Version.ToString(3) + ".ico"); } }
26
+ [System.Runtime.InteropServices.DllImport("shell32.dll")] private static extern void SHChangeNotify(uint eventId, uint flags, IntPtr item1, IntPtr item2);
25
27
  private static Window window;
26
28
  private static bool working, installed;
27
29
  private static T C<T>(string name) where T : FrameworkElement { return (T)window.FindName(name); }
@@ -42,6 +44,7 @@ namespace DrumBuddySetup
42
44
  var app = new Application();
43
45
  using (var s = Assembly.GetExecutingAssembly().GetManifestResourceStream("Setup.xaml")) window = (Window)XamlReader.Load(s);
44
46
  C<Image>("SetupLogo").Source = DrumBuddy.BrandArtwork.Raster();
47
+ window.Icon = DrumBuddy.BrandArtwork.Raster();
45
48
  window.Height = Math.Min(window.Height,SystemParameters.WorkArea.Height - 30);
46
49
  C<Button>("InstallButton").Click += async delegate {
47
50
  if (installed) { LaunchApp(); window.Close(); return; }
@@ -98,7 +101,7 @@ namespace DrumBuddySetup
98
101
  Directory.CreateDirectory(Path.GetDirectoryName(destination));
99
102
  File.Copy(source,destination,true);
100
103
  }
101
- File.WriteAllText(Path.Combine(InstallPath,".drumbuddy-installed"),"Flux 1.5.0\n" + InstallPath,Encoding.UTF8);
104
+ File.WriteAllText(Path.Combine(InstallPath,".drumbuddy-installed"),"Flux 1.6.0\n" + InstallPath,Encoding.UTF8);
102
105
  string[] files = Directory.GetFiles(stage,"*",SearchOption.AllDirectories).Select(f => f.Substring(stage.Length + 1)).Concat(new[] { ".drumbuddy-installed","installed-files.txt" }).ToArray();
103
106
  File.WriteAllLines(Path.Combine(InstallPath,"installed-files.txt"),files,Encoding.UTF8);
104
107
  string exe = Path.Combine(InstallPath,"DrumBuddy.exe");
@@ -109,13 +112,14 @@ namespace DrumBuddySetup
109
112
  RemoveLegacyShortcut(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu),"Programs","Drum Buddy.lnk"),exe);
110
113
  RemoveLegacyShortcut(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),"Drum Buddy.lnk"),exe);
111
114
  using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(RegistryKey)) {
112
- key.SetValue("DisplayName","Flux"); key.SetValue("DisplayVersion","1.5.0"); key.SetValue("Publisher","Flux");
113
- key.SetValue("InstallLocation",InstallPath); key.SetValue("DisplayIcon",exe);
115
+ key.SetValue("DisplayName","Flux"); key.SetValue("DisplayVersion","1.6.0"); key.SetValue("Publisher","Flux");
116
+ key.SetValue("InstallLocation",InstallPath); key.SetValue("DisplayIcon",BrandIcon);
114
117
  key.SetValue("UninstallString","\"" + Path.Combine(InstallPath,"Uninstall.exe") + "\"");
115
118
  key.SetValue("NoModify",1); key.SetValue("NoRepair",1);
116
119
  long size = Directory.GetFiles(InstallPath,"*",SearchOption.AllDirectories).Sum(f => new FileInfo(f).Length);
117
120
  key.SetValue("EstimatedSize",(int)(size / 1024));
118
121
  }
122
+ SHChangeNotify(0x08000000,0,IntPtr.Zero,IntPtr.Zero);
119
123
  }
120
124
  finally { try { if (Directory.Exists(stage)) Directory.Delete(stage,true); } catch { } }
121
125
  }
@@ -180,7 +184,7 @@ namespace DrumBuddySetup
180
184
  }
181
185
  type.InvokeMember("TargetPath",BindingFlags.SetProperty,null,link,new object[] { exe });
182
186
  type.InvokeMember("WorkingDirectory",BindingFlags.SetProperty,null,link,new object[] { Path.GetDirectoryName(exe) });
183
- type.InvokeMember("IconLocation",BindingFlags.SetProperty,null,link,new object[] { exe + ",0" });
187
+ type.InvokeMember("IconLocation",BindingFlags.SetProperty,null,link,new object[] { BrandIcon + ",0" });
184
188
  type.InvokeMember("Save",BindingFlags.InvokeMethod,null,link,null);
185
189
  }
186
190
  finally { System.Runtime.InteropServices.Marshal.FinalReleaseComObject(link); }
@@ -1,4 +1,4 @@
1
- Flux 1.5.0 - third-party components
1
+ Flux 1.6.0 - third-party components
2
2
 
3
3
  FFmpeg 9.0.2, release essentials build by Gyan Doshi
4
4
  Used as a separate command-line decoding process.
@@ -20,8 +20,9 @@ Copyright (c) .NET Foundation and Contributors. MIT license.
20
20
  https://github.com/dotnet/runtime/blob/main/LICENSE.TXT
21
21
 
22
22
  Zoom H5studio and R16/R24 drivers: linked from Zoom's official support pages, not bundled.
23
- R16 reference panel image: copyright Zoom Corporation. The npm installer
24
- fetches the image directly from Zoom's public product website.
23
+ R16 reference panel image: copyright Zoom Corporation. The reference image
24
+ is included from Zoom's public product website; its source URL and SHA-256
25
+ are recorded in dependency-lock.json.
25
26
  Zoom and R16 are trademarks of Zoom Corporation. This is an independent app.
26
27
 
27
28
  The original drummer mascot was generated for Drum Buddy using OpenAI's