@pro-gram/flux 0.0.0-stage → 1.5.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 +32 -0
- package/README.md +102 -2
- package/bin/flux.js +29 -0
- package/dependencies.json +14 -0
- package/package.json +60 -6
- package/payload/DrumBuddy-Setup.exe +0 -0
- package/scripts/install.js +58 -0
- package/scripts/version.js +12 -0
- package/source/AssetBootstrap.cs +49 -0
- package/source/Audio.cs +453 -0
- package/source/AudioDeviceSettings.cs +126 -0
- package/source/AudioInputs.cs +50 -0
- package/source/AudioOutputs.cs +327 -0
- package/source/AudioWorkspace.cs +71 -0
- package/source/BrandArtwork.cs +71 -0
- package/source/Build Instructions.txt +59 -0
- package/source/Build.ps1 +123 -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/EditorView.cs +626 -0
- package/source/Fetch-Dependencies.ps1 +15 -0
- package/source/FloatRecording.cs +99 -0
- package/source/FloatRecordingTests.cs +170 -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 +219 -0
- package/source/InterfaceTests.cs +166 -0
- package/source/LICENSE.txt +23 -0
- package/source/Logo Prompt.txt +16 -0
- package/source/Main.cs +781 -0
- package/source/Main.xaml +292 -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 +134 -0
- package/source/Quick Start.txt +77 -0
- package/source/SelfTest.cs +78 -0
- package/source/SettingsWindow.cs +208 -0
- package/source/Setup.cs +192 -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 +32 -0
- package/source/Timeline.cs +261 -0
- package/source/TimelineGestures.cs +57 -0
- package/source/Uninstall.cs +69 -0
- package/source/WorkspaceTests.cs +198 -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
package/source/Main.cs
ADDED
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.IO;
|
|
3
|
+
using System.Linq;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.Collections.ObjectModel;
|
|
6
|
+
using System.Diagnostics;
|
|
7
|
+
using System.Globalization;
|
|
8
|
+
using System.Reflection;
|
|
9
|
+
using System.Threading;
|
|
10
|
+
using System.Threading.Tasks;
|
|
11
|
+
using System.Windows;
|
|
12
|
+
using System.Windows.Controls;
|
|
13
|
+
using System.Windows.Markup;
|
|
14
|
+
using System.Windows.Media;
|
|
15
|
+
using System.Windows.Shapes;
|
|
16
|
+
using System.Windows.Threading;
|
|
17
|
+
using System.Xml.Serialization;
|
|
18
|
+
using NAudio.Wave;
|
|
19
|
+
using NAudio.Wave.SampleProviders;
|
|
20
|
+
|
|
21
|
+
[assembly: AssemblyTitle("Flux")]
|
|
22
|
+
[assembly: AssemblyDescription("PCM/float media player, 16-track audio editor, H5studio recording and R16 control")]
|
|
23
|
+
[assembly: AssemblyVersion("1.5.0.0")]
|
|
24
|
+
|
|
25
|
+
namespace DrumBuddy
|
|
26
|
+
{
|
|
27
|
+
public sealed class Preferences
|
|
28
|
+
{
|
|
29
|
+
public string ExportFolder, RecordFolder;
|
|
30
|
+
public int Bits = 24;
|
|
31
|
+
public string AudioOutputKey;
|
|
32
|
+
public string AudioInputKey;
|
|
33
|
+
}
|
|
34
|
+
public sealed class OutputChoice
|
|
35
|
+
{
|
|
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"; } }
|
|
40
|
+
public override string ToString() { return Name; }
|
|
41
|
+
}
|
|
42
|
+
public sealed class ModeChoice
|
|
43
|
+
{
|
|
44
|
+
public int Mode;
|
|
45
|
+
public string Name;
|
|
46
|
+
public override string ToString() { return Name; }
|
|
47
|
+
}
|
|
48
|
+
public sealed class RateChoice
|
|
49
|
+
{
|
|
50
|
+
public int Rate;
|
|
51
|
+
public override string ToString() { return (Rate / 1000.0).ToString("0.#", CultureInfo.InvariantCulture) + " kHz"; }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
internal static class OutputDevices
|
|
55
|
+
{
|
|
56
|
+
[System.Runtime.InteropServices.DllImport("winmm.dll")]
|
|
57
|
+
internal static extern int waveOutGetNumDevs();
|
|
58
|
+
[System.Runtime.InteropServices.DllImport("winmm.dll",CharSet=System.Runtime.InteropServices.CharSet.Auto)]
|
|
59
|
+
private static extern int waveOutGetDevCaps(IntPtr device, out WaveOutCapabilities caps, int size);
|
|
60
|
+
internal static string Name(int device) {
|
|
61
|
+
WaveOutCapabilities caps;
|
|
62
|
+
if (waveOutGetDevCaps((IntPtr)device,out caps,System.Runtime.InteropServices.Marshal.SizeOf(typeof(WaveOutCapabilities))) != 0) return "Audio output " + (device + 1);
|
|
63
|
+
return caps.ProductName;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public sealed class MainWindow
|
|
68
|
+
{
|
|
69
|
+
private HomeView homeView;
|
|
70
|
+
private long recordingAnchor;
|
|
71
|
+
private int recordingTrack, configuredInputChannels = 1;
|
|
72
|
+
private bool configuringConversion;
|
|
73
|
+
private string appliedGridDefaults;
|
|
74
|
+
private DockablePane converterPane;
|
|
75
|
+
private readonly Window window;
|
|
76
|
+
private readonly ObservableCollection<Clip> clips = new ObservableCollection<Clip>();
|
|
77
|
+
private readonly string cache = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "DrumBuddy", Guid.NewGuid().ToString("N"));
|
|
78
|
+
private readonly string settings = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DrumBuddy", "settings.xml");
|
|
79
|
+
private Preferences prefs;
|
|
80
|
+
private CancellationTokenSource cancel;
|
|
81
|
+
private bool busy, closeWhenIdle, stopping;
|
|
82
|
+
private Recorder recorder;
|
|
83
|
+
private readonly ProgressBar[] meters = new ProgressBar[8];
|
|
84
|
+
private readonly TextBlock[] meterLabels = new TextBlock[8];
|
|
85
|
+
private readonly DispatcherTimer timer;
|
|
86
|
+
private ControlMode controlMode;
|
|
87
|
+
private bool configuringCapture;
|
|
88
|
+
private string configuredDriver;
|
|
89
|
+
private DateTime captureStarted;
|
|
90
|
+
private MediaPlayerView mediaPlayerView;
|
|
91
|
+
private EditorView editorView;
|
|
92
|
+
private int workspace;
|
|
93
|
+
|
|
94
|
+
private T C<T>(string name) where T : FrameworkElement { return (T)window.FindName(name); }
|
|
95
|
+
private Clip Selected { get { return C<ListBox>("FileList").SelectedItem as Clip; } }
|
|
96
|
+
private bool Protect { get { return C<CheckBox>("ProtectCheck").IsChecked == true; } }
|
|
97
|
+
|
|
98
|
+
public MainWindow(string preferenceFile = null, bool testMode = false)
|
|
99
|
+
{
|
|
100
|
+
if (preferenceFile != null) settings = preferenceFile;
|
|
101
|
+
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Main.xaml")) window = (Window)XamlReader.Load(stream);
|
|
102
|
+
window.Height = Math.Min(window.Height, SystemParameters.WorkArea.Height - 30);
|
|
103
|
+
window.Width = Math.Min(window.Width, SystemParameters.WorkArea.Width - 30);
|
|
104
|
+
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());
|
|
107
|
+
try { using (var s = File.OpenRead(settings)) prefs = (Preferences)new XmlSerializer(typeof(Preferences)).Deserialize(s); }
|
|
108
|
+
catch { prefs = new Preferences(); }
|
|
109
|
+
if (!testMode) InterfacePreferences.Load(prefs.AudioOutputKey, prefs.AudioInputKey);
|
|
110
|
+
ThemePalette.Install(window);
|
|
111
|
+
prefs.AudioInputKey = InterfacePreferences.Current.AudioInputKey;
|
|
112
|
+
AudioOutputs.PreferredKey = InterfacePreferences.Current.AudioOutputKey;
|
|
113
|
+
string music = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic);
|
|
114
|
+
if (string.IsNullOrEmpty(music)) music = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
|
115
|
+
if (string.IsNullOrEmpty(prefs.ExportFolder)) prefs.ExportFolder = System.IO.Path.Combine(music, "Flux", "Exports");
|
|
116
|
+
if (string.IsNullOrEmpty(prefs.RecordFolder)) prefs.RecordFolder = System.IO.Path.Combine(music, "Flux", "Recordings");
|
|
117
|
+
C<TextBlock>("ExportFolderText").Text = prefs.ExportFolder;
|
|
118
|
+
C<TextBlock>("ExportFolderText").ToolTip = prefs.ExportFolder;
|
|
119
|
+
C<TextBlock>("RecordFolderText").Text = "Recordings: " + prefs.RecordFolder;
|
|
120
|
+
C<TextBlock>("RecordFolderText").ToolTip = prefs.RecordFolder;
|
|
121
|
+
C<RadioButton>(prefs.Bits == 16 ? "Bits16" : "Bits24").IsChecked = true;
|
|
122
|
+
C<ListBox>("FileList").ItemsSource = clips;
|
|
123
|
+
C<ListBox>("FileList").SelectionChanged += delegate { ShowClip(); };
|
|
124
|
+
C<Button>("AddButton").Click += async delegate {
|
|
125
|
+
var dialog = new Microsoft.Win32.OpenFileDialog { Title = "Choose songs or videos", Multiselect = true,
|
|
126
|
+
Filter = "Audio and video|*.mp3;*.mp4;*.wav;*.m4a;*.aac;*.flac;*.ogg;*.wma;*.mov;*.mkv;*.aiff;*.aif;*.opus;*.webm|All files|*.*" };
|
|
127
|
+
if (dialog.ShowDialog(window) == true) await AddFiles(dialog.FileNames);
|
|
128
|
+
};
|
|
129
|
+
C<Button>("RemoveButton").Click += delegate {
|
|
130
|
+
Clip clip = Selected; if (clip == null) return; clips.Remove(clip); Audio.TryDelete(clip.Cache);
|
|
131
|
+
if (clips.Count > 0) C<ListBox>("FileList").SelectedIndex = 0;
|
|
132
|
+
ShowClip();
|
|
133
|
+
};
|
|
134
|
+
C<Button>("ClearButton").Click += delegate { foreach (var clip in clips) Audio.TryDelete(clip.Cache); clips.Clear(); ShowClip(); };
|
|
135
|
+
C<Button>("RecordTab").Click += delegate { if (WorkspacesBusy) { Status("Finish or cancel the current task first."); return; } StopWorkspaces(); RefreshDevices(); SwitchPage(true); };
|
|
136
|
+
C<Button>("MediaPlayerTab").Click += delegate { OpenWorkspace(false); };
|
|
137
|
+
C<Button>("EditorTab").Click += delegate { OpenWorkspace(true); };
|
|
138
|
+
C<Button>("ControlTab").Click += delegate {
|
|
139
|
+
if (busy || recorder != null || WorkspacesBusy) { Status("Finish saving the current task before opening Full Control."); return; }
|
|
140
|
+
StopWorkspaces();
|
|
141
|
+
if (controlMode == null) controlMode = new ControlMode(prefs.RecordFolder,prefs.ExportFolder);
|
|
142
|
+
controlMode.Open(window,delegate { OpenHome(); window.Show(); window.Activate(); });
|
|
143
|
+
};
|
|
144
|
+
AudioOutputs.OutputChanged += SyncOutputChoice;
|
|
145
|
+
C<Button>("ExportButton").Click += async delegate { await ExportFiles(); };
|
|
146
|
+
C<Button>("CancelButton").Click += delegate { if (cancel != null) cancel.Cancel(); };
|
|
147
|
+
C<Button>("ChangeFolderButton").Click += delegate {
|
|
148
|
+
string folder = ChooseFolder(prefs.ExportFolder); if (folder == null) return;
|
|
149
|
+
prefs.ExportFolder = folder; C<TextBlock>("ExportFolderText").Text = folder; C<TextBlock>("ExportFolderText").ToolTip = folder; SavePreferences();
|
|
150
|
+
};
|
|
151
|
+
C<Button>("ChangeRecordFolderButton").Click += delegate {
|
|
152
|
+
string folder = ChooseFolder(prefs.RecordFolder); if (folder == null) return;
|
|
153
|
+
prefs.RecordFolder = folder; C<TextBlock>("RecordFolderText").Text = "Recordings: " + folder; C<TextBlock>("RecordFolderText").ToolTip = folder; SavePreferences();
|
|
154
|
+
};
|
|
155
|
+
C<Button>("OpenExportsButton").Click += delegate { OpenFolder(prefs.ExportFolder); };
|
|
156
|
+
C<Button>("OpenRecordingsButton").Click += delegate { OpenFolder(prefs.RecordFolder); };
|
|
157
|
+
C<Button>("RefreshDevicesButton").Click += delegate { RefreshDevices(); };
|
|
158
|
+
C<ComboBox>("InputDevice").SelectionChanged += delegate {
|
|
159
|
+
if (configuringCapture) return; ConfigureRecordingDevice();
|
|
160
|
+
var input = C<ComboBox>("InputDevice").SelectedItem as InputChoice;
|
|
161
|
+
if (input != null) { prefs.AudioInputKey = InterfacePreferences.Current.AudioInputKey = input.Key; try { InterfacePreferences.Save(InterfacePreferences.Current.Copy()); } catch (Exception ex) { Status(ex.Message); } }
|
|
162
|
+
};
|
|
163
|
+
C<RadioButton>("RecordFloat").Checked += delegate { ShowRecordingFormat(); };
|
|
164
|
+
C<RadioButton>("Record16").Checked += delegate { ShowRecordingFormat(); };
|
|
165
|
+
C<RadioButton>("Record24").Checked += delegate { ShowRecordingFormat(); };
|
|
166
|
+
C<Button>("StartRecordButton").Click += delegate { StartRecording(); };
|
|
167
|
+
C<Button>("StopRecordButton").Click += async delegate { await StopRecording(true); };
|
|
168
|
+
C<Button>("HelpButton").Click += delegate { ShowHelp(); };
|
|
169
|
+
C<Button>("InstallDriversButton").Click += delegate { DriverSetup.ShowGuide(window); };
|
|
170
|
+
C<Button>("AudioDevicesButton").Click += delegate { ShowAudioDevices(); };
|
|
171
|
+
C<Button>("HomeTab").Click += delegate { OpenHome(); };
|
|
172
|
+
C<Button>("ThemeToggle").Click += delegate { var themeSettings = InterfacePreferences.Current.Copy(); themeSettings.Theme = themeSettings.Theme == "Dark" ? "Light" : "Dark"; try { InterfacePreferences.Save(themeSettings); } catch (Exception ex) { Status(ex.Message); } };
|
|
173
|
+
InterfacePreferences.Changed += ApplyInterfaceSettings;
|
|
174
|
+
BindMenus(); ApplyInterfaceSettings(); InitializeConversion(); InitializeConverterLayout();
|
|
175
|
+
window.PreviewDragOver += delegate(object sender, DragEventArgs e) {
|
|
176
|
+
e.Effects = !busy && !WorkspacesBusy && recorder == null && e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None;
|
|
177
|
+
e.Handled = true;
|
|
178
|
+
};
|
|
179
|
+
window.Drop += async delegate(object sender, DragEventArgs e) {
|
|
180
|
+
if (!busy && !WorkspacesBusy && recorder == null && e.Data.GetDataPresent(DataFormats.FileDrop)) {
|
|
181
|
+
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
|
182
|
+
if (workspace == 2) await mediaPlayerView.AddFiles(files);
|
|
183
|
+
else if (workspace == 3) await editorView.AddFiles(files);
|
|
184
|
+
else { SwitchPage(false); await AddFiles(files); }
|
|
185
|
+
}
|
|
186
|
+
e.Handled = true;
|
|
187
|
+
};
|
|
188
|
+
window.Closing += async delegate(object sender, System.ComponentModel.CancelEventArgs e) {
|
|
189
|
+
if (recorder != null) {
|
|
190
|
+
e.Cancel = true;
|
|
191
|
+
if (stopping) return;
|
|
192
|
+
if (MessageBox.Show(window, "Stop and save this recording before closing?", "Save your recording", MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes) return;
|
|
193
|
+
await StopRecording(false); window.Close(); return;
|
|
194
|
+
}
|
|
195
|
+
if (busy) {
|
|
196
|
+
e.Cancel = true;
|
|
197
|
+
if (MessageBox.Show(window, "Cancel the current task and close? Files already saved will be kept.", "Work in progress", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) {
|
|
198
|
+
closeWhenIdle = true; if (cancel != null) cancel.Cancel();
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (WorkspacesBusy) {
|
|
203
|
+
e.Cancel = true;
|
|
204
|
+
if (MessageBox.Show(window, "Cancel the running media/editor task and close? Completed files are kept.", "Work in progress", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) {
|
|
205
|
+
closeWhenIdle = true; if (mediaPlayerView != null) mediaPlayerView.CancelWork(); if (editorView != null) editorView.CancelWork();
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (editorView != null && !editorView.ConfirmClose()) e.Cancel = true;
|
|
210
|
+
};
|
|
211
|
+
window.PreviewKeyDown += HandleInterfaceKey;
|
|
212
|
+
window.Closed += delegate {
|
|
213
|
+
AudioOutputs.OutputChanged -= SyncOutputChoice;
|
|
214
|
+
InterfacePreferences.Changed -= ApplyInterfaceSettings;
|
|
215
|
+
timer.Stop(); SavePreferences();
|
|
216
|
+
if (controlMode != null) controlMode.Dispose();
|
|
217
|
+
if (mediaPlayerView != null) mediaPlayerView.Dispose();
|
|
218
|
+
if (editorView != null) editorView.Dispose();
|
|
219
|
+
try { if (Directory.Exists(cache)) Directory.Delete(cache, true); } catch { }
|
|
220
|
+
};
|
|
221
|
+
C<ComboBox>("InputMode").Items.Add(new ModeChoice { Mode = -1, Name = "All available inputs - separate WAV files" });
|
|
222
|
+
C<ComboBox>("InputMode").Items.Add(new ModeChoice { Mode = 0, Name = "Inputs 1 + 2 — one stereo WAV" });
|
|
223
|
+
for (int i = 1; i <= 8; i++) C<ComboBox>("InputMode").Items.Add(new ModeChoice { Mode = i, Name = "Input " + i + " — one mono WAV" });
|
|
224
|
+
C<ComboBox>("InputMode").SelectedIndex = 0;
|
|
225
|
+
C<ComboBox>("RecordRate").ItemsSource = new[] { new RateChoice { Rate = 44100 }, new RateChoice { Rate = 48000 }, new RateChoice { Rate = 96000 } };
|
|
226
|
+
C<ComboBox>("RecordRate").SelectedIndex = 0;
|
|
227
|
+
for (int i = 0; i < 8; i++) {
|
|
228
|
+
var panel = new StackPanel { Margin = new Thickness(4,0,4,0) };
|
|
229
|
+
panel.Children.Add(new TextBlock { Text = "IN " + (i + 1), FontSize = 11, HorizontalAlignment = HorizontalAlignment.Center });
|
|
230
|
+
meters[i] = new ProgressBar { Minimum = 0, Maximum = 1, Height = 10, Margin = new Thickness(0,5,0,4), Foreground = Brush("#007A6B") };
|
|
231
|
+
panel.Children.Add(meters[i]);
|
|
232
|
+
meterLabels[i] = new TextBlock { Text = "—", FontSize = 11, Foreground = Brush("#526771"), HorizontalAlignment = HorizontalAlignment.Center };
|
|
233
|
+
panel.Children.Add(meterLabels[i]); C<System.Windows.Controls.Primitives.UniformGrid>("MeterPanel").Children.Add(panel);
|
|
234
|
+
}
|
|
235
|
+
timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
|
|
236
|
+
timer.Tick += async delegate { await Tick(); };
|
|
237
|
+
timer.Start();
|
|
238
|
+
window.Loaded += async delegate {
|
|
239
|
+
RefreshDevices(false); ShowClip();
|
|
240
|
+
if (InterfacePreferences.Current.Startup == "Home") OpenHome(); else OpenWorkspace(true);
|
|
241
|
+
string[] paths = Environment.GetCommandLineArgs().Skip(1).Where(File.Exists).ToArray();
|
|
242
|
+
if (paths.Length > 0) { OpenWorkspace(true); await editorView.AddFiles(paths); }
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
public void Show() { window.Show(); }
|
|
247
|
+
public Window NativeWindow { get { return window; } }
|
|
248
|
+
internal void OpenEditorForTest() { OpenWorkspace(true); }
|
|
249
|
+
internal void OpenHomeForTest() { OpenHome(); }
|
|
250
|
+
internal void EditorFixtureForTest(EditorSession session, IDictionary<string, Clip> clips) { editorView.SetForTest(session, clips); editorView.ShowSelectionForTest(Audio.Rate, Audio.Rate * 4L); }
|
|
251
|
+
internal void RecordingPanelForTest()
|
|
252
|
+
{
|
|
253
|
+
var inputs = AudioInputs.Choices(); configuringCapture = true; C<ComboBox>("InputDevice").ItemsSource = inputs; C<ComboBox>("InputDevice").SelectedItem = inputs.FirstOrDefault(c => c.Driver == null); configuringCapture = false;
|
|
254
|
+
ConfigureRecordingDevice(); editorView.ShowRecordingOptions(true);
|
|
255
|
+
}
|
|
256
|
+
internal void SetThemeForTest(bool dark) { ThemePalette.Apply(dark); ThemePalette.Install(window); C<Button>("ThemeToggle").Content = dark ? "Light theme" : "Dark theme"; }
|
|
257
|
+
internal void OpenConverterForTest() { SwitchPage(false); }
|
|
258
|
+
internal DockablePane ConverterPaneForTest { get { return converterPane; } }
|
|
259
|
+
internal DockablePane RecordingPaneForTest { get { return editorView.RecordingPaneForTest; } }
|
|
260
|
+
internal MediaPlayerView OpenPlayerForTest() { OpenWorkspace(false); return mediaPlayerView; }
|
|
261
|
+
internal void ConverterClipForTest(Clip clip) { clips.Add(clip); C<ListBox>("FileList").SelectedItem = clip; ShowClip(); }
|
|
262
|
+
internal void ApplySettingsForTest(InterfaceSettings value) { InterfacePreferences.Current = value.Copy(); ThemePalette.Apply(value.Theme == "Dark"); ApplyInterfaceSettings(); }
|
|
263
|
+
internal TempoGridOptions GridForTest { get { return editorView.GridOptions; } }
|
|
264
|
+
internal void SetGridForTest(TempoGridOptions value, double zoom) { editorView.ApplyGridOptions(value); editorView.ZoomForTest(zoom); }
|
|
265
|
+
internal bool IntegratedRecorderForTest { get { return editorView != null && C<StackPanel>("RecordPage").Parent != null && C<ComboBox>("InputDevice") != null; } }
|
|
266
|
+
private static Brush Brush(string hex) { return ThemePalette.Brush(hex); }
|
|
267
|
+
private void Status(string text) { C<TextBlock>("StatusText").Text = text; C<Border>("StatusHost").Visibility = string.IsNullOrWhiteSpace(text) ? Visibility.Collapsed : Visibility.Visible; }
|
|
268
|
+
private void SwitchPage(bool record)
|
|
269
|
+
{
|
|
270
|
+
if (record) { OpenWorkspace(true); if (editorView != null) editorView.ShowRecordingOptions(true); return; }
|
|
271
|
+
if (WorkspacesBusy) { Status("Finish or cancel the running media/editor task first."); return; }
|
|
272
|
+
Status("");
|
|
273
|
+
StopWorkspaces(); workspace = record ? 1 : 0;
|
|
274
|
+
C<ScrollViewer>("MainScroll").Visibility = Visibility.Collapsed; C<Grid>("ConverterHost").Visibility = Visibility.Visible;
|
|
275
|
+
C<Grid>("MediaPlayerHost").Visibility = Visibility.Collapsed; C<Grid>("EditorHost").Visibility = Visibility.Collapsed;
|
|
276
|
+
C<Grid>("HomeHost").Visibility = Visibility.Collapsed;
|
|
277
|
+
C<Button>("HomeTab").Style = (Style)window.Resources[typeof(Button)];
|
|
278
|
+
C<Button>("MediaPlayerTab").Style = (Style)window.Resources[typeof(Button)]; C<Button>("EditorTab").Style = (Style)window.Resources[typeof(Button)];
|
|
279
|
+
C<Border>("ExportCard").Visibility = record ? Visibility.Collapsed : Visibility.Visible;
|
|
280
|
+
C<Grid>("ConvertPage").Visibility = record ? Visibility.Collapsed : Visibility.Visible;
|
|
281
|
+
C<StackPanel>("RecordPage").Visibility = record ? Visibility.Visible : Visibility.Collapsed;
|
|
282
|
+
C<Button>("RecordTab").Style = (Style)window.Resources[record ? (object)"Primary" : typeof(Button)];
|
|
283
|
+
}
|
|
284
|
+
private bool WorkspacesBusy { get { return (mediaPlayerView != null && mediaPlayerView.IsBusy) || (editorView != null && editorView.IsBusy); } }
|
|
285
|
+
private void StopWorkspaces() { if (mediaPlayerView != null) mediaPlayerView.Stop(); if (editorView != null) editorView.Stop(); }
|
|
286
|
+
private void OpenWorkspace(bool editor)
|
|
287
|
+
{
|
|
288
|
+
if (busy || recorder != null || WorkspacesBusy) { Status("Finish or cancel the current task before changing workspaces."); return; }
|
|
289
|
+
Status("");
|
|
290
|
+
StopWorkspaces();
|
|
291
|
+
if (editor && editorView == null) {
|
|
292
|
+
editorView = new EditorView(window, System.IO.Path.Combine(cache, "editor"), Status, delegate { if (mediaPlayerView != null) mediaPlayerView.Stop(); });
|
|
293
|
+
C<Grid>("EditorHost").Children.Add(editorView);
|
|
294
|
+
var panel = C<StackPanel>("RecordPage"); var parent = panel.Parent as Panel; if (parent != null) parent.Children.Remove(panel);
|
|
295
|
+
panel.Visibility = Visibility.Visible; editorView.AttachRecordingPanel(panel); editorView.RecordRequested = async delegate { if (recorder == null) StartRecording(); else await StopRecording(true); };
|
|
296
|
+
editorView.StopRequested = async delegate { if (recorder != null) await StopRecording(true); };
|
|
297
|
+
editorView.RecordingOptionsRequested = delegate { var input = C<ComboBox>("InputDevice").SelectedItem as InputChoice; if (input != null && configuredDriver != input.Key && recorder == null) ConfigureRecordingDevice(); };
|
|
298
|
+
editorView.GridSettingsRequested = delegate { OpenSettings("timeline"); };
|
|
299
|
+
}
|
|
300
|
+
if (!editor && mediaPlayerView == null) {
|
|
301
|
+
mediaPlayerView = new MediaPlayerView(window, System.IO.Path.Combine(cache, "player"), Status, delegate { if (editorView != null) editorView.Stop(); });
|
|
302
|
+
C<Grid>("MediaPlayerHost").Children.Add(mediaPlayerView);
|
|
303
|
+
}
|
|
304
|
+
workspace = editor ? 3 : 2;
|
|
305
|
+
C<Grid>("HomeHost").Visibility = Visibility.Collapsed; C<StackPanel>("RecordPage").Visibility = editor ? Visibility.Visible : Visibility.Collapsed;
|
|
306
|
+
C<Border>("ExportCard").Visibility = Visibility.Collapsed; C<Grid>("ConverterHost").Visibility = Visibility.Collapsed; C<ScrollViewer>("MainScroll").Visibility = Visibility.Collapsed;
|
|
307
|
+
C<Grid>("MediaPlayerHost").Visibility = editor ? Visibility.Collapsed : Visibility.Visible; C<Grid>("EditorHost").Visibility = editor ? Visibility.Visible : Visibility.Collapsed;
|
|
308
|
+
foreach (string name in new[] { "HomeTab", "RecordTab", "MediaPlayerTab", "EditorTab" }) C<Button>(name).Style = (Style)window.Resources[name == (editor ? "EditorTab" : "MediaPlayerTab") ? (object)"Primary" : typeof(Button)];
|
|
309
|
+
}
|
|
310
|
+
private void SetBusy(bool value, string message)
|
|
311
|
+
{
|
|
312
|
+
busy = value; Status(message);
|
|
313
|
+
foreach (string name in new[] { "AddButton", "RemoveButton", "ClearButton", "RecordTab", "MediaPlayerTab", "EditorTab", "ChangeFolderButton", "ChangeRecordFolderButton", "Bits16", "Bits24", "FileList", "InputDevice", "InputMode", "Record16", "Record24", "RecordFloat", "RecordRate", "RefreshDevicesButton", "ConvertProfile", "ConvertFormat", "ConvertRate" })
|
|
314
|
+
C<Control>(name).IsEnabled = !value;
|
|
315
|
+
C<ProgressBar>("WorkProgress").Visibility = value ? Visibility.Visible : Visibility.Collapsed;
|
|
316
|
+
C<Button>("CancelButton").Visibility = value ? Visibility.Visible : Visibility.Collapsed;
|
|
317
|
+
C<ProgressBar>("WorkProgress").Value = 0;
|
|
318
|
+
ShowClip();
|
|
319
|
+
C<Button>("StartRecordButton").IsEnabled = !value && recorder == null && C<ComboBox>("InputDevice").SelectedItem != null;
|
|
320
|
+
UpdateConversionControls();
|
|
321
|
+
if (!value && closeWhenIdle) window.Dispatcher.BeginInvoke((Action)delegate { window.Close(); });
|
|
322
|
+
}
|
|
323
|
+
private void Progress(double value) { window.Dispatcher.BeginInvoke((Action)delegate { C<ProgressBar>("WorkProgress").Value = value * 100; }); }
|
|
324
|
+
|
|
325
|
+
private async Task AddFiles(IEnumerable<string> paths)
|
|
326
|
+
{
|
|
327
|
+
if (busy || recorder != null) return;
|
|
328
|
+
string[] files = paths.Where(File.Exists).Select(System.IO.Path.GetFullPath)
|
|
329
|
+
.Distinct(StringComparer.OrdinalIgnoreCase).Where(p => !clips.Any(c => string.Equals(c.Source,p,StringComparison.OrdinalIgnoreCase))).ToArray();
|
|
330
|
+
if (files.Length == 0) { Status("No new audio files were added."); return; }
|
|
331
|
+
cancel = new CancellationTokenSource();
|
|
332
|
+
SetBusy(true, "Reading audio…");
|
|
333
|
+
int failures = 0, added = 0;
|
|
334
|
+
try {
|
|
335
|
+
for (int i = 0; i < files.Length; i++) {
|
|
336
|
+
cancel.Token.ThrowIfCancellationRequested();
|
|
337
|
+
var placeholder = new Clip { Source = files[i], Name = System.IO.Path.GetFileName(files[i]), Status = "Reading audio…" };
|
|
338
|
+
clips.Add(placeholder); if (Selected == null) C<ListBox>("FileList").SelectedItem = placeholder;
|
|
339
|
+
Status("Reading " + (i + 1) + " of " + files.Length + ": " + placeholder.Name);
|
|
340
|
+
try {
|
|
341
|
+
var loaded = await Task.Run(() => Audio.Load(placeholder.Source, cache, cancel.Token, Progress));
|
|
342
|
+
int index = clips.IndexOf(placeholder);
|
|
343
|
+
bool selected = Selected == placeholder;
|
|
344
|
+
clips[index] = loaded;
|
|
345
|
+
if (selected) C<ListBox>("FileList").SelectedItem = loaded;
|
|
346
|
+
added++;
|
|
347
|
+
}
|
|
348
|
+
catch (OperationCanceledException) { clips.Remove(placeholder); throw; }
|
|
349
|
+
catch (Exception ex) { placeholder.Status = ex.Message; failures++; C<ListBox>("FileList").Items.Refresh(); }
|
|
350
|
+
}
|
|
351
|
+
SetBusy(false, failures > 0 ? added + " ready · " + failures + " could not be read. Select a file to see why." : added + " file" + (added == 1 ? " is" : "s are") + " ready. Choose the output type, then convert.");
|
|
352
|
+
}
|
|
353
|
+
catch (OperationCanceledException) { SetBusy(false, "Stopped reading files. Your ready files are still here."); }
|
|
354
|
+
finally { cancel.Dispose(); cancel = null; }
|
|
355
|
+
if (Selected == null && clips.Count > 0) C<ListBox>("FileList").SelectedIndex = 0;
|
|
356
|
+
ShowClip();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private void ShowClip()
|
|
360
|
+
{
|
|
361
|
+
Clip clip = Selected; bool ready = clip != null && clip.Ready;
|
|
362
|
+
C<TextBlock>("QueueTitle").Text = "Files to convert" + (clips.Count > 0 ? " (" + clips.Count + ")" : "");
|
|
363
|
+
|
|
364
|
+
C<Button>("ExportButton").IsEnabled = !busy && clips.Any(c => c.Ready);
|
|
365
|
+
C<Button>("RemoveButton").IsEnabled = !busy && clip != null;
|
|
366
|
+
C<Button>("ClearButton").IsEnabled = !busy && clips.Count > 0;
|
|
367
|
+
|
|
368
|
+
}
|
|
369
|
+
private async Task ExportFiles()
|
|
370
|
+
{
|
|
371
|
+
if (busy || recorder != null) return;
|
|
372
|
+
Clip[] ready = clips.Where(c => c.Ready).ToArray(); if (ready.Length == 0) return;
|
|
373
|
+
cancel = new CancellationTokenSource();
|
|
374
|
+
int bits = C<RadioButton>("Bits16").IsChecked == true ? 16 : 24;
|
|
375
|
+
bool protect = Protect; prefs.Bits = bits; SavePreferences();
|
|
376
|
+
int done = 0, failed = 0;
|
|
377
|
+
SetBusy(true,"Converting audio files…");
|
|
378
|
+
try {
|
|
379
|
+
for (int i = 0; i < ready.Length; i++) {
|
|
380
|
+
cancel.Token.ThrowIfCancellationRequested();
|
|
381
|
+
Clip clip = ready[i]; Status("Saving " + (i + 1) + " of " + ready.Length + ": " + clip.Name);
|
|
382
|
+
try {
|
|
383
|
+
var options = SelectedConversion(bits, protect);
|
|
384
|
+
string path = await Task.Run(() => ConversionFormats.Export(clip,prefs.ExportFolder,options,cancel.Token,Progress));
|
|
385
|
+
clip.Status = "Saved as " + System.IO.Path.GetFileName(path); done++;
|
|
386
|
+
}
|
|
387
|
+
catch (OperationCanceledException) { throw; }
|
|
388
|
+
catch (Exception ex) { clip.Status = "Save failed: " + ex.Message; failed++; }
|
|
389
|
+
C<ListBox>("FileList").Items.Refresh();
|
|
390
|
+
}
|
|
391
|
+
SetBusy(false,done + " audio file" + (done == 1 ? "" : "s") + " converted" + (failed > 0 ? " · " + failed + " failed. Select a file to see why." : ". Click Open folder to find them."));
|
|
392
|
+
}
|
|
393
|
+
catch (OperationCanceledException) { SetBusy(false,"Cancelled. " + done + " finished file" + (done == 1 ? " was" : "s were") + " kept."); }
|
|
394
|
+
finally { cancel.Dispose(); cancel = null; }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private void SyncOutputChoice()
|
|
398
|
+
{
|
|
399
|
+
if (!window.Dispatcher.CheckAccess()) { window.Dispatcher.BeginInvoke((Action)SyncOutputChoice); return; }
|
|
400
|
+
StopWorkspaces(); SavePreferences();
|
|
401
|
+
if (InterfacePreferences.Current.AudioOutputKey != AudioOutputs.PreferredKey) {
|
|
402
|
+
var settings = InterfacePreferences.Current.Copy(); settings.AudioOutputKey = AudioOutputs.PreferredKey;
|
|
403
|
+
try { InterfacePreferences.Save(settings); } catch (Exception ex) { Status(ex.Message); }
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
private void OpenSettings(string category)
|
|
407
|
+
{
|
|
408
|
+
if (category == "audio") { StopWorkspaces(); }
|
|
409
|
+
var initial = InterfacePreferences.Current.Copy(); if (editorView != null) initial.Grid = editorView.GridOptions;
|
|
410
|
+
var dialog = new SettingsWindow(window, category, recorder != null || busy || WorkspacesBusy, null, initial); dialog.ShowDialog();
|
|
411
|
+
}
|
|
412
|
+
private void ApplyInterfaceSettings()
|
|
413
|
+
{
|
|
414
|
+
ThemePalette.Install(window); C<Button>("ThemeToggle").Content = ThemePalette.Dark ? "Light theme" : "Dark theme";
|
|
415
|
+
var settings = InterfacePreferences.Current;
|
|
416
|
+
string gridSignature = settings.Grid.Signature();
|
|
417
|
+
if (appliedGridDefaults != gridSignature) { appliedGridDefaults = gridSignature; if (editorView != null) editorView.ApplyGridOptions(settings.Grid); }
|
|
418
|
+
if (editorView != null) editorView.RefreshGridPreferences();
|
|
419
|
+
if (window.IsLoaded && recorder == null && !busy && prefs.AudioInputKey != settings.AudioInputKey) {
|
|
420
|
+
prefs.AudioInputKey = settings.AudioInputKey;
|
|
421
|
+
configuringCapture = true; C<ComboBox>("InputDevice").SelectedItem = null; configuringCapture = false; RefreshDevices();
|
|
422
|
+
}
|
|
423
|
+
AudioOutputs.PreferredKey = settings.AudioOutputKey ?? "default";
|
|
424
|
+
RefreshMenuGestures(); SavePreferences();
|
|
425
|
+
}
|
|
426
|
+
private void OpenHome()
|
|
427
|
+
{
|
|
428
|
+
if (busy || recorder != null || WorkspacesBusy) { Status("Finish the current task before opening Home."); return; }
|
|
429
|
+
Status("");
|
|
430
|
+
StopWorkspaces();
|
|
431
|
+
if (homeView == null) { homeView = new HomeView(async delegate(string action) { await ExecuteInterfaceAction(action); }); C<Grid>("HomeHost").Children.Add(homeView); }
|
|
432
|
+
workspace = 4; C<Grid>("HomeHost").Visibility = Visibility.Visible;
|
|
433
|
+
C<Grid>("EditorHost").Visibility = C<Grid>("MediaPlayerHost").Visibility = Visibility.Collapsed;
|
|
434
|
+
C<ScrollViewer>("MainScroll").Visibility = Visibility.Collapsed; C<Border>("ExportCard").Visibility = Visibility.Collapsed; C<Grid>("ConverterHost").Visibility = Visibility.Collapsed;
|
|
435
|
+
foreach (string name in new[] { "HomeTab", "RecordTab", "MediaPlayerTab", "EditorTab" }) C<Button>(name).Style = (Style)window.Resources[name == "HomeTab" ? (object)"Primary" : typeof(Button)];
|
|
436
|
+
}
|
|
437
|
+
private void BindMenus()
|
|
438
|
+
{
|
|
439
|
+
string[,] entries = { { "FileNew", "new" }, { "FileOpen", "open" }, { "FileSave", "save" }, { "FileSaveAs", "saveAs" }, { "FileImport", "import" }, { "FileExport", "export" }, { "ViewHome", "home" }, { "ViewEditor", "editor" }, { "ViewConverter", "converter" }, { "ViewPlayer", "player" }, { "ViewControl", "control" }, { "ViewRecording", "recordOptions" }, { "SettingsGeneral", "settings" }, { "SettingsTimeline", "timelineSettings" }, { "SettingsAudio", "audio" }, { "SettingsKeys", "keybinds" } };
|
|
440
|
+
for (int i = 0; i < entries.GetLength(0); i++) { string action = entries[i, 1]; C<MenuItem>(entries[i, 0]).Click += async delegate { await ExecuteInterfaceAction(action); }; }
|
|
441
|
+
C<MenuItem>("FileExit").Click += delegate { window.Close(); };
|
|
442
|
+
C<MenuItem>("InstallAudioDrivers").Click += delegate { DriverSetup.ShowGuide(window); };
|
|
443
|
+
C<MenuItem>("HelpGuide").Click += delegate { ShowHelp(); };
|
|
444
|
+
C<MenuItem>("HelpAbout").Click += delegate { OpenSettings("about"); };
|
|
445
|
+
}
|
|
446
|
+
private void RefreshMenuGestures()
|
|
447
|
+
{
|
|
448
|
+
string[,] entries = { { "FileNew", "new" }, { "FileOpen", "open" }, { "FileSave", "save" }, { "FileSaveAs", "saveAs" }, { "FileImport", "import" }, { "FileExport", "export" }, { "ViewHome", "home" }, { "ViewEditor", "editor" }, { "ViewConverter", "converter" }, { "ViewPlayer", "player" }, { "SettingsGeneral", "settings" } };
|
|
449
|
+
for (int i = 0; i < entries.GetLength(0); i++) { string gesture = InterfacePreferences.Gesture(entries[i, 1]); C<MenuItem>(entries[i, 0]).InputGestureText = gesture == "Unassigned" ? "" : gesture; }
|
|
450
|
+
}
|
|
451
|
+
private async void HandleInterfaceKey(object sender, System.Windows.Input.KeyEventArgs e)
|
|
452
|
+
{
|
|
453
|
+
var key = e.Key == System.Windows.Input.Key.System ? e.SystemKey : e.Key;
|
|
454
|
+
if (key == System.Windows.Input.Key.Escape && workspace == 3 && editorView != null && editorView.HandleKey(e)) { e.Handled = true; return; }
|
|
455
|
+
string action = InterfacePreferences.Resolve(key, System.Windows.Input.Keyboard.Modifiers);
|
|
456
|
+
if (action == null) return;
|
|
457
|
+
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; }
|
|
460
|
+
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
|
+
e.Handled = true; await ExecuteInterfaceAction(action);
|
|
462
|
+
}
|
|
463
|
+
private async Task ExecuteInterfaceAction(string action)
|
|
464
|
+
{
|
|
465
|
+
switch (action) {
|
|
466
|
+
case "home": OpenHome(); return;
|
|
467
|
+
case "editor": OpenWorkspace(true); return;
|
|
468
|
+
case "converter": SwitchPage(false); return;
|
|
469
|
+
case "player": OpenWorkspace(false); return;
|
|
470
|
+
case "settings": OpenSettings("general"); return;
|
|
471
|
+
case "timelineSettings": OpenSettings("timeline"); return;
|
|
472
|
+
case "audio": OpenSettings("audio"); return;
|
|
473
|
+
case "keybinds": OpenSettings("keybinds"); return;
|
|
474
|
+
case "control": C<Button>("ControlTab").RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); return;
|
|
475
|
+
case "recordOptions": if (workspace != 3) OpenWorkspace(true); if (editorView != null) editorView.ShowRecordingOptions(true); return;
|
|
476
|
+
case "record": if (recorder != null) await StopRecording(true); else StartRecording(); return;
|
|
477
|
+
case "play": if (recorder != null) { await StopRecording(true); return; } break;
|
|
478
|
+
case "stop": if (recorder != null) { await StopRecording(true); return; } break;
|
|
479
|
+
}
|
|
480
|
+
if (workspace == 2 && await mediaPlayerView.ExecuteAction(action)) return;
|
|
481
|
+
if (workspace == 0 && (action == "export" || action == "save")) { await ExportFiles(); return; }
|
|
482
|
+
if (workspace != 3) OpenWorkspace(true);
|
|
483
|
+
if (workspace == 3 && editorView != null) await editorView.ExecuteAction(action);
|
|
484
|
+
}
|
|
485
|
+
private void InitializeConverterLayout()
|
|
486
|
+
{
|
|
487
|
+
var page = C<Grid>("ConvertPage"); var pageParent = page.Parent as Panel; if (pageParent != null) pageParent.Children.Remove(page);
|
|
488
|
+
var options = C<Border>("ExportCard"); var optionsParent = options.Parent as Panel; if (optionsParent != null) optionsParent.Children.Remove(options);
|
|
489
|
+
converterPane = new DockablePane(page, options, "Convert to", true);
|
|
490
|
+
C<Grid>("ConverterHost").Children.Add(converterPane);
|
|
491
|
+
}
|
|
492
|
+
private void InitializeConversion()
|
|
493
|
+
{
|
|
494
|
+
C<ComboBox>("ConvertProfile").ItemsSource = new[] { "General audio", "Zoom R16" }; C<ComboBox>("ConvertProfile").SelectedIndex = 0;
|
|
495
|
+
C<ComboBox>("ConvertFormat").ItemsSource = new[] { "WAV PCM", "WAV float", "FLAC", "MP3", "AAC", "AIFF" }; C<ComboBox>("ConvertFormat").SelectedIndex = 0;
|
|
496
|
+
C<ComboBox>("ConvertRate").ItemsSource = new[] { "Keep source rate", "44.1 kHz", "48 kHz", "96 kHz" }; C<ComboBox>("ConvertRate").SelectedIndex = 0;
|
|
497
|
+
C<ComboBox>("ConvertProfile").SelectionChanged += delegate { UpdateConversionControls(); };
|
|
498
|
+
C<ComboBox>("ConvertFormat").SelectionChanged += delegate { UpdateConversionControls(); };
|
|
499
|
+
UpdateConversionControls();
|
|
500
|
+
}
|
|
501
|
+
private void UpdateConversionControls()
|
|
502
|
+
{
|
|
503
|
+
if (configuringConversion) return; configuringConversion = true;
|
|
504
|
+
bool r16 = C<ComboBox>("ConvertProfile").SelectedIndex == 1;
|
|
505
|
+
if (r16) { C<ComboBox>("ConvertFormat").SelectedIndex = 0; C<ComboBox>("ConvertRate").SelectedIndex = 1; }
|
|
506
|
+
C<ComboBox>("ConvertFormat").IsEnabled = C<ComboBox>("ConvertRate").IsEnabled = !r16 && !busy;
|
|
507
|
+
int format = C<ComboBox>("ConvertFormat").SelectedIndex;
|
|
508
|
+
if (format == 3 && C<ComboBox>("ConvertRate").SelectedIndex == 3) C<ComboBox>("ConvertRate").SelectedIndex = 2;
|
|
509
|
+
C<RadioButton>("Bits16").IsEnabled = C<RadioButton>("Bits24").IsEnabled = !busy && (format == 0 || format == 2 || format == 5);
|
|
510
|
+
C<CheckBox>("ProtectCheck").IsEnabled = !busy && format != 1;
|
|
511
|
+
C<Button>("ExportButton").Content = "Convert files";
|
|
512
|
+
configuringConversion = false;
|
|
513
|
+
}
|
|
514
|
+
private ConversionOptions SelectedConversion(int bits, bool protect)
|
|
515
|
+
{
|
|
516
|
+
return new ConversionOptions { Format = (string)C<ComboBox>("ConvertFormat").SelectedItem, Bits = bits, Protect = protect && C<ComboBox>("ConvertFormat").SelectedIndex != 1,
|
|
517
|
+
R16 = C<ComboBox>("ConvertProfile").SelectedIndex == 1, SampleRate = new[] { 0, 44100, 48000, 96000 }[Math.Max(0, C<ComboBox>("ConvertRate").SelectedIndex)] };
|
|
518
|
+
}
|
|
519
|
+
private void ShowAudioDevices()
|
|
520
|
+
{
|
|
521
|
+
OpenSettings("audio");
|
|
522
|
+
}
|
|
523
|
+
private void RefreshDevices(bool configureInput = true)
|
|
524
|
+
{
|
|
525
|
+
if (recorder != null || busy) return;
|
|
526
|
+
var previous = C<ComboBox>("InputDevice").SelectedItem as InputChoice;
|
|
527
|
+
var inputs = AudioInputs.Choices();
|
|
528
|
+
string wanted = previous == null ? prefs.AudioInputKey : previous.Key;
|
|
529
|
+
configuringCapture = true;
|
|
530
|
+
C<ComboBox>("InputDevice").ItemsSource = inputs;
|
|
531
|
+
C<ComboBox>("InputDevice").SelectedItem = inputs.FirstOrDefault(d => d.Key == wanted) ?? inputs.FirstOrDefault(d => IsH5studio(d.Driver)) ?? inputs.FirstOrDefault(d => d.Driver != null && IsZoom(d.Driver)) ?? inputs.FirstOrDefault();
|
|
532
|
+
var chosenInput = C<ComboBox>("InputDevice").SelectedItem as InputChoice; InterfacePreferences.Current.AudioInputKey = chosenInput == null ? null : chosenInput.Key;
|
|
533
|
+
configuringCapture = false;
|
|
534
|
+
if (configureInput) ConfigureRecordingDevice();
|
|
535
|
+
}
|
|
536
|
+
private static bool IsH5studio(string text) { return CaptureLayout.IsH5studio(text); }
|
|
537
|
+
private static bool IsZoom(string text) { return text.IndexOf("R16",StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("R24",StringComparison.OrdinalIgnoreCase) >= 0; }
|
|
538
|
+
private void ShowRecordingFormat()
|
|
539
|
+
{
|
|
540
|
+
bool floating = C<RadioButton>("RecordFloat").IsChecked == true;
|
|
541
|
+
var input = C<ComboBox>("InputDevice").SelectedItem as InputChoice;
|
|
542
|
+
if (input != null && input.Driver == null) {
|
|
543
|
+
C<TextBlock>("CaptureFormatText").Text = "Windows microphone input: 16-bit PCM. " + (floating ? "A float WAV keeps those samples; it does not add native float headroom. Use the H5studio ASIO input for native 32-bit float." : "The selected WAV format is used for saving."); return;
|
|
544
|
+
}
|
|
545
|
+
C<TextBlock>("CaptureFormatText").Text = floating
|
|
546
|
+
? "32-bit float master: peaks above 0 dBFS are preserved when the driver supplies float audio. Convert files makes PCM copies."
|
|
547
|
+
: "Integer PCM WAV for playback and sharing. Set input levels below 0 dBFS to prevent clipping.";
|
|
548
|
+
}
|
|
549
|
+
private void ConfigureRecordingDevice()
|
|
550
|
+
{
|
|
551
|
+
if (configuringCapture || recorder != null) return;
|
|
552
|
+
var input = C<ComboBox>("InputDevice").SelectedItem as InputChoice;
|
|
553
|
+
string driver = input == null ? null : input.Driver;
|
|
554
|
+
bool changed = input == null || input.Key != configuredDriver;
|
|
555
|
+
configuredDriver = input == null ? null : input.Key; configuringCapture = true;
|
|
556
|
+
C<Button>("StartRecordButton").IsEnabled = false;
|
|
557
|
+
try {
|
|
558
|
+
if (input == null) {
|
|
559
|
+
C<TextBlock>("RecordingState").Text = "No recording input found";
|
|
560
|
+
C<TextBlock>("RecordSetupHint").Text = "Connect a Windows microphone or a Zoom recorder, then Refresh. Audio devices at the top of the app lists recording inputs and playback outputs.";
|
|
561
|
+
ShowRecordingFormat(); return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
bool h5 = IsH5studio(driver);
|
|
565
|
+
C<TextBlock>("RecordSetupHint").Text = h5
|
|
566
|
+
? "H5studio: USB > Audio Interface. Use Stereo mix for a mixed microphone recording, or Multi Track for separate inputs. Audio I/F Only allows 44.1 / 48 / 96 kHz."
|
|
567
|
+
: driver != null ? "Select the inputs exposed by your ASIO driver. On an R16 choose USB > AUDIO INTERFACE. Monitor live sound through the recorder's headphones."
|
|
568
|
+
: "Record from your selected Windows microphone (Realtek, USB or headset). Input is captured as 16-bit PCM; allow desktop microphone access in Windows Settings. Use headphones to avoid feedback.";
|
|
569
|
+
var oldMode = C<ComboBox>("InputMode").SelectedItem as ModeChoice;
|
|
570
|
+
var oldRate = C<ComboBox>("RecordRate").SelectedItem as RateChoice;
|
|
571
|
+
var modes = new List<ModeChoice>(); var rates = new List<RateChoice>();
|
|
572
|
+
int count;
|
|
573
|
+
if (driver != null) using (var probe = new AsioOut(driver)) {
|
|
574
|
+
count = Math.Min(8, probe.DriverInputChannelCount);
|
|
575
|
+
if (count < 1) throw new IOException("This ASIO device has no recording inputs.");
|
|
576
|
+
modes.Add(new ModeChoice { Mode = -1, Name = "All " + count + " inputs - separate mono WAV files" });
|
|
577
|
+
if (count >= 2) modes.Add(new ModeChoice { Mode = 0, Name = "Inputs 1 + 2 - one stereo WAV" });
|
|
578
|
+
for (int i = 0; i < count; i++) modes.Add(new ModeChoice { Mode = i + 1, Name = "Input " + (i + 1) + " - " + probe.AsioInputChannelName(i) });
|
|
579
|
+
foreach (int rate in new[] { 44100, 48000, 96000 })
|
|
580
|
+
if (probe.IsSampleRateSupported(rate)) rates.Add(new RateChoice { Rate = rate });
|
|
581
|
+
if (rates.Count == 0) throw new IOException("The recorder is not offering 44.1, 48 or 96 kHz. Check its USB audio mode.");
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
count = AudioInputs.Channels(input);
|
|
585
|
+
modes.Add(new ModeChoice { Mode = -1, Name = "All " + count + " microphone channels - separate WAV files" });
|
|
586
|
+
if (count >= 2) modes.Add(new ModeChoice { Mode = 0, Name = "Channels 1 + 2 - stereo WAV" });
|
|
587
|
+
for (int i = 0; i < count; i++) modes.Add(new ModeChoice { Mode = i + 1, Name = "Microphone channel " + (i + 1) + " - mono WAV" });
|
|
588
|
+
foreach (int rate in AudioInputs.Rates(input, count)) rates.Add(new RateChoice { Rate = rate });
|
|
589
|
+
if (rates.Count == 0) throw new IOException("Windows is not offering 16-bit PCM microphone capture at 44.1, 48 or 96 kHz. Check microphone access in Windows Settings.");
|
|
590
|
+
}
|
|
591
|
+
for (int i = 0; i < meters.Length; i++) {
|
|
592
|
+
var panel = (StackPanel)meters[i].Parent;
|
|
593
|
+
panel.Visibility = i < count ? Visibility.Visible : Visibility.Collapsed;
|
|
594
|
+
}
|
|
595
|
+
configuredInputChannels = count; C<System.Windows.Controls.Primitives.UniformGrid>("MeterPanel").Columns = Math.Min(4, count);
|
|
596
|
+
C<ComboBox>("InputMode").ItemsSource = null;
|
|
597
|
+
C<ComboBox>("InputMode").Items.Clear(); C<ComboBox>("InputMode").ItemsSource = modes;
|
|
598
|
+
C<ComboBox>("InputMode").SelectedItem = !changed && oldMode != null ? modes.FirstOrDefault(m => m.Mode == oldMode.Mode) : null;
|
|
599
|
+
if (C<ComboBox>("InputMode").SelectedItem == null) C<ComboBox>("InputMode").SelectedItem = h5 ? modes.FirstOrDefault(m => m.Mode == 0) ?? modes[0] : modes[0];
|
|
600
|
+
C<ComboBox>("RecordRate").ItemsSource = rates;
|
|
601
|
+
int wantedRate = !changed && oldRate != null ? oldRate.Rate : h5 || driver == null ? 48000 : 44100;
|
|
602
|
+
C<ComboBox>("RecordRate").SelectedItem = rates.FirstOrDefault(r => r.Rate == wantedRate) ?? rates[0];
|
|
603
|
+
if (changed) C<RadioButton>(h5 ? "RecordFloat" : driver == null ? "Record16" : "Record24").IsChecked = true;
|
|
604
|
+
C<TextBlock>("RecordingState").Text = "Ready when you are";
|
|
605
|
+
C<Button>("StartRecordButton").IsEnabled = true;
|
|
606
|
+
ShowRecordingFormat();
|
|
607
|
+
}
|
|
608
|
+
catch (Exception ex) {
|
|
609
|
+
C<TextBlock>("RecordingState").Text = "Device not ready";
|
|
610
|
+
C<TextBlock>("CaptureFormatText").Text = ex.Message + " Connect the recorder in Audio Interface mode, close other audio apps, then Refresh.";
|
|
611
|
+
}
|
|
612
|
+
finally { configuringCapture = false; }
|
|
613
|
+
}
|
|
614
|
+
private async void StartRecording()
|
|
615
|
+
{
|
|
616
|
+
if (busy || recorder != null) return;
|
|
617
|
+
var input = C<ComboBox>("InputDevice").SelectedItem as InputChoice;
|
|
618
|
+
if (input == null) { Status("Choose a recording input under Audio devices or Record drums, then click Refresh."); return; }
|
|
619
|
+
if (configuredDriver != input.Key) ConfigureRecordingDevice();
|
|
620
|
+
if (!C<Button>("StartRecordButton").IsEnabled) { Status("Choose a ready recording input in Record options."); return; }
|
|
621
|
+
string driver = input.Name;
|
|
622
|
+
|
|
623
|
+
int mode = ((ModeChoice)C<ComboBox>("InputMode").SelectedItem).Mode;
|
|
624
|
+
int bits = C<RadioButton>("RecordFloat").IsChecked == true ? 32 : C<RadioButton>("Record16").IsChecked == true ? 16 : 24;
|
|
625
|
+
var rateChoice = C<ComboBox>("RecordRate").SelectedItem as RateChoice;
|
|
626
|
+
if (rateChoice == null) { Status("Choose a sample rate before recording."); return; }
|
|
627
|
+
if (editorView == null) OpenWorkspace(true);
|
|
628
|
+
if (editorView == null || editorView.IsBusy) { Status("Finish the editor task before recording."); return; }
|
|
629
|
+
int files = mode == -1 ? configuredInputChannels : 1;
|
|
630
|
+
StopWorkspaces();
|
|
631
|
+
recordingTrack = editorView.SelectedTrack; recordingAnchor = editorView.PlayheadFrame;
|
|
632
|
+
if (recordingTrack + files > 16) { Status("Select an earlier track so all recording channels fit within the 16-track timeline."); return; }
|
|
633
|
+
string folder = System.IO.Path.Combine(prefs.RecordFolder,"Take_" + DateTime.Now.ToString("yyyyMMdd_HHmmss_fff",CultureInfo.InvariantCulture));
|
|
634
|
+
recorder = new Recorder();
|
|
635
|
+
Exception startError = null;
|
|
636
|
+
try {
|
|
637
|
+
Status("Connecting to " + driver + "…");
|
|
638
|
+
recorder.Start(input,mode,bits,folder,rateChoice.Rate); captureStarted = DateTime.UtcNow;
|
|
639
|
+
editorView.SetRecordingState(true);
|
|
640
|
+
foreach (string name in new[] { "RecordTab", "StartRecordButton", "InputDevice", "InputMode", "Record16", "Record24", "RecordFloat", "RecordRate", "RefreshDevicesButton", "ChangeRecordFolderButton" }) C<Control>(name).IsEnabled = false;
|
|
641
|
+
C<Button>("StopRecordButton").IsEnabled = true;
|
|
642
|
+
C<TextBlock>("RecordingState").Text = "● Recording"; C<TextBlock>("RecordingState").Foreground = Brush("#B52C36");
|
|
643
|
+
Status("Recording. Click Stop & save when you finish.");
|
|
644
|
+
}
|
|
645
|
+
catch (Exception ex) { startError = ex; }
|
|
646
|
+
if (startError != null) {
|
|
647
|
+
if (editorView != null) editorView.SetRecordingState(false);
|
|
648
|
+
Recorder failed = recorder; recorder = null; failed.Stop(); await failed.Finish();
|
|
649
|
+
// Keep any partial audio from a failed start; never delete a take.
|
|
650
|
+
Status("Could not start recording: " + startError.Message + " Check Audio Interface mode and close other audio apps.");
|
|
651
|
+
RefreshDevices();
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
private async Task StopRecording(bool addFiles)
|
|
655
|
+
{
|
|
656
|
+
if (recorder == null || stopping) return;
|
|
657
|
+
stopping = true; C<Button>("StopRecordButton").IsEnabled = false;
|
|
658
|
+
Recorder finished = recorder;
|
|
659
|
+
Status("Saving your recording…");
|
|
660
|
+
finished.Stop(); await finished.Finish(); recorder = null;
|
|
661
|
+
if (editorView != null) editorView.SetRecordingState(false);
|
|
662
|
+
foreach (string name in new[] { "RecordTab", "StartRecordButton", "InputDevice", "InputMode", "Record16", "Record24", "RecordFloat", "RecordRate", "RefreshDevicesButton", "ChangeRecordFolderButton" }) C<Control>(name).IsEnabled = true;
|
|
663
|
+
C<TextBlock>("RecordingState").Text = finished.Error == null ? "Take saved" : "Recording stopped - check status";
|
|
664
|
+
C<TextBlock>("RecordingState").Foreground = Brush(finished.Error == null ? "#006D68" : "#B52C36");
|
|
665
|
+
stopping = false;
|
|
666
|
+
if (Interlocked.Read(ref finished.Frames) > 0 && finished.Paths.Count > 0 && editorView != null) {
|
|
667
|
+
try { await editorView.AddRecordedFiles(finished.Paths, recordingAnchor, recordingTrack); }
|
|
668
|
+
catch (Exception ex) { Status("Take saved to its folder, but could not be added to the timeline: " + ex.Message); return; }
|
|
669
|
+
}
|
|
670
|
+
Status(finished.Error == null ? "Take saved and added to the timeline. Original recording files are preserved." : "Recording stopped: " + finished.Error.Message + " Captured audio was kept.");
|
|
671
|
+
}
|
|
672
|
+
private async Task Tick()
|
|
673
|
+
{
|
|
674
|
+
if (closeWhenIdle && !busy && !WorkspacesBusy && recorder == null) { closeWhenIdle = false; window.Close(); return; }
|
|
675
|
+
if (recorder == null || stopping) return;
|
|
676
|
+
long frames = Interlocked.Read(ref recorder.Frames);
|
|
677
|
+
C<TextBlock>("RecordTime").Text = WorkspaceUi.Clock(frames / (double)recorder.SampleRate);
|
|
678
|
+
if (frames == 0 && (DateTime.UtcNow - captureStarted).TotalSeconds > 8 && recorder.Error == null)
|
|
679
|
+
recorder.Error = new IOException("No audio arrived from the selected input. Check its connection, microphone permissions and selected channels.");
|
|
680
|
+
if (recorder.SourceFormat != null)
|
|
681
|
+
C<TextBlock>("CaptureFormatText").Text = "Input: " + (recorder.SourceIsFloat ? "native 32-bit float" : recorder.SourceFormat) + " | WAV: " + recorder.Bits + (recorder.Bits == 32 ? "-bit float" : "-bit PCM") + " | " + recorder.SampleRate + " Hz";
|
|
682
|
+
bool tooLoud = false;
|
|
683
|
+
for (int i = 0; i < 8; i++) {
|
|
684
|
+
float peak = Interlocked.Exchange(ref recorder.Peaks[i],0);
|
|
685
|
+
double db = peak > 0 ? 20 * Math.Log10(peak) : -60;
|
|
686
|
+
meters[i].Value = Math.Max(0,Math.Min(1,(db + 60) / 60));
|
|
687
|
+
meters[i].Foreground = Brush(peak >= .99 ? "#B52C36" : "#007A6B");
|
|
688
|
+
meterLabels[i].Text = peak > .001 ? db.ToString("0",CultureInfo.InvariantCulture) + " dB" : "—";
|
|
689
|
+
tooLoud |= peak >= .99;
|
|
690
|
+
}
|
|
691
|
+
C<TextBlock>("RecordHint").Text = recorder.Bits == 32 && recorder.SourceIsFloat
|
|
692
|
+
? (tooLoud ? "Peaks above 0 dBFS are kept in the float master. Enable Prevent PCM clipping when making a PCM copy." : "Recording the original float samples. Monitor through the recorder's headphones.")
|
|
693
|
+
: (tooLoud ? "Input is near full scale. Turn down the recorder's input level to prevent clipping." : "Set input levels on the recorder. Adjust a copy in Media Player.");
|
|
694
|
+
if (recorder.SourceFormat == "Windows 16-bit PCM") C<TextBlock>("RecordHint").Text = tooLoud ? "Input is near full scale. Lower the microphone level in Windows Sound settings." : "Windows microphone recording. The take will be added to the selected timeline track.";
|
|
695
|
+
if (recorder.Error != null) await StopRecording(true);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
private string ChooseFolder(string initial)
|
|
699
|
+
{
|
|
700
|
+
using (var dialog = new System.Windows.Forms.FolderBrowserDialog { Description = "Choose where to save your WAV files", SelectedPath = initial, ShowNewFolderButton = true })
|
|
701
|
+
return dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK ? dialog.SelectedPath : null;
|
|
702
|
+
}
|
|
703
|
+
private void OpenFolder(string path) { try { Directory.CreateDirectory(path); Process.Start(new ProcessStartInfo(path) { UseShellExecute = true }); } catch (Exception ex) { Status("Could not open this folder: " + ex.Message); } }
|
|
704
|
+
private void SavePreferences()
|
|
705
|
+
{
|
|
706
|
+
try { var input = C<ComboBox>("InputDevice").SelectedItem as InputChoice; prefs.AudioInputKey = input == null ? prefs.AudioInputKey : input.Key; prefs.AudioOutputKey = AudioOutputs.PreferredKey; prefs.Bits = C<RadioButton>("Bits16").IsChecked == true ? 16 : 24; Directory.CreateDirectory(System.IO.Path.GetDirectoryName(settings)); using (var stream = File.Create(settings)) new XmlSerializer(typeof(Preferences)).Serialize(stream,prefs); } catch { }
|
|
707
|
+
}
|
|
708
|
+
private void ShowHelp()
|
|
709
|
+
{
|
|
710
|
+
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" +
|
|
712
|
+
"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
|
+
"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
|
+
"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" +
|
|
715
|
+
"EDITOR + RECORD\nThe Editor opens by default. Record options contains input, channels, WAV format and sample rate. Record or R starts a take at the playhead on the selected track. Stop & save preserves the take and adds it to the timeline. All inputs uses consecutive tracks. Use hardware direct monitoring while recording; software overdub synchronisation is not provided.\n\n" +
|
|
716
|
+
"H5STUDIO FLOAT\nOn the recorder choose USB > Audio Interface > Connect. Audio I/F Only supports 44.1, 48 and 96 kHz. Stereo mix exposes a mixed stereo signal; Multi Track exposes separate inputs. Choose 32-bit float to keep the original float samples, including peaks above 0 dBFS. The app checks that the H5studio driver is actually sending float samples.\n\n" +
|
|
717
|
+
"PLAYBACK COMPATIBILITY\nSome players reject float or multichannel WAV files. Open Converter from View, enable Prevent PCM clipping and choose 16-bit or 24-bit WAV PCM. The original float master remains unchanged.\n\n" +
|
|
718
|
+
"FULL CONTROL\nAn R16 front-panel view with PC multitrack playback, mixer, EQ, recording and Mackie MIDI control. Open Menu reveals files, track settings, saved effects and devices. Escape returns home.\n\n" +
|
|
719
|
+
"R16 memory is available in CARD READER mode. Open a PRJDATA.ZDT project to read assignments and settings. Save project copy preserves original metadata and audio, with supported edits, in a new folder. Zoom's internal effect DSP and analogue knobs cannot be remotely emulated.\n\n" +
|
|
720
|
+
"Use the R16 gain knobs to set recording levels. Listen through the R16's direct headphone monitoring.\n\n" +
|
|
721
|
+
"Install Drivers opens a short guide and links to Zoom's official driver download pages.\n\n" +
|
|
722
|
+
"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
|
+
"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",
|
|
726
|
+
"Flux — quick help",MessageBoxButton.OK,MessageBoxImage.Information);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
public static class Program
|
|
731
|
+
{
|
|
732
|
+
[STAThread]
|
|
733
|
+
public static int Main(string[] args)
|
|
734
|
+
{
|
|
735
|
+
if (args.Length > 0 && args[0] == "--self-test") return SelfTest.Run(args.Skip(1).ToArray());
|
|
736
|
+
if (args.Length > 0 && args[0] == "--float-self-test") return FloatRecordingTests.Run(args.Skip(1).ToArray());
|
|
737
|
+
if (args.Length > 0 && args[0] == "--grid-self-test") return TempoGridTests.Run(args.Skip(1).ToArray());
|
|
738
|
+
if (args.Length > 0 && args[0] == "--workspace-self-test") return WorkspaceTests.Run(args.Skip(1).ToArray());
|
|
739
|
+
if (args.Length > 0 && args[0] == "--interface-self-test") return InterfaceTests.Run(args.Skip(1).ToArray());
|
|
740
|
+
if (args.Length > 0 && args[0] == "--conversion-self-test") return ConversionTests.Run(args.Skip(1).ToArray());
|
|
741
|
+
if (args.Length > 0 && args[0] == "--output-self-test") return PlaybackTests.Run(args.Skip(1).ToArray());
|
|
742
|
+
if (args.Length > 1 && args[0] == "--audio-diagnostics") { File.WriteAllText(args[1], AudioDiagnostics.Report(null), System.Text.Encoding.UTF8); return 0; }
|
|
743
|
+
if (args.Length > 0 && args[0] == "--control-self-test") return ControlTests.Run(args.Skip(1).ToArray());
|
|
744
|
+
var app = new Application { ShutdownMode = ShutdownMode.OnExplicitShutdown };
|
|
745
|
+
app.DispatcherUnhandledException += delegate(object sender, DispatcherUnhandledExceptionEventArgs e) {
|
|
746
|
+
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 { }
|
|
747
|
+
MessageBox.Show("Flux encountered a problem.\n\n" + e.Exception.Message,"Flux",MessageBoxButton.OK,MessageBoxImage.Error); e.Handled = true;
|
|
748
|
+
};
|
|
749
|
+
Window splash = null;
|
|
750
|
+
try {
|
|
751
|
+
var started = Stopwatch.StartNew();
|
|
752
|
+
var logo = BrandArtwork.Raster();
|
|
753
|
+
splash = new Window {
|
|
754
|
+
Width = 300, Height = 320, WindowStyle = WindowStyle.None,
|
|
755
|
+
AllowsTransparency = true, Background = Brushes.Transparent,
|
|
756
|
+
ResizeMode = ResizeMode.NoResize, ShowInTaskbar = false,
|
|
757
|
+
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
|
758
|
+
Content = new Image { Source = logo, Stretch = Stretch.Uniform }
|
|
759
|
+
};
|
|
760
|
+
splash.Show();
|
|
761
|
+
// Paint the transparent logo before constructing the main interface.
|
|
762
|
+
app.Dispatcher.Invoke((Action)delegate { },DispatcherPriority.Render);
|
|
763
|
+
var main = new MainWindow();
|
|
764
|
+
app.MainWindow = main.NativeWindow;
|
|
765
|
+
app.ShutdownMode = ShutdownMode.OnMainWindowClose;
|
|
766
|
+
var splashTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(Math.Max(1,300 - started.ElapsedMilliseconds)) };
|
|
767
|
+
splashTimer.Tick += delegate {
|
|
768
|
+
splashTimer.Stop();
|
|
769
|
+
// Remove the splash before Loaded/device discovery runs on the main window.
|
|
770
|
+
splash.Close(); main.Show();
|
|
771
|
+
};
|
|
772
|
+
splashTimer.Start();
|
|
773
|
+
return app.Run();
|
|
774
|
+
}
|
|
775
|
+
catch (Exception ex) {
|
|
776
|
+
if (splash != null) splash.Close();
|
|
777
|
+
MessageBox.Show("Flux could not open. Please reinstall it.\n\n" + ex.Message,"Flux",MessageBoxButton.OK,MessageBoxImage.Error); return 1;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|