@pro-gram/flux 0.0.0-stage → 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.
- package/LICENSE +23 -0
- package/NOTICE.txt +33 -0
- package/README.md +125 -2
- package/bin/flux.js +29 -0
- package/dependencies.json +15 -0
- package/package.json +60 -6
- package/payload/DrumBuddy-Setup.exe +4 -0
- package/scripts/install.js +60 -0
- package/scripts/version.js +12 -0
- package/source/AssetBootstrap.cs +49 -0
- package/source/Audio Decoders.txt +230 -0
- package/source/Audio.cs +453 -0
- package/source/AudioDeviceSettings.cs +126 -0
- package/source/AudioEndpointWatcher.cs +25 -0
- package/source/AudioInputs.cs +50 -0
- package/source/AudioOutputs.cs +307 -0
- package/source/AudioWorkspace.cs +72 -0
- package/source/BrandArtwork.cs +71 -0
- package/source/Build Instructions.txt +65 -0
- package/source/Build.ps1 +135 -0
- package/source/ControlMode.cs +687 -0
- package/source/ControlTests.cs +88 -0
- package/source/ConversionFormats.cs +59 -0
- package/source/ConversionTests.cs +66 -0
- package/source/DockablePane.cs +95 -0
- package/source/DriverSetup.cs +28 -0
- package/source/EditorCompactLayout.cs +36 -0
- package/source/EditorPlaybackTests.cs +87 -0
- package/source/EditorSelectionTests.cs +73 -0
- package/source/EditorToolInteractions.cs +227 -0
- package/source/EditorTools.cs +78 -0
- package/source/EditorToolsTests.cs +93 -0
- package/source/EditorView.cs +798 -0
- package/source/Fetch-Dependencies.ps1 +15 -0
- package/source/FloatRecording.cs +99 -0
- package/source/FloatRecordingTests.cs +170 -0
- package/source/Flux-Logo.png +0 -0
- package/source/Flux-Mark.svg +49 -0
- package/source/FluxTempo Logo Prompt.txt +3 -0
- package/source/FluxTempo-Mark.svg +49 -0
- package/source/HomeView.cs +32 -0
- package/source/InterfaceSettings.cs +225 -0
- package/source/InterfaceTests.cs +172 -0
- package/source/LICENSE.txt +23 -0
- package/source/Logo Prompt.txt +16 -0
- package/source/Main.cs +787 -0
- package/source/Main.xaml +289 -0
- package/source/MediaPlayerView.cs +274 -0
- package/source/MidiControl.cs +113 -0
- package/source/MidiPerformance.cs +125 -0
- package/source/Mixer.cs +280 -0
- package/source/NativeMemory.cs +165 -0
- package/source/PanelFeedback.cs +75 -0
- package/source/PlaybackTests.cs +141 -0
- package/source/Quick Start.txt +96 -0
- package/source/SelfTest.cs +78 -0
- package/source/SettingsWindow.cs +209 -0
- package/source/Setup.cs +196 -0
- package/source/Setup.xaml +28 -0
- package/source/ShellStyles.xaml +99 -0
- package/source/TempoGrid.cs +95 -0
- package/source/TempoGridTests.cs +108 -0
- package/source/Third Party Notices.txt +33 -0
- package/source/Timeline.cs +268 -0
- package/source/TimelineGestures.cs +57 -0
- package/source/TimelineZoom.cs +21 -0
- package/source/Uninstall.cs +69 -0
- package/source/WaveformDetail.cs +104 -0
- package/source/WindowsAudioPlayer.cs +137 -0
- package/source/WorkspaceTests.cs +202 -0
- package/source/ZoomTests.cs +95 -0
- package/source/app.manifest +7 -0
- package/source/brand-archive/Drum-Buddy-Black-Hair.png +0 -0
- package/source/dependency-lock.json +62 -0
- package/source/fluxtempo-mark.png +0 -0
- package/source/logo.png +0 -0
- package/source/r16-panel.png +0 -0
- package/source/references/R16 Format Research.txt +16 -0
- package/test/package.test.js +55 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.IO;
|
|
3
|
+
using System.Linq;
|
|
4
|
+
using System.Text;
|
|
5
|
+
using System.Collections.Generic;
|
|
6
|
+
using System.Windows;
|
|
7
|
+
using System.Windows.Input;
|
|
8
|
+
using System.Windows.Media;
|
|
9
|
+
using System.Xml.Serialization;
|
|
10
|
+
using System.Windows.Markup;
|
|
11
|
+
using System.Windows.Controls;
|
|
12
|
+
using System.Windows.Controls.Primitives;
|
|
13
|
+
using System.Windows.Data;
|
|
14
|
+
using System.Runtime.InteropServices;
|
|
15
|
+
using System.Windows.Interop;
|
|
16
|
+
|
|
17
|
+
namespace DrumBuddy
|
|
18
|
+
{
|
|
19
|
+
public sealed class ShortcutBinding
|
|
20
|
+
{
|
|
21
|
+
public string Action, Key;
|
|
22
|
+
public int Modifiers;
|
|
23
|
+
public ShortcutBinding Copy() { return new ShortcutBinding { Action = Action, Key = Key, Modifiers = Modifiers }; }
|
|
24
|
+
public string Gesture {
|
|
25
|
+
get {
|
|
26
|
+
if (string.IsNullOrEmpty(Key) || Key == "None") return "Unassigned";
|
|
27
|
+
string text = ""; var mods = (ModifierKeys)Modifiers;
|
|
28
|
+
if ((mods & ModifierKeys.Control) != 0) text += "Ctrl+";
|
|
29
|
+
if ((mods & ModifierKeys.Alt) != 0) text += "Alt+";
|
|
30
|
+
if ((mods & ModifierKeys.Shift) != 0) text += "Shift+";
|
|
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);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
public sealed class InterfaceSettings
|
|
37
|
+
{
|
|
38
|
+
public string Theme = "Dark", Startup = "Editor", AudioOutputKey = "default", AudioInputKey;
|
|
39
|
+
public int PlaybackLatency = 80;
|
|
40
|
+
public int KeymapRevision;
|
|
41
|
+
public TempoGridOptions Grid = new TempoGridOptions();
|
|
42
|
+
public bool SnapToGrid = true, SnapToEdges = true;
|
|
43
|
+
public List<ShortcutBinding> Keybinds = new List<ShortcutBinding>();
|
|
44
|
+
public InterfaceSettings Copy()
|
|
45
|
+
{ return new InterfaceSettings { Theme = Theme, Startup = Startup, AudioOutputKey = AudioOutputKey, AudioInputKey = AudioInputKey, PlaybackLatency = PlaybackLatency, KeymapRevision = KeymapRevision, Grid = Grid.Copy(), SnapToGrid = SnapToGrid, SnapToEdges = SnapToEdges, Keybinds = Keybinds.Select(b => b.Copy()).ToList() }; }
|
|
46
|
+
public string Signature()
|
|
47
|
+
{ return Theme + "|" + Startup + "|" + AudioOutputKey + "|" + AudioInputKey + "|" + PlaybackLatency + "|" + Grid.Signature() + "|" + SnapToGrid + "|" + SnapToEdges + "|" + string.Join(";", Keybinds.OrderBy(b => b.Action).Select(b => b.Action + ":" + b.Key + ":" + b.Modifiers)); }
|
|
48
|
+
}
|
|
49
|
+
public static class InterfacePreferences
|
|
50
|
+
{
|
|
51
|
+
public static InterfaceSettings Current = Defaults();
|
|
52
|
+
public static event Action Changed;
|
|
53
|
+
public static readonly string SettingsFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DrumBuddy", "interface-settings.xml");
|
|
54
|
+
public static readonly Dictionary<string, string> Actions = new Dictionary<string, string> {
|
|
55
|
+
{ "play", "Play / pause" }, { "stop", "Stop playback" }, { "record", "Start / stop recording" },
|
|
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" },
|
|
58
|
+
{ "cut", "Cut selection" }, { "paste", "Paste at playhead" }, { "delete", "Delete selection" },
|
|
59
|
+
{ "deleteAlternate", "Delete selection (alternate)" },
|
|
60
|
+
{ "undo", "Undo" }, { "redo", "Redo" }, { "save", "Save session" }, { "saveAs", "Save session as" },
|
|
61
|
+
{ "open", "Open session" }, { "new", "New session" }, { "import", "Import audio" }, { "export", "Export mix" },
|
|
62
|
+
{ "start", "Go to start" }, { "end", "Go to end" }, { "home", "Open Home" }, { "editor", "Open Editor" },
|
|
63
|
+
{ "converter", "Open Converter" }, { "player", "Open Media Player" }, { "settings", "Open 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" }
|
|
65
|
+
};
|
|
66
|
+
public static InterfaceSettings Defaults()
|
|
67
|
+
{
|
|
68
|
+
var settings = new InterfaceSettings { KeymapRevision = 1 };
|
|
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();
|
|
72
|
+
foreach (string text in simple) {
|
|
73
|
+
string[] parts = text.Split(':');
|
|
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 });
|
|
75
|
+
}
|
|
76
|
+
return settings;
|
|
77
|
+
}
|
|
78
|
+
public static void Load(string legacyOutput, string legacyInput)
|
|
79
|
+
{
|
|
80
|
+
var settings = Defaults();
|
|
81
|
+
try {
|
|
82
|
+
using (var file = File.OpenRead(SettingsFile)) settings = (InterfaceSettings)new XmlSerializer(typeof(InterfaceSettings)).Deserialize(file);
|
|
83
|
+
Upgrade(settings);
|
|
84
|
+
Validate(settings);
|
|
85
|
+
}
|
|
86
|
+
catch { settings = Defaults(); settings.AudioOutputKey = legacyOutput ?? "default"; settings.AudioInputKey = legacyInput; }
|
|
87
|
+
settings.AudioOutputKey = AudioOutputs.NormalizeKey(settings.AudioOutputKey);
|
|
88
|
+
Current = settings; ThemePalette.Apply(settings.Theme == "Dark"); AudioOutputs.LatencyMilliseconds = settings.PlaybackLatency;
|
|
89
|
+
}
|
|
90
|
+
public static void Validate(InterfaceSettings settings)
|
|
91
|
+
{
|
|
92
|
+
if (settings.Grid == null) settings.Grid = new TempoGridOptions(); settings.Grid.Validate();
|
|
93
|
+
if (settings.Theme != "Dark" && settings.Theme != "Light") throw new IOException("Choose Dark or Light theme.");
|
|
94
|
+
if (settings.Startup != "Editor" && settings.Startup != "Home") throw new IOException("Choose Editor or Home as the startup screen.");
|
|
95
|
+
if (settings.PlaybackLatency < 40 || settings.PlaybackLatency > 250) throw new IOException("Choose a playback buffer between 40 and 250 milliseconds.");
|
|
96
|
+
var used = new Dictionary<string, string>();
|
|
97
|
+
foreach (var binding in settings.Keybinds) {
|
|
98
|
+
if (!Actions.ContainsKey(binding.Action)) continue;
|
|
99
|
+
Key key;
|
|
100
|
+
if (string.IsNullOrEmpty(binding.Key) || binding.Key == "None") continue;
|
|
101
|
+
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;
|
|
103
|
+
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
|
+
used[signature] = binding.Action;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
public static void Upgrade(InterfaceSettings settings)
|
|
108
|
+
{
|
|
109
|
+
settings.AudioOutputKey = AudioOutputs.NormalizeKey(settings.AudioOutputKey);
|
|
110
|
+
if (settings.Grid == null) settings.Grid = new TempoGridOptions();
|
|
111
|
+
if (settings.Keybinds == null) settings.Keybinds = new List<ShortcutBinding>();
|
|
112
|
+
if (settings.KeymapRevision < 1) {
|
|
113
|
+
var oldDelete = settings.Keybinds.FirstOrDefault(b => b.Action == "delete");
|
|
114
|
+
if (oldDelete != null && oldDelete.Key == "Delete" && oldDelete.Modifiers == 0 && !settings.Keybinds.Any(b => b.Action != "delete" && b.Key == "Back" && b.Modifiers == 0)) oldDelete.Key = "Back";
|
|
115
|
+
settings.KeymapRevision = 1;
|
|
116
|
+
}
|
|
117
|
+
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; }
|
|
119
|
+
settings.Keybinds.Add(binding);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
public static void Save(InterfaceSettings settings)
|
|
123
|
+
{
|
|
124
|
+
SaveToFile(settings, SettingsFile);
|
|
125
|
+
Current = settings.Copy(); ThemePalette.Apply(settings.Theme == "Dark"); AudioOutputs.LatencyMilliseconds = settings.PlaybackLatency;
|
|
126
|
+
var changed = Changed; if (changed != null) changed();
|
|
127
|
+
}
|
|
128
|
+
public static void SaveToFile(InterfaceSettings settings, string path)
|
|
129
|
+
{
|
|
130
|
+
Validate(settings); Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
|
|
131
|
+
string temporary = path + "." + Guid.NewGuid().ToString("N") + ".tmp";
|
|
132
|
+
try {
|
|
133
|
+
using (var file = File.Create(temporary)) new XmlSerializer(typeof(InterfaceSettings)).Serialize(file, settings);
|
|
134
|
+
if (File.Exists(path)) File.Replace(temporary, path, null); else File.Move(temporary, path);
|
|
135
|
+
}
|
|
136
|
+
finally { if (File.Exists(temporary)) File.Delete(temporary); }
|
|
137
|
+
}
|
|
138
|
+
public static bool IsModifier(Key key)
|
|
139
|
+
{ 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; }
|
|
140
|
+
public static string Resolve(Key key, ModifierKeys modifiers)
|
|
141
|
+
{
|
|
142
|
+
var binding = Current.Keybinds.FirstOrDefault(b => b.Key == key.ToString() && b.Modifiers == (int)modifiers);
|
|
143
|
+
return binding == null ? null : binding.Action;
|
|
144
|
+
}
|
|
145
|
+
public static string Gesture(string action)
|
|
146
|
+
{ var binding = Current.Keybinds.FirstOrDefault(b => b.Action == action); return binding == null ? "Unassigned" : binding.Gesture; }
|
|
147
|
+
}
|
|
148
|
+
public static class ThemePalette
|
|
149
|
+
{
|
|
150
|
+
[DllImport("dwmapi.dll")] private static extern int DwmSetWindowAttribute(IntPtr window, int attribute, ref int value, int size);
|
|
151
|
+
private static void Caption(Window window)
|
|
152
|
+
{ try { var handle = new WindowInteropHelper(window).Handle; if (handle != IntPtr.Zero) { int dark = Dark ? 1 : 0; if (DwmSetWindowAttribute(handle, 20, ref dark, 4) != 0) DwmSetWindowAttribute(handle, 19, ref dark, 4); } } catch { } }
|
|
153
|
+
private static readonly Dictionary<string, SolidColorBrush> brushes = new Dictionary<string, SolidColorBrush>(StringComparer.OrdinalIgnoreCase);
|
|
154
|
+
private static readonly Dictionary<string, PaletteColor> colorValues = new Dictionary<string, PaletteColor>(StringComparer.OrdinalIgnoreCase);
|
|
155
|
+
private sealed class PaletteColor : DependencyObject
|
|
156
|
+
{
|
|
157
|
+
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(Color), typeof(PaletteColor));
|
|
158
|
+
public Color Value { get { return (Color)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } }
|
|
159
|
+
}
|
|
160
|
+
public static bool Dark = true;
|
|
161
|
+
private static readonly Dictionary<string, string> dark = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) {
|
|
162
|
+
{ "#FFFFFF", "#171A22" }, { "#F3F5F5", "#0D0F14" }, { "#182C37", "#E9EDF7" }, { "#526771", "#9AA4BA" },
|
|
163
|
+
{ "#3F625F", "#A0B9D0" }, { "#7EA7A4", "#435577" }, { "#F0F5F4", "#171C29" },
|
|
164
|
+
{ "#006D68", "#4FBCFF" }, { "#007A6B", "#4FBCFF" }, { "#E6ECEC", "#201E31" }, { "#C3CED0", "#32394D" },
|
|
165
|
+
{ "#DFE5E6", "#282D3D" }, { "#DCE5E4", "#282D3D" }, { "#D4DFDF", "#282D3D" }, { "#EAF5F2", "#111C2B" },
|
|
166
|
+
{ "#EAF6F3", "#141C2C" }, { "#F7FAF9", "#10131B" }, { "#FCFDFC", "#151923" }, { "#F4F8F6", "#12151E" },
|
|
167
|
+
{ "#F0F8F5", "#192139" }, { "#E4ECEA", "#202536" }, { "#DCEFEB", "#1C2943" }, { "#EDF3F1", "#171B26" },
|
|
168
|
+
{ "#D6E1DF", "#2C3349" }, { "#E0E8E5", "#242B3C" }, { "#DAE5E0", "#2A2B43" }, { "#B9DDD2", "#334567" },
|
|
169
|
+
{ "#438B77", "#59CCFF" }, { "#5E9D89", "#7D9CFA" }, { "#CBD8D4", "#353951" }, { "#D0772C", "#FFB55E" },
|
|
170
|
+
{ "#DAD5FA", "#3A315D" }, { "#7359C7", "#B497FF" },
|
|
171
|
+
{ "#D17626", "#FFB55E" }, { "#277FA5", "#64CBFF" }, { "#386BA8", "#A28CFF" }, { "#5596C8", "#6BAAFF" },
|
|
172
|
+
{ "#15877C", "#45B8F0" }, { "#16887D", "#45B8F0" }, { "#B52C36", "#EE657D" }, { "#D16A19", "#B3A0FF" }
|
|
173
|
+
};
|
|
174
|
+
public static Brush Brush(string value)
|
|
175
|
+
{
|
|
176
|
+
value = value == "White" ? "#FFFFFF" : value;
|
|
177
|
+
SolidColorBrush brush;
|
|
178
|
+
if (!brushes.TryGetValue(value, out brush)) {
|
|
179
|
+
var state = new PaletteColor { Value = Color(value) }; colorValues[value] = state;
|
|
180
|
+
brush = new SolidColorBrush();
|
|
181
|
+
BindingOperations.SetBinding(brush, SolidColorBrush.ColorProperty, new Binding("Value") { Source = state, Mode = BindingMode.OneWay });
|
|
182
|
+
brushes[value] = brush;
|
|
183
|
+
}
|
|
184
|
+
return brush;
|
|
185
|
+
}
|
|
186
|
+
private static Color Color(string value)
|
|
187
|
+
{ string mapped; return (Color)ColorConverter.ConvertFromString(Dark && dark.TryGetValue(value, out mapped) ? mapped : value); }
|
|
188
|
+
public static void Apply(bool isDark)
|
|
189
|
+
{
|
|
190
|
+
Dark = isDark; foreach (var item in colorValues) item.Value.Value = Color(item.Key);
|
|
191
|
+
if (Application.Current != null) InstallResources(Application.Current.Resources);
|
|
192
|
+
}
|
|
193
|
+
public static void InstallResources(ResourceDictionary resources)
|
|
194
|
+
{
|
|
195
|
+
string[] colors = { "#FFFFFF", "#F3F5F5", "#182C37", "#526771", "#006D68", "#E6ECEC", "#C3CED0", "#DFE5E6", "#D4DFDF", "#EAF6F3", "#D16A19" };
|
|
196
|
+
foreach (string color in colors.Concat(dark.Keys).Distinct()) resources["Theme" + color.Substring(1)] = Brush(color);
|
|
197
|
+
resources["ThemeOnAccent"] = new SolidColorBrush(Dark ? Colors.Black : Colors.White);
|
|
198
|
+
var accent = new LinearGradientBrush { StartPoint = new Point(0, .5), EndPoint = new Point(1, .5) };
|
|
199
|
+
accent.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString(Dark ? "#4FBCFF" : "#006D88"), 0));
|
|
200
|
+
accent.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString(Dark ? "#A895FF" : "#6D51AE"), 1));
|
|
201
|
+
resources["ThemeAccent"] = accent;
|
|
202
|
+
resources[SystemColors.WindowBrushKey] = Brush("#FFFFFF"); resources[SystemColors.ControlBrushKey] = Brush("#FFFFFF");
|
|
203
|
+
resources[SystemColors.WindowTextBrushKey] = Brush("#182C37"); resources[SystemColors.ControlTextBrushKey] = Brush("#182C37");
|
|
204
|
+
resources[SystemColors.MenuBrushKey] = Brush("#F3F5F5"); resources[SystemColors.MenuTextBrushKey] = Brush("#182C37");
|
|
205
|
+
resources[SystemColors.HighlightBrushKey] = Brush("#006D68"); resources[SystemColors.HighlightTextBrushKey] = resources["ThemeOnAccent"];
|
|
206
|
+
}
|
|
207
|
+
public static void Install(Window window)
|
|
208
|
+
{
|
|
209
|
+
InstallResources(window.Resources);
|
|
210
|
+
if (!window.Resources.Contains("FluxShellStyles")) {
|
|
211
|
+
using (var file = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("ShellStyles.xaml")) {
|
|
212
|
+
if (file != null) {
|
|
213
|
+
var styles = (ResourceDictionary)XamlReader.Load(file);
|
|
214
|
+
foreach (var type in new[] { typeof(Button), typeof(ToggleButton), typeof(ComboBox), typeof(ComboBoxItem), typeof(TextBox), typeof(MenuItem), typeof(ScrollBar), typeof(ListBox), typeof(ListBoxItem), typeof(CheckBox), typeof(RadioButton), typeof(Slider), typeof(ProgressBar) }) window.Resources[type] = styles[type];
|
|
215
|
+
}
|
|
216
|
+
else throw new IOException("The interface theme resource is missing from the app.");
|
|
217
|
+
}
|
|
218
|
+
window.Resources["FluxShellStyles"] = true;
|
|
219
|
+
window.SourceInitialized += delegate { Caption(window); };
|
|
220
|
+
}
|
|
221
|
+
window.Background = Brush("#F3F5F5"); window.Foreground = Brush("#182C37");
|
|
222
|
+
Caption(window);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.IO;
|
|
3
|
+
using System.Linq;
|
|
4
|
+
using System.Text;
|
|
5
|
+
using System.Threading;
|
|
6
|
+
using System.Threading.Tasks;
|
|
7
|
+
using System.Collections.Generic;
|
|
8
|
+
using System.Windows;
|
|
9
|
+
using System.Windows.Controls;
|
|
10
|
+
using System.Windows.Input;
|
|
11
|
+
using System.Windows.Media;
|
|
12
|
+
using System.Windows.Media.Imaging;
|
|
13
|
+
using System.Windows.Threading;
|
|
14
|
+
using System.Xml.Serialization;
|
|
15
|
+
|
|
16
|
+
namespace DrumBuddy
|
|
17
|
+
{
|
|
18
|
+
public static class InterfaceTests
|
|
19
|
+
{
|
|
20
|
+
private static readonly List<string> log = new List<string>();
|
|
21
|
+
private static void Check(bool value, string message) { if (!value) throw new Exception(message); log.Add("PASS " + message); }
|
|
22
|
+
private static IEnumerable<Button> Buttons(DependencyObject node)
|
|
23
|
+
{
|
|
24
|
+
var button = node as Button; if (button != null) yield return button;
|
|
25
|
+
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(node); i++) foreach (var child in Buttons(VisualTreeHelper.GetChild(node, i))) yield return child;
|
|
26
|
+
}
|
|
27
|
+
private static void Picture(FrameworkElement view, string path, int width, int height)
|
|
28
|
+
{
|
|
29
|
+
view.Measure(new Size(width, height)); view.Arrange(new Rect(0, 0, width, height)); view.UpdateLayout();
|
|
30
|
+
var image = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
|
|
31
|
+
var background = new DrawingVisual(); using (var context = background.RenderOpen()) context.DrawRectangle(ThemePalette.Brush("#F3F5F5"), null, new Rect(0, 0, width, height));
|
|
32
|
+
image.Render(background); image.Render(view); var encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(image)); using (var file = File.Create(path)) encoder.Save(file);
|
|
33
|
+
}
|
|
34
|
+
private static void RunOnDispatcher(Func<Task> action)
|
|
35
|
+
{
|
|
36
|
+
var previous = SynchronizationContext.Current; var dispatcher = Dispatcher.CurrentDispatcher;
|
|
37
|
+
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(dispatcher));
|
|
38
|
+
try {
|
|
39
|
+
var task = action(); var frame = new DispatcherFrame();
|
|
40
|
+
task.ContinueWith(delegate { dispatcher.BeginInvoke((Action)delegate { frame.Continue = false; }); });
|
|
41
|
+
if (!task.IsCompleted) Dispatcher.PushFrame(frame); task.GetAwaiter().GetResult();
|
|
42
|
+
}
|
|
43
|
+
finally { SynchronizationContext.SetSynchronizationContext(previous); }
|
|
44
|
+
}
|
|
45
|
+
public static int Run(string[] args)
|
|
46
|
+
{
|
|
47
|
+
string root = Path.GetFullPath(args.Length > 0 ? args[0] : Path.Combine(Path.GetTempPath(), "FluxInterface_" + Guid.NewGuid().ToString("N")));
|
|
48
|
+
Directory.CreateDirectory(root); log.Clear(); var old = InterfacePreferences.Current; string oldOutput = AudioOutputs.PreferredKey;
|
|
49
|
+
try {
|
|
50
|
+
InterfacePreferences.Current = InterfacePreferences.Defaults(); AudioOutputs.PreferredKey = "default"; ThemePalette.Apply(true);
|
|
51
|
+
InterfacePreferences.Validate(InterfacePreferences.Current);
|
|
52
|
+
Check(InterfacePreferences.Current.Startup == "Editor" && InterfacePreferences.Current.Theme == "Dark", "Default to the Editor and dark theme");
|
|
53
|
+
Check(InterfacePreferences.Resolve(Key.S, ModifierKeys.None) == "split" && InterfacePreferences.Resolve(Key.C, ModifierKeys.Control) == "copy" && InterfacePreferences.Resolve(Key.V, ModifierKeys.Control) == "paste", "Keep S split and normal Ctrl+C / Ctrl+V defaults");
|
|
54
|
+
var conflict = InterfacePreferences.Defaults(); conflict.Keybinds.First(b => b.Action == "split").Key = "R"; bool rejected = false;
|
|
55
|
+
try { InterfacePreferences.Validate(conflict); } catch (IOException) { rejected = true; }
|
|
56
|
+
Check(rejected, "Reject duplicate shortcuts before saving settings");
|
|
57
|
+
string settingsFile = Path.Combine(root, "interface-settings.xml");
|
|
58
|
+
var settings = new SettingsWindow(null, "keybinds", false, value => InterfacePreferences.SaveToFile(value, settingsFile));
|
|
59
|
+
Picture((FrameworkElement)settings.Content, Path.Combine(root, "settings-keybinds-dark.png"), 920, 640);
|
|
60
|
+
Check(settings.CaptureForTest("split", Key.F9, ModifierKeys.Control), "Edit immediately selects a capture field, waits for keys, and replaces the binding");
|
|
61
|
+
Check(settings.HasUnsavedChanges, "Mark an edited shortcut as unsaved");
|
|
62
|
+
Check(settings.LeaveForTest(true) && !settings.HasUnsavedChanges && File.Exists(settingsFile), "Yes saves pending keybind settings before exit");
|
|
63
|
+
InterfaceSettings saved;
|
|
64
|
+
using (var file = File.OpenRead(settingsFile)) saved = (InterfaceSettings)new XmlSerializer(typeof(InterfaceSettings)).Deserialize(file);
|
|
65
|
+
Check(saved.Keybinds.First(b => b.Action == "split").Gesture == "Ctrl+F9", "Persist the captured shortcut to the settings file");
|
|
66
|
+
var changed = saved.Copy(); changed.Theme = "Light"; settings.SetDraftForTest(changed); byte[] before = File.ReadAllBytes(settingsFile);
|
|
67
|
+
Check(settings.HasUnsavedChanges && settings.LeaveForTest(false) && before.SequenceEqual(File.ReadAllBytes(settingsFile)), "No exits with pending changes without saving them");
|
|
68
|
+
settings.SetDraftForTest(saved); Check(settings.CaptureForTest("split", Key.C, ModifierKeys.Control), "Capture a shortcut containing a standard modifier combination");
|
|
69
|
+
Check(!settings.LeaveForTest(true) && settings.HasUnsavedChanges, "A conflicting shortcut prevents the Save-and-exit path from closing settings");
|
|
70
|
+
settings.SetDraftForTest(saved); settings.Close();
|
|
71
|
+
var text = (SolidColorBrush)ThemePalette.Brush("#182C37"); var dark = text.Color; ThemePalette.Apply(false);
|
|
72
|
+
Check(text.Color != dark && text.Color == (Color)ColorConverter.ConvertFromString("#182C37"), "Switch the same live brush from dark to light without rebuilding controls");
|
|
73
|
+
ThemePalette.Apply(true);
|
|
74
|
+
Picture(new Image { Source = BrandArtwork.Load(), Stretch = Stretch.Uniform }, Path.Combine(root, "refined-vector-logo.png"), 840, 640);
|
|
75
|
+
var main = new MainWindow(Path.Combine(root, "legacy-preferences.xml"), true);
|
|
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");
|
|
78
|
+
main.OpenEditorForTest(); Check(main.IntegratedRecorderForTest, "Mount recording controls inside the main Editor while retaining named input controls");
|
|
79
|
+
string demoPath = Path.Combine(root, "Generated demo.wav"); var demo = new float[Audio.Rate * 8];
|
|
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));
|
|
81
|
+
using (var writer = new FloatWaveWriter(demoPath, 1, Audio.Rate)) writer.Write(demo, demo.Length, 1);
|
|
82
|
+
var demoClip = Audio.Load(demoPath, Path.Combine(root, "demo-cache"), CancellationToken.None, null);
|
|
83
|
+
var demoSession = new EditorSession { Name = "Generated demo" };
|
|
84
|
+
string[] names = { "Kick", "Snare", "Hi-hat", "Toms", "Overhead L", "Overhead R", "Room", "Guide" };
|
|
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 }); }
|
|
86
|
+
main.EditorFixtureForTest(demoSession, new Dictionary<string, Clip> { { demoClip.Source, demoClip } });
|
|
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");
|
|
89
|
+
Check(main.NativeWindow.FindName("SettingsTimeline") != null, "Expose Timeline / grid in Settings");
|
|
90
|
+
var initialGrid = InterfacePreferences.Current.Copy(); initialGrid.Grid = main.GridForTest;
|
|
91
|
+
string gridSettingsFile = Path.Combine(root, "saved-grid-settings.xml");
|
|
92
|
+
var gridSettings = new SettingsWindow(null, "timeline", false, value => { InterfacePreferences.SaveToFile(value, gridSettingsFile); main.ApplySettingsForTest(value); }, initialGrid);
|
|
93
|
+
Picture((FrameworkElement)gridSettings.Content, Path.Combine(root, "settings-timeline-dark.png"), 920, 640);
|
|
94
|
+
var gridDraft = initialGrid.Copy(); gridDraft.Grid.Tempo = 135; gridDraft.Grid.BeatsPerBar = 7; gridDraft.Grid.BeatUnit = 8; gridDraft.Grid.Division = 32; gridDraft.SnapToEdges = false;
|
|
95
|
+
gridSettings.SetDraftForTest(gridDraft);
|
|
96
|
+
Check(gridSettings.HasUnsavedChanges && gridSettings.LeaveForTest(false) && !File.Exists(gridSettingsFile) && main.GridForTest.Tempo == 120, "No exits pending timeline changes without applying them");
|
|
97
|
+
gridDraft.Grid.Tempo = double.NaN; gridSettings.SetDraftForTest(gridDraft);
|
|
98
|
+
Check(!gridSettings.LeaveForTest(true) && !File.Exists(gridSettingsFile), "Invalid BPM blocks saving and leaving timeline settings");
|
|
99
|
+
gridDraft.Grid.Tempo = 135; gridSettings.SetDraftForTest(gridDraft);
|
|
100
|
+
Check(gridSettings.LeaveForTest(true) && main.GridForTest.Tempo == 135 && main.GridForTest.BeatUnit == 8 && !InterfacePreferences.Current.SnapToEdges, "Save applies project tempo, time signature, division and independent snapping");
|
|
101
|
+
gridSettings.Close();
|
|
102
|
+
main.EditorFixtureForTest(demoSession, new Dictionary<string, Clip> { { demoClip.Source, demoClip } });
|
|
103
|
+
var appearance = InterfacePreferences.Current.Copy(); appearance.Theme = "Light"; main.ApplySettingsForTest(appearance);
|
|
104
|
+
Check(main.GridForTest.Tempo == 120, "Changing appearance preserves a project's grid when it differs from saved defaults");
|
|
105
|
+
main.ApplySettingsForTest(InterfacePreferences.Defaults()); main.OpenEditorForTest();
|
|
106
|
+
main.SetGridForTest(new TempoGridOptions { Division = 32 }, 320); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "editor-grid-32-dark.png"), 1280, 800);
|
|
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);
|
|
108
|
+
main.EditorFixtureForTest(demoSession, new Dictionary<string, Clip> { { demoClip.Source, demoClip } }); main.SetGridForTest(demoSession.Grid, 40); main.OpenEditorForTest();
|
|
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.");
|
|
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.");
|
|
115
|
+
main.RecordingPanelForTest(); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "editor-record-dark.png"), 1280, 800);
|
|
116
|
+
Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "editor-record-compact-dark.png"), 850, 560);
|
|
117
|
+
main.RecordingPaneForTest.SetBottom(true); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "editor-record-bottom-compact-dark.png"), 850, 560);
|
|
118
|
+
Check(main.RecordingPaneForTest.MainContent.ActualHeight >= 239, "Keep room for the editor when bottom recording options are constrained by a small window");
|
|
119
|
+
Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "editor-record-bottom-dark.png"), 1280, 800);
|
|
120
|
+
Check(main.RecordingPaneForTest.OnBottom && main.RecordingPaneForTest.MainContent.ActualHeight > 120 && main.RecordingPaneForTest.RowDefinitions[2].ActualHeight >= 200, "Restore a useful recording-panel height after enlarging the window");
|
|
121
|
+
main.RecordingPaneForTest.SetExpanded(false); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "editor-record-collapsed-dark.png"), 1280, 800);
|
|
122
|
+
Check(!main.RecordingPaneForTest.IsExpanded && main.RecordingPaneForTest.RowDefinitions[2].ActualHeight <= 33, "Collapse recording controls to a small restore strip");
|
|
123
|
+
main.RecordingPaneForTest.SetExpanded(true); main.RecordingPaneForTest.SetBottom(false);
|
|
124
|
+
main.OpenHomeForTest(); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "home-dark.png"), 1280, 800);
|
|
125
|
+
main.SetThemeForTest(false); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "home-light.png"), 1280, 800);
|
|
126
|
+
main.OpenEditorForTest(); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "editor-shell-light.png"), 1280, 800);
|
|
127
|
+
main.OpenConverterForTest(); main.ConverterClipForTest(demoClip); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "converter-light.png"), 1280, 800);
|
|
128
|
+
main.SetThemeForTest(true); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "converter-dark.png"), 1280, 800);
|
|
129
|
+
Check(main.NativeWindow.FindName("ConvertTab") == null && main.NativeWindow.FindName("ViewConverter") != null && main.NativeWindow.FindName("GainSlider") == null, "Open Converter from View without a navigation tab or gain amplifier");
|
|
130
|
+
Check(((ComboBox)main.NativeWindow.FindName("ConvertFormat")).Items.Contains("AIFF"), "Offer AIFF as a conversion destination");
|
|
131
|
+
main.ConverterPaneForTest.SetBottom(false); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "converter-side-dark.png"), 1280, 800);
|
|
132
|
+
double widePane = main.ConverterPaneForTest.ColumnDefinitions[2].ActualWidth, oldMain = main.ConverterPaneForTest.MainContent.ActualWidth;
|
|
133
|
+
main.ConverterPaneForTest.ColumnDefinitions[2].Width = new GridLength(245); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "converter-side-narrow-dark.png"), 1280, 800);
|
|
134
|
+
Check(main.ConverterPaneForTest.ColumnDefinitions[2].ActualWidth < widePane && main.ConverterPaneForTest.MainContent.ActualWidth > oldMain, "Shrink the conversion options pane and give the recovered width to the file area");
|
|
135
|
+
main.ConverterPaneForTest.SetExpanded(false); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "converter-collapsed-dark.png"), 850, 560);
|
|
136
|
+
Check(main.ConverterPaneForTest.MainContent.ActualWidth > 700, "Collapse converter options while keeping a visible restore control");
|
|
137
|
+
var player = main.OpenPlayerForTest(); var playerClip = Audio.Load(demoPath, Path.Combine(root, "player-gain-cache"), CancellationToken.None, null); byte[] playerOriginal = File.ReadAllBytes(demoPath); player.SetForTest(playerClip);
|
|
138
|
+
player.GainForTest(-6, false); Check(Math.Abs(player.PlaybackGainForTest - .8 * Math.Pow(10, -6 / 20.0)) < .0001, "Apply a media-player gain reduction to its playback sample multiplier");
|
|
139
|
+
string reduced = player.SaveCopyForTest(Path.Combine(root, "gain-copies"), "Reduced copy"); var reducedClip = Audio.Load(reduced, Path.Combine(root, "gain-copy-cache"), CancellationToken.None, null);
|
|
140
|
+
Check(Math.Abs(reducedClip.Peak / playerClip.Peak - Math.Pow(10, -6 / 20.0)) < .002, "Save the actual -6 dB adjustment in a new PCM audio copy");
|
|
141
|
+
player.GainForTest(6, false); Check(player.PlaybackGainForTest > 1.59, "Amplify media-player playback with positive gain when protection is disabled");
|
|
142
|
+
player.GainForTest(6, true); string safeCopy = player.SaveCopyForTest(Path.Combine(root, "gain-copies"), "Safe copy");
|
|
143
|
+
Check(Audio.Load(safeCopy, Path.Combine(root, "safe-copy-cache"), CancellationToken.None, null).Peak < .9 && playerOriginal.SequenceEqual(File.ReadAllBytes(demoPath)), "Cap amplified copy peaks with protection and preserve the original audio bytes");
|
|
144
|
+
Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "media-player-gain-dark.png"), 1280, 800);
|
|
145
|
+
player.GainPaneForTest.SetBottom(true); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "media-player-gain-bottom-dark.png"), 1280, 800);
|
|
146
|
+
Check(player.GainPaneForTest.OnBottom && player.GainPaneForTest.MainContent.ActualHeight > 100, "Dock media-player gain and its compact Save copy action below playback");
|
|
147
|
+
player.GainPaneForTest.SetExpanded(false); Picture((FrameworkElement)main.NativeWindow.Content, Path.Combine(root, "media-player-gain-collapsed-dark.png"), 850, 560);
|
|
148
|
+
Check(!player.GainPaneForTest.IsExpanded, "Collapse the media-player gain panel and retain a restore control");
|
|
149
|
+
Check(main.NativeWindow.FindName("ThemeToggle") != null && main.NativeWindow.FindName("FileSave") != null && main.NativeWindow.FindName("SettingsKeys") != null, "Expose top-right theme switching and File/View/Install/Settings/Help menus");
|
|
150
|
+
main.NativeWindow.Close();
|
|
151
|
+
ThemePalette.Apply(true);
|
|
152
|
+
var owner = new Window(); ThemePalette.Install(owner);
|
|
153
|
+
string source = Path.Combine(root, "recorded-input.wav"); var samples = new float[4800]; for (int i = 0; i < samples.Length; i++) samples[i] = (float)(1.5 * Math.Sin(i * .08));
|
|
154
|
+
using (var writer = new FloatWaveWriter(source, 1, 48000)) writer.Write(samples, samples.Length, 1);
|
|
155
|
+
byte[] original = File.ReadAllBytes(source);
|
|
156
|
+
using (var editor = new EditorView(owner, Path.Combine(root, "record-cache"), delegate(string message) { }, delegate { })) {
|
|
157
|
+
owner.Content = editor;
|
|
158
|
+
RunOnDispatcher(() => editor.AddRecordedFiles(new[] { source }, Audio.Rate * 2L, 5));
|
|
159
|
+
var session = editor.SessionForTest;
|
|
160
|
+
Check(session.Regions.Count == 1 && session.Regions[0].Track == 5 && session.Regions[0].Start == Audio.Rate * 2L, "Insert a saved recording at its original playhead position on the selected editor track");
|
|
161
|
+
Check(original.SequenceEqual(File.ReadAllBytes(source)), "Keep the original float take unchanged while adding it to the timeline");
|
|
162
|
+
RunOnDispatcher(async delegate { await editor.ExecuteAction("undo"); }); Check(editor.SessionForTest.Regions.Count == 0, "Undo adding a recording without deleting its source file");
|
|
163
|
+
}
|
|
164
|
+
owner.Content = null; owner.Close();
|
|
165
|
+
log.Add("Offscreen UI and generated-buffer checks. Physical key focus, real recording timing and the affected Realtek PC still require hands-on acceptance.");
|
|
166
|
+
File.WriteAllLines(Path.Combine(root, "interface-test-results.txt"), log, Encoding.UTF8); return 0;
|
|
167
|
+
}
|
|
168
|
+
catch (Exception ex) { log.Add("FAIL " + ex); File.WriteAllLines(Path.Combine(root, "interface-test-results.txt"), log, Encoding.UTF8); return 1; }
|
|
169
|
+
finally { InterfacePreferences.Current = old; AudioOutputs.PreferredKey = oldOutput; ThemePalette.Apply(old.Theme == "Dark"); }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Drum Buddy contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
Third-party programs, libraries and the Zoom driver retain their own licenses.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Generation mode: OpenAI built-in image generation, transparent background.
|
|
2
|
+
|
|
3
|
+
Final logo brief:
|
|
4
|
+
Original mascot logo and Windows app icon for Drum Buddy. A friendly vintage
|
|
5
|
+
drummer with long, thick, curly black hair flowing out from under a black
|
|
6
|
+
marching-band hat to his shoulders. Natural, easygoing, youthful 1970s face
|
|
7
|
+
with a relaxed smile. Red vest, cream white puff-sleeve shirt, brass accents,
|
|
8
|
+
two wooden drumsticks and a red-and-cream marching snare drum. Nostalgic
|
|
9
|
+
early-20th-century snack-box illustration influence with a 1970s musician's
|
|
10
|
+
hair and face. Crisp black/navy outlines, limited red, black, cream and brass
|
|
11
|
+
palette, compact centered composition. Transparent background, no lettering,
|
|
12
|
+
extra figures, frame, or watermark.
|
|
13
|
+
|
|
14
|
+
The final artwork was generated as an edit of the first mascot, changing the
|
|
15
|
+
face and hairstyle while preserving his clothes, hat, hands, sticks, drum,
|
|
16
|
+
composition, and transparent background.
|