@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
package/source/Main.xaml
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Flux" Width="1280" Height="820" MinWidth="850" MinHeight="560" WindowStartupLocation="CenterScreen" Background="{DynamicResource ThemeF3F5F5}" FontFamily="Segoe UI" FontSize="14" Foreground="{DynamicResource Theme182C37}" AllowDrop="True">
|
|
3
|
+
<Window.Resources>
|
|
4
|
+
<SolidColorBrush x:Key="Teal" Color="#006D68" />
|
|
5
|
+
<Style TargetType="Button">
|
|
6
|
+
<Setter Property="FontSize" Value="13" />
|
|
7
|
+
<Setter Property="FontWeight" Value="SemiBold" />
|
|
8
|
+
<Setter Property="MinHeight" Value="32" />
|
|
9
|
+
<Setter Property="Padding" Value="11,5" />
|
|
10
|
+
<Setter Property="Cursor" Value="Hand" />
|
|
11
|
+
<Setter Property="Background" Value="{DynamicResource ThemeFFFFFF}" />
|
|
12
|
+
<Setter Property="Foreground" Value="{DynamicResource Theme182C37}" />
|
|
13
|
+
<Setter Property="BorderBrush" Value="{DynamicResource ThemeC3CED0}" />
|
|
14
|
+
<Setter Property="BorderThickness" Value="1" />
|
|
15
|
+
<Setter Property="Template">
|
|
16
|
+
<Setter.Value>
|
|
17
|
+
<ControlTemplate TargetType="Button">
|
|
18
|
+
<Border x:Name="B" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="9" Padding="{TemplateBinding Padding}">
|
|
19
|
+
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" RecognizesAccessKey="True" />
|
|
20
|
+
</Border>
|
|
21
|
+
<ControlTemplate.Triggers>
|
|
22
|
+
<Trigger Property="IsMouseOver" Value="True">
|
|
23
|
+
<Setter TargetName="B" Property="Opacity" Value="0.84" />
|
|
24
|
+
</Trigger>
|
|
25
|
+
<Trigger Property="IsPressed" Value="True">
|
|
26
|
+
<Setter TargetName="B" Property="Opacity" Value="0.65" />
|
|
27
|
+
</Trigger>
|
|
28
|
+
<Trigger Property="IsEnabled" Value="False">
|
|
29
|
+
<Setter TargetName="B" Property="Opacity" Value="0.42" />
|
|
30
|
+
</Trigger>
|
|
31
|
+
<Trigger Property="IsKeyboardFocused" Value="True">
|
|
32
|
+
<Setter TargetName="B" Property="BorderBrush" Value="#D16A19" />
|
|
33
|
+
<Setter TargetName="B" Property="BorderThickness" Value="3" />
|
|
34
|
+
</Trigger>
|
|
35
|
+
</ControlTemplate.Triggers>
|
|
36
|
+
</ControlTemplate>
|
|
37
|
+
</Setter.Value>
|
|
38
|
+
</Setter>
|
|
39
|
+
</Style>
|
|
40
|
+
<Style x:Key="Primary" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
|
41
|
+
<Setter Property="Background" Value="{DynamicResource ThemeAccent}" />
|
|
42
|
+
<Setter Property="Foreground" Value="{DynamicResource ThemeOnAccent}" />
|
|
43
|
+
<Setter Property="BorderBrush" Value="{DynamicResource Theme006D68}" />
|
|
44
|
+
</Style>
|
|
45
|
+
<Style TargetType="ComboBox">
|
|
46
|
+
<Setter Property="FontSize" Value="13" />
|
|
47
|
+
<Setter Property="MinHeight" Value="32" />
|
|
48
|
+
<Setter Property="Padding" Value="7,4" />
|
|
49
|
+
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
50
|
+
</Style>
|
|
51
|
+
<Style TargetType="RadioButton">
|
|
52
|
+
<Setter Property="FontSize" Value="13" />
|
|
53
|
+
<Setter Property="Margin" Value="0,4,12,4" />
|
|
54
|
+
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
55
|
+
</Style>
|
|
56
|
+
<Style TargetType="CheckBox">
|
|
57
|
+
<Setter Property="FontSize" Value="13" />
|
|
58
|
+
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
59
|
+
</Style>
|
|
60
|
+
<Style x:Key="Card" TargetType="Border">
|
|
61
|
+
<Setter Property="Background" Value="{DynamicResource ThemeFFFFFF}" />
|
|
62
|
+
<Setter Property="CornerRadius" Value="14" />
|
|
63
|
+
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDFE5E6}" />
|
|
64
|
+
<Setter Property="BorderThickness" Value="1" />
|
|
65
|
+
<Setter Property="Padding" Value="22" />
|
|
66
|
+
</Style>
|
|
67
|
+
</Window.Resources>
|
|
68
|
+
<DockPanel Margin="16,10,16,10">
|
|
69
|
+
<Grid DockPanel.Dock="Top" Margin="0,0,0,6">
|
|
70
|
+
<Grid.RowDefinitions><RowDefinition Height="26" /><RowDefinition Height="36" /></Grid.RowDefinitions>
|
|
71
|
+
<Menu Margin="-5,0,0,0" FontSize="12" Background="{DynamicResource ThemeF3F5F5}" Foreground="{DynamicResource Theme182C37}">
|
|
72
|
+
<MenuItem Header="_File">
|
|
73
|
+
<MenuItem x:Name="FileNew" Header="New session" />
|
|
74
|
+
<MenuItem x:Name="FileOpen" Header="Open session..." />
|
|
75
|
+
<MenuItem x:Name="FileSave" Header="Save" />
|
|
76
|
+
<MenuItem x:Name="FileSaveAs" Header="Save as..." />
|
|
77
|
+
<Separator />
|
|
78
|
+
<MenuItem x:Name="FileImport" Header="Add audio..." />
|
|
79
|
+
<MenuItem x:Name="FileExport" Header="Export audio..." />
|
|
80
|
+
<Separator />
|
|
81
|
+
<MenuItem x:Name="FileExit" Header="Exit" />
|
|
82
|
+
</MenuItem>
|
|
83
|
+
<MenuItem Header="_View">
|
|
84
|
+
<MenuItem x:Name="ViewHome" Header="Home" />
|
|
85
|
+
<MenuItem x:Name="ViewEditor" Header="Editor + Record" />
|
|
86
|
+
<MenuItem x:Name="ViewRecording" Header="Recording options" />
|
|
87
|
+
<MenuItem x:Name="ViewConverter" Header="Converter" />
|
|
88
|
+
<MenuItem x:Name="ViewPlayer" Header="Media Player" />
|
|
89
|
+
<MenuItem x:Name="ViewControl" Header="R16 Full Control" />
|
|
90
|
+
</MenuItem>
|
|
91
|
+
<MenuItem Header="_Install">
|
|
92
|
+
<MenuItem x:Name="InstallAudioDrivers" Header="Audio driver guide..." />
|
|
93
|
+
</MenuItem>
|
|
94
|
+
<MenuItem Header="_Settings">
|
|
95
|
+
<MenuItem x:Name="SettingsGeneral" Header="General..." />
|
|
96
|
+
<MenuItem x:Name="SettingsTimeline" Header="Timeline / grid..." />
|
|
97
|
+
<MenuItem x:Name="SettingsAudio" Header="Audio devices..." />
|
|
98
|
+
<MenuItem x:Name="SettingsKeys" Header="Keybinds..." />
|
|
99
|
+
</MenuItem>
|
|
100
|
+
<MenuItem Header="_Help">
|
|
101
|
+
<MenuItem x:Name="HelpGuide" Header="Quick help..." />
|
|
102
|
+
<MenuItem x:Name="HelpAbout" Header="About Flux..." />
|
|
103
|
+
</MenuItem>
|
|
104
|
+
</Menu>
|
|
105
|
+
<Grid Grid.Row="1">
|
|
106
|
+
<Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions>
|
|
107
|
+
<StackPanel Orientation="Horizontal" Margin="0,0,14,0">
|
|
108
|
+
<Image x:Name="BrandLogo" Width="44" Height="34" Stretch="Uniform" Margin="0,0,5,0" />
|
|
109
|
+
<TextBlock Text="Flux" FontSize="15" FontWeight="SemiBold" VerticalAlignment="Center" />
|
|
110
|
+
</StackPanel>
|
|
111
|
+
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
|
112
|
+
<Button x:Name="HomeTab" Content="Home" MinHeight="26" Width="60" FontSize="12" Padding="7,2" Margin="0,0,4,0" />
|
|
113
|
+
<Button x:Name="RecordTab" Content="Record" Visibility="Collapsed" />
|
|
114
|
+
<Button x:Name="MediaPlayerTab" Content="Player" MinHeight="26" Width="64" FontSize="12" Padding="7,2" Margin="0,0,4,0" />
|
|
115
|
+
<Button x:Name="EditorTab" Content="Editor + Record" MinHeight="26" Width="116" FontSize="12" Padding="7,2" Margin="0,0,4,0" />
|
|
116
|
+
<Button x:Name="ControlTab" Content="R16 Control" MinHeight="26" Width="92" FontSize="12" Padding="7,2" />
|
|
117
|
+
</StackPanel>
|
|
118
|
+
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
|
119
|
+
<Button x:Name="ThemeToggle" Content="Light" MinHeight="26" FontSize="12" Padding="8,2" Margin="0,0,5,0" ToolTip="Switch between dark and light themes" />
|
|
120
|
+
<Button x:Name="AudioDevicesButton" Content="Audio devices" MinHeight="26" FontSize="12" Padding="8,2" ToolTip="Select playback and recording devices" />
|
|
121
|
+
<Button x:Name="InstallDriversButton" Content="Install Drivers" Visibility="Collapsed" />
|
|
122
|
+
<Button x:Name="HelpButton" Content="Help" Visibility="Collapsed" />
|
|
123
|
+
</StackPanel>
|
|
124
|
+
</Grid>
|
|
125
|
+
</Grid>
|
|
126
|
+
<Border x:Name="StatusHost" DockPanel.Dock="Bottom" Background="{DynamicResource ThemeF3F5F5}" BorderBrush="{DynamicResource ThemeDFE5E6}" BorderThickness="0,1,0,0" Padding="6,4" Margin="0,6,0,0" Visibility="Collapsed">
|
|
127
|
+
<Grid>
|
|
128
|
+
<Grid.ColumnDefinitions>
|
|
129
|
+
<ColumnDefinition />
|
|
130
|
+
<ColumnDefinition Width="160" />
|
|
131
|
+
<ColumnDefinition Width="Auto" />
|
|
132
|
+
</Grid.ColumnDefinitions>
|
|
133
|
+
<TextBlock x:Name="StatusText" Text="" TextWrapping="Wrap" VerticalAlignment="Center" FontSize="12" Foreground="{DynamicResource Theme526771}" />
|
|
134
|
+
<ProgressBar x:Name="WorkProgress" Grid.Column="1" Height="8" Minimum="0" Maximum="100" Visibility="Collapsed" Margin="12,0" VerticalAlignment="Center" />
|
|
135
|
+
<Button x:Name="CancelButton" Grid.Column="2" Content="Cancel" Visibility="Collapsed" MinHeight="26" FontSize="12" Padding="8,3" />
|
|
136
|
+
</Grid>
|
|
137
|
+
</Border>
|
|
138
|
+
<Border xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="ExportCard" Padding="12" Background="{DynamicResource ThemeFFFFFF}">
|
|
139
|
+
<StackPanel>
|
|
140
|
+
<WrapPanel>
|
|
141
|
+
<StackPanel Width="166" Margin="0,0,10,8">
|
|
142
|
+
<TextBlock Text="Output type" FontSize="12" Margin="0,0,0,4" />
|
|
143
|
+
<ComboBox x:Name="ConvertFormat" MinHeight="30" />
|
|
144
|
+
</StackPanel>
|
|
145
|
+
<StackPanel Width="160" Margin="0,0,10,8">
|
|
146
|
+
<TextBlock Text="Sample rate" FontSize="12" Margin="0,0,0,4" />
|
|
147
|
+
<ComboBox x:Name="ConvertRate" MinHeight="30" />
|
|
148
|
+
</StackPanel>
|
|
149
|
+
<StackPanel Width="160" Margin="0,0,10,8">
|
|
150
|
+
<TextBlock Text="Device preset" FontSize="12" Margin="0,0,0,4" />
|
|
151
|
+
<ComboBox x:Name="ConvertProfile" MinHeight="30" />
|
|
152
|
+
</StackPanel>
|
|
153
|
+
<StackPanel Margin="0,0,10,8">
|
|
154
|
+
<TextBlock Text="PCM depth" FontSize="12" Margin="0,0,0,4" />
|
|
155
|
+
<StackPanel Orientation="Horizontal">
|
|
156
|
+
<RadioButton x:Name="Bits16" Content="16-bit" GroupName="ExportBits" />
|
|
157
|
+
<RadioButton x:Name="Bits24" Content="24-bit" IsChecked="True" GroupName="ExportBits" />
|
|
158
|
+
</StackPanel>
|
|
159
|
+
</StackPanel>
|
|
160
|
+
<CheckBox x:Name="ProtectCheck" Content="Prevent PCM clipping" IsChecked="True" Margin="0,16,0,8" ToolTip="Reduce an over-range source only when needed for integer PCM. Gain editing is available in Media Player." />
|
|
161
|
+
</WrapPanel>
|
|
162
|
+
<DockPanel Margin="0,0,0,9">
|
|
163
|
+
<Button x:Name="ChangeFolderButton" DockPanel.Dock="Right" Content="Folder..." MinHeight="28" Padding="8,3" Margin="8,0,0,0" />
|
|
164
|
+
<TextBlock x:Name="ExportFolderText" TextTrimming="CharacterEllipsis" VerticalAlignment="Center" FontSize="12" />
|
|
165
|
+
</DockPanel>
|
|
166
|
+
<WrapPanel>
|
|
167
|
+
<Button x:Name="ExportButton" Content="Convert files" Width="116" HorizontalAlignment="Left" MinHeight="30" Padding="10,4" Style="{StaticResource Primary}" Margin="0,0,8,0" />
|
|
168
|
+
<Button x:Name="OpenExportsButton" Content="Open folder" MinHeight="30" Padding="10,4" />
|
|
169
|
+
</WrapPanel>
|
|
170
|
+
</StackPanel>
|
|
171
|
+
</Border>
|
|
172
|
+
<Grid>
|
|
173
|
+
<ScrollViewer x:Name="MainScroll" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Padding="0,0,4,0">
|
|
174
|
+
<Grid>
|
|
175
|
+
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="ConvertPage">
|
|
176
|
+
<Grid.RowDefinitions>
|
|
177
|
+
<RowDefinition Height="Auto" />
|
|
178
|
+
<RowDefinition />
|
|
179
|
+
</Grid.RowDefinitions>
|
|
180
|
+
<StackPanel Margin="0,0,0,16">
|
|
181
|
+
<TextBlock Text="Converter" FontSize="24" FontWeight="SemiBold" />
|
|
182
|
+
<TextBlock Text="Drop a file. Choose the output type. Convert." FontSize="13" Foreground="{DynamicResource Theme526771}" Margin="0,5,0,0" />
|
|
183
|
+
</StackPanel>
|
|
184
|
+
<Grid Grid.Row="1">
|
|
185
|
+
<Grid.ColumnDefinitions>
|
|
186
|
+
<ColumnDefinition Width="0.8*" />
|
|
187
|
+
<ColumnDefinition Width="1.2*" />
|
|
188
|
+
</Grid.ColumnDefinitions>
|
|
189
|
+
<Border Margin="0,0,14,0" BorderBrush="{DynamicResource Theme7EA7A4}" BorderThickness="1" CornerRadius="8" Background="{DynamicResource ThemeEAF6F3}" Padding="18">
|
|
190
|
+
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
|
191
|
+
<TextBlock Text="⇧" FontSize="38" HorizontalAlignment="Center" Foreground="{DynamicResource Theme006D68}" />
|
|
192
|
+
<TextBlock Text="Drop audio here" FontSize="19" FontWeight="SemiBold" HorizontalAlignment="Center" Margin="0,12,0,7" />
|
|
193
|
+
<TextBlock Text="MP3, AIFF, WAV, FLAC and more" TextWrapping="Wrap" TextAlignment="Center" FontSize="12" Foreground="{DynamicResource Theme526771}" />
|
|
194
|
+
<Button x:Name="AddButton" Content="Choose files..." HorizontalAlignment="Center" MinHeight="30" Padding="14,4" Margin="0,18,0,0" Style="{StaticResource Primary}" />
|
|
195
|
+
</StackPanel>
|
|
196
|
+
</Border>
|
|
197
|
+
<Grid Grid.Column="1">
|
|
198
|
+
<Grid.RowDefinitions>
|
|
199
|
+
<RowDefinition Height="Auto" />
|
|
200
|
+
<RowDefinition />
|
|
201
|
+
<RowDefinition Height="Auto" />
|
|
202
|
+
</Grid.RowDefinitions>
|
|
203
|
+
<DockPanel Margin="0,0,0,10">
|
|
204
|
+
<TextBlock x:Name="QueueTitle" Text="Files to convert" FontSize="17" FontWeight="SemiBold" />
|
|
205
|
+
</DockPanel>
|
|
206
|
+
<ListBox x:Name="FileList" Grid.Row="1" Background="{DynamicResource ThemeFFFFFF}" BorderBrush="{DynamicResource ThemeC3CED0}" BorderThickness="1" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
|
207
|
+
<ListBox.ItemTemplate>
|
|
208
|
+
<DataTemplate>
|
|
209
|
+
<Border BorderBrush="{DynamicResource ThemeDFE5E6}" BorderThickness="0,0,0,1" Padding="8,9">
|
|
210
|
+
<StackPanel>
|
|
211
|
+
<TextBlock Text="{Binding DisplayName}" FontWeight="SemiBold" TextTrimming="CharacterEllipsis" />
|
|
212
|
+
<TextBlock Text="{Binding SourceSummary}" FontSize="11" Foreground="{DynamicResource Theme526771}" Margin="0,3,0,3" TextTrimming="CharacterEllipsis" />
|
|
213
|
+
<TextBlock Text="{Binding Status}" FontSize="12" Foreground="{DynamicResource Theme006D68}" TextWrapping="Wrap" />
|
|
214
|
+
</StackPanel>
|
|
215
|
+
</Border>
|
|
216
|
+
</DataTemplate>
|
|
217
|
+
</ListBox.ItemTemplate>
|
|
218
|
+
</ListBox>
|
|
219
|
+
<WrapPanel Grid.Row="2" Margin="0,8,0,0">
|
|
220
|
+
<Button x:Name="RemoveButton" Content="Remove selected" Margin="0,0,6,0" />
|
|
221
|
+
<Button x:Name="ClearButton" Content="Clear list" />
|
|
222
|
+
</WrapPanel>
|
|
223
|
+
</Grid>
|
|
224
|
+
</Grid>
|
|
225
|
+
</Grid>
|
|
226
|
+
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="RecordPage" Visibility="Collapsed">
|
|
227
|
+
<TextBlock Text="Record to timeline" FontSize="18" FontWeight="SemiBold" Margin="0,0,0,9" />
|
|
228
|
+
<TextBlock x:Name="RecordSetupHint" Text="Choose a microphone or Zoom ASIO input." FontSize="12" Foreground="{DynamicResource Theme526771}" TextWrapping="Wrap" Margin="0,0,0,12" />
|
|
229
|
+
<WrapPanel>
|
|
230
|
+
<StackPanel Width="266" Margin="0,0,10,8">
|
|
231
|
+
<Grid Margin="0,0,0,5">
|
|
232
|
+
<Grid.ColumnDefinitions>
|
|
233
|
+
<ColumnDefinition />
|
|
234
|
+
<ColumnDefinition Width="Auto" />
|
|
235
|
+
</Grid.ColumnDefinitions>
|
|
236
|
+
<TextBlock Text="Recording input" FontSize="12" VerticalAlignment="Center" />
|
|
237
|
+
<Button x:Name="RefreshDevicesButton" Grid.Column="1" Content="Refresh" MinHeight="26" Padding="7,3" FontSize="12" />
|
|
238
|
+
</Grid>
|
|
239
|
+
<ComboBox x:Name="InputDevice" Margin="0,0,0,10" />
|
|
240
|
+
<TextBlock Text="Channels" FontSize="12" Margin="0,0,0,5" />
|
|
241
|
+
<ComboBox x:Name="InputMode" Margin="0,0,0,10" />
|
|
242
|
+
</StackPanel>
|
|
243
|
+
<StackPanel Width="266" Margin="0,0,10,8">
|
|
244
|
+
<TextBlock Text="Save recording as" FontSize="12" />
|
|
245
|
+
<WrapPanel Margin="0,4,0,7">
|
|
246
|
+
<RadioButton x:Name="Record16" Content="16-bit PCM" GroupName="RecordBits" FontSize="12" />
|
|
247
|
+
<RadioButton x:Name="Record24" Content="24-bit PCM" GroupName="RecordBits" IsChecked="True" FontSize="12" />
|
|
248
|
+
<RadioButton x:Name="RecordFloat" Content="32-bit float" GroupName="RecordBits" FontSize="12" />
|
|
249
|
+
</WrapPanel>
|
|
250
|
+
<TextBlock Text="Sample rate" FontSize="12" Margin="0,0,0,5" />
|
|
251
|
+
<ComboBox x:Name="RecordRate" Margin="0,0,0,8" />
|
|
252
|
+
<TextBlock x:Name="CaptureFormatText" Text="Use H5studio ASIO for native 32-bit float." FontSize="11" Foreground="{DynamicResource Theme526771}" TextWrapping="Wrap" Margin="0,0,0,12" />
|
|
253
|
+
</StackPanel>
|
|
254
|
+
<StackPanel Width="266" Margin="0,0,10,8">
|
|
255
|
+
<Grid>
|
|
256
|
+
<Grid.ColumnDefinitions>
|
|
257
|
+
<ColumnDefinition />
|
|
258
|
+
<ColumnDefinition Width="Auto" />
|
|
259
|
+
</Grid.ColumnDefinitions>
|
|
260
|
+
<TextBlock x:Name="RecordingState" Text="Ready" FontSize="13" FontWeight="SemiBold" VerticalAlignment="Center" />
|
|
261
|
+
<TextBlock x:Name="RecordTime" Grid.Column="1" Text="00:00" FontSize="22" FontFamily="Consolas" />
|
|
262
|
+
</Grid>
|
|
263
|
+
<UniformGrid x:Name="MeterPanel" Columns="4" Margin="0,7,0,8" />
|
|
264
|
+
<TextBlock x:Name="RecordHint" Text="Recordings are added at the timeline playhead on the selected track." FontSize="11" Foreground="{DynamicResource Theme526771}" TextWrapping="Wrap" Margin="0,0,0,10" />
|
|
265
|
+
<Grid>
|
|
266
|
+
<Grid.ColumnDefinitions>
|
|
267
|
+
<ColumnDefinition />
|
|
268
|
+
<ColumnDefinition />
|
|
269
|
+
</Grid.ColumnDefinitions>
|
|
270
|
+
<Button x:Name="StartRecordButton" Content="Record" Background="{DynamicResource ThemeB52C36}" Foreground="{DynamicResource ThemeOnAccent}" MinHeight="36" FontSize="14" Margin="0,0,6,0" />
|
|
271
|
+
<Button x:Name="StopRecordButton" Grid.Column="1" Content="Stop & save" MinHeight="36" FontSize="13" IsEnabled="False" />
|
|
272
|
+
</Grid>
|
|
273
|
+
</StackPanel>
|
|
274
|
+
<StackPanel Width="266" Margin="0,0,10,8">
|
|
275
|
+
<Button x:Name="OpenRecordingsButton" Content="My recordings" Margin="0,9,0,10" MinHeight="28" FontSize="12" />
|
|
276
|
+
<TextBlock x:Name="RecordFolderText" TextTrimming="CharacterEllipsis" FontSize="11" Foreground="{DynamicResource Theme526771}" Margin="0,0,0,5" />
|
|
277
|
+
<Button x:Name="ChangeRecordFolderButton" Content="Change recording folder..." FontSize="12" MinHeight="28" />
|
|
278
|
+
</StackPanel>
|
|
279
|
+
</WrapPanel>
|
|
280
|
+
</StackPanel>
|
|
281
|
+
</Grid>
|
|
282
|
+
</ScrollViewer>
|
|
283
|
+
<Grid x:Name="ConverterHost" Visibility="Collapsed" />
|
|
284
|
+
<Grid x:Name="HomeHost" Visibility="Collapsed" />
|
|
285
|
+
<Grid x:Name="MediaPlayerHost" Visibility="Collapsed" />
|
|
286
|
+
<Grid x:Name="EditorHost" Visibility="Collapsed" />
|
|
287
|
+
</Grid>
|
|
288
|
+
</DockPanel>
|
|
289
|
+
</Window>
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.IO;
|
|
3
|
+
using System.Linq;
|
|
4
|
+
using System.Threading;
|
|
5
|
+
using System.Threading.Tasks;
|
|
6
|
+
using System.Collections.Generic;
|
|
7
|
+
using System.Collections.ObjectModel;
|
|
8
|
+
using System.Globalization;
|
|
9
|
+
using System.Windows;
|
|
10
|
+
using System.Windows.Controls;
|
|
11
|
+
using System.Windows.Controls.Primitives;
|
|
12
|
+
using System.Windows.Input;
|
|
13
|
+
using System.Windows.Media;
|
|
14
|
+
using System.Windows.Shapes;
|
|
15
|
+
using System.Windows.Threading;
|
|
16
|
+
using NAudio.Wave;
|
|
17
|
+
using NAudio.Wave.SampleProviders;
|
|
18
|
+
using Path = System.IO.Path;
|
|
19
|
+
|
|
20
|
+
namespace DrumBuddy
|
|
21
|
+
{
|
|
22
|
+
public sealed class MediaPlayerView : Grid, IDisposable
|
|
23
|
+
{
|
|
24
|
+
private readonly Window owner;
|
|
25
|
+
private readonly string cache;
|
|
26
|
+
private readonly Action<string> status;
|
|
27
|
+
private readonly Action beforePlay;
|
|
28
|
+
private readonly ObservableCollection<Clip> playlist = new ObservableCollection<Clip>();
|
|
29
|
+
private readonly ListBox list;
|
|
30
|
+
private readonly TextBlock title, info, clock, hint, gainText, gainHint;
|
|
31
|
+
private readonly Slider seek, volume, gain;
|
|
32
|
+
private readonly Canvas waveform;
|
|
33
|
+
private readonly Line playLine;
|
|
34
|
+
private readonly Button play, add, cancel, saveCopy;
|
|
35
|
+
private readonly CheckBox repeat, protectGain;
|
|
36
|
+
private readonly ComboBox saveFormat;
|
|
37
|
+
private readonly DispatcherTimer timer;
|
|
38
|
+
private readonly WorkspaceOutput output;
|
|
39
|
+
private readonly AudioOutputPicker outputPicker;
|
|
40
|
+
private readonly DockablePane gainPane;
|
|
41
|
+
private WaveFileReader reader;
|
|
42
|
+
private CancellationTokenSource loading;
|
|
43
|
+
private bool updating, dragging, disposed;
|
|
44
|
+
private double stoppedAt;
|
|
45
|
+
public bool IsBusy { get { return loading != null; } }
|
|
46
|
+
private Clip Selected { get { return list.SelectedItem as Clip; } }
|
|
47
|
+
|
|
48
|
+
public MediaPlayerView(Window owner, string cache, Action<string> status, Action beforePlay)
|
|
49
|
+
{
|
|
50
|
+
this.owner = owner; this.cache = cache; this.status = status; this.beforePlay = beforePlay;
|
|
51
|
+
Background = WorkspaceUi.Color("#F3F5F5");
|
|
52
|
+
output = new WorkspaceOutput(Dispatcher);
|
|
53
|
+
RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
|
54
|
+
RowDefinitions.Add(new RowDefinition());
|
|
55
|
+
var heading = new DockPanel { Margin = new Thickness(0, 0, 0, 12) };
|
|
56
|
+
add = WorkspaceUi.Button("Open audio…", async delegate { await ChooseFiles(); }); DockPanel.SetDock(add, Dock.Right); heading.Children.Add(add);
|
|
57
|
+
cancel = WorkspaceUi.Button("Cancel loading", delegate { if (loading != null) loading.Cancel(); }); cancel.Visibility = Visibility.Collapsed; DockPanel.SetDock(cancel, Dock.Right); heading.Children.Add(cancel);
|
|
58
|
+
heading.Children.Add(WorkspaceUi.Text("Media Player", 27)); Children.Add(heading);
|
|
59
|
+
var body = new Grid(); SetRow(body, 1); Children.Add(body);
|
|
60
|
+
body.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(230) }); body.ColumnDefinitions.Add(new ColumnDefinition());
|
|
61
|
+
var queue = new DockPanel { Margin = new Thickness(0, 0, 18, 0) };
|
|
62
|
+
var queueButtons = new WrapPanel(); DockPanel.SetDock(queueButtons, Dock.Bottom); queue.Children.Add(queueButtons);
|
|
63
|
+
queueButtons.Children.Add(WorkspaceUi.Button("Remove", delegate { Remove(); }));
|
|
64
|
+
queueButtons.Children.Add(WorkspaceUi.Button("Clear playlist", delegate { if (!IsBusy) { Stop(); playlist.Clear(); ShowClip(); } }));
|
|
65
|
+
list = new ListBox { ItemsSource = playlist, DisplayMemberPath = "DisplayName", FontSize = 16, BorderBrush = WorkspaceUi.Color("#D4DFDF"), Padding = new Thickness(6) };
|
|
66
|
+
list.SelectionChanged += delegate { Stop(); stoppedAt = 0; ShowClip(); };
|
|
67
|
+
list.MouseDoubleClick += delegate { PlayPause(); }; queue.Children.Add(list); body.Children.Add(queue);
|
|
68
|
+
var panel = new StackPanel { Margin = new Thickness(14) };
|
|
69
|
+
var card = new Border { Background = WorkspaceUi.Color("#FFFFFF"), BorderBrush = WorkspaceUi.Color("#DCE5E4"), BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(14), Child = new ScrollViewer { Content = panel, VerticalScrollBarVisibility = ScrollBarVisibility.Auto, HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled } };
|
|
70
|
+
SetColumn(card, 1); body.Children.Add(card);
|
|
71
|
+
title = WorkspaceUi.Text("Open a recording or a song", 24); title.FontWeight = FontWeights.SemiBold; panel.Children.Add(title);
|
|
72
|
+
info = WorkspaceUi.Text("PCM and float WAV, FLAC, MP3, AIFF, and audio from videos.", 15); info.Foreground = WorkspaceUi.Color("#526771"); info.Margin = new Thickness(0, 8, 0, 18); panel.Children.Add(info);
|
|
73
|
+
waveform = new Canvas { Height = 190, Background = WorkspaceUi.Color("#EAF5F2"), ClipToBounds = true, Cursor = Cursors.Hand };
|
|
74
|
+
playLine = new Line { Stroke = WorkspaceUi.Color("#D17626"), StrokeThickness = 2 };
|
|
75
|
+
waveform.SizeChanged += delegate { DrawWave(); };
|
|
76
|
+
waveform.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs e) { if (Selected != null) Seek(Selected.Duration * e.GetPosition(waveform).X / Math.Max(1, waveform.ActualWidth)); };
|
|
77
|
+
panel.Children.Add(waveform);
|
|
78
|
+
seek = new Slider { Minimum = 0, Maximum = 1, IsMoveToPointEnabled = true, Margin = new Thickness(0, 14, 0, 4), IsEnabled = false };
|
|
79
|
+
seek.PreviewMouseLeftButtonDown += delegate { dragging = true; };
|
|
80
|
+
seek.PreviewMouseLeftButtonUp += delegate { dragging = false; Seek(seek.Value); };
|
|
81
|
+
seek.ValueChanged += delegate { if (!updating && !dragging) Seek(seek.Value); }; panel.Children.Add(seek);
|
|
82
|
+
clock = WorkspaceUi.Text("00:00.00 / 00:00.00", 18); clock.FontFamily = new FontFamily("Consolas"); panel.Children.Add(clock);
|
|
83
|
+
var transport = new WrapPanel { Margin = new Thickness(0, 17, 0, 3) };
|
|
84
|
+
transport.Children.Add(WorkspaceUi.Button("Previous", delegate { Next(-1); }));
|
|
85
|
+
play = WorkspaceUi.Button("Play", delegate { PlayPause(); }); play.MinWidth = 105; transport.Children.Add(play);
|
|
86
|
+
transport.Children.Add(WorkspaceUi.Button("Stop", delegate { Stop(); stoppedAt = 0; UpdatePosition(); }));
|
|
87
|
+
transport.Children.Add(WorkspaceUi.Button("Next", delegate { Next(1); })); panel.Children.Add(transport);
|
|
88
|
+
repeat = new CheckBox { Content = "Repeat track", FontSize = 15, Margin = new Thickness(0, 0, 0, 12) }; panel.Children.Add(repeat);
|
|
89
|
+
panel.Children.Add(WorkspaceUi.Text("Playback volume", 15));
|
|
90
|
+
volume = new Slider { Minimum = 0, Maximum = 100, Value = 80, Margin = new Thickness(0, 8, 0, 12) };
|
|
91
|
+
volume.ValueChanged += delegate { output.Volume(PlaybackGain()); }; panel.Children.Add(volume);
|
|
92
|
+
var gainPanel = new StackPanel { Margin = new Thickness(12) };
|
|
93
|
+
var gainHeading = new DockPanel();
|
|
94
|
+
gainText = WorkspaceUi.Text("0.0 dB", 16); DockPanel.SetDock(gainText, Dock.Right); gainHeading.Children.Add(gainText); gainHeading.Children.Add(WorkspaceUi.Text("Amplify / reduce", 14)); gainPanel.Children.Add(gainHeading);
|
|
95
|
+
var gainControls = new Grid(); gainControls.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); gainControls.ColumnDefinitions.Add(new ColumnDefinition()); gainControls.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
|
96
|
+
var quieter = WorkspaceUi.Button("−", delegate { gain.Value -= 1; }); gainControls.Children.Add(quieter);
|
|
97
|
+
gain = new Slider { Minimum = -36, Maximum = 36, TickFrequency = .5, IsSnapToTickEnabled = true, Margin = new Thickness(8, 0, 12, 6), VerticalAlignment = VerticalAlignment.Center };
|
|
98
|
+
gain.ValueChanged += delegate { if (!updating && Selected != null) Selected.Gain = gain.Value; UpdateGain(); }; SetColumn(gain, 1); gainControls.Children.Add(gain);
|
|
99
|
+
var louder = WorkspaceUi.Button("+", delegate { gain.Value += 1; }); SetColumn(louder, 2); gainControls.Children.Add(louder); gainPanel.Children.Add(gainControls);
|
|
100
|
+
var gainActions = new WrapPanel(); gainActions.Children.Add(WorkspaceUi.Button("Reset", delegate { gain.Value = 0; }));
|
|
101
|
+
gainActions.Children.Add(WorkspaceUi.Button("Set safe level", delegate { if (Selected != null && Selected.Peak > 0) gain.Value = Math.Max(-36, Math.Min(36, -1 - Selected.PeakDb)); }));
|
|
102
|
+
protectGain = new CheckBox { Content = "Prevent clipping", IsChecked = true, Margin = new Thickness(8, 0, 0, 6), VerticalAlignment = VerticalAlignment.Center }; protectGain.Checked += delegate { UpdateGain(); }; protectGain.Unchecked += delegate { UpdateGain(); }; gainActions.Children.Add(protectGain); gainPanel.Children.Add(gainActions);
|
|
103
|
+
gainHint = WorkspaceUi.Text("Gain changes playback and saved copies. The original stays unchanged.", 12); gainHint.Foreground = WorkspaceUi.Color("#526771"); gainHint.Margin = new Thickness(0, 0, 0, 8); gainPanel.Children.Add(gainHint);
|
|
104
|
+
var saveRow = new WrapPanel(); saveFormat = new ComboBox { ItemsSource = new[] { "24-bit PCM WAV", "16-bit PCM WAV", "32-bit float WAV" }, SelectedIndex = 0, Width = 160, MinHeight = 30, Margin = new Thickness(0, 0, 8, 0) }; saveRow.Children.Add(saveFormat);
|
|
105
|
+
saveCopy = WorkspaceUi.Button("Save copy…", async delegate { await SaveAdjustedCopy(); }); saveCopy.Width = 110; saveCopy.MinHeight = 30; saveRow.Children.Add(saveCopy); gainPanel.Children.Add(saveRow);
|
|
106
|
+
body.Children.Remove(card); gainPane = new DockablePane(card, gainPanel, "Gain / Save"); SetColumn(gainPane, 1); body.Children.Add(gainPane);
|
|
107
|
+
SizeChanged += delegate { body.ColumnDefinitions[0].Width = new GridLength(ActualWidth < 1000 ? 180 : 230); };
|
|
108
|
+
hint = WorkspaceUi.Text("Drop files here or use Open audio. Playback uses Flux's decoder; no Windows Media Player codec is required.", 14); hint.Foreground = WorkspaceUi.Color("#526771"); panel.Children.Add(hint);
|
|
109
|
+
outputPicker = new AudioOutputPicker(delegate { Stop(); }, status); panel.Children.Insert(2, outputPicker);
|
|
110
|
+
timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; timer.Tick += delegate { UpdatePosition(); }; timer.Start();
|
|
111
|
+
ShowClip();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private async Task ChooseFiles()
|
|
115
|
+
{
|
|
116
|
+
if (IsBusy) return;
|
|
117
|
+
var dialog = new Microsoft.Win32.OpenFileDialog { Title = "Open audio or video for audio playback", Filter = WorkspaceUi.AudioFilter, Multiselect = true };
|
|
118
|
+
if (dialog.ShowDialog(owner) == true) await AddFiles(dialog.FileNames);
|
|
119
|
+
}
|
|
120
|
+
public async Task AddFiles(IEnumerable<string> paths)
|
|
121
|
+
{
|
|
122
|
+
if (IsBusy || disposed) return;
|
|
123
|
+
var files = paths.Where(File.Exists).Select(Path.GetFullPath).Distinct(StringComparer.OrdinalIgnoreCase)
|
|
124
|
+
.Where(p => !playlist.Any(c => string.Equals(c.Source, p, StringComparison.OrdinalIgnoreCase))).ToArray();
|
|
125
|
+
if (files.Length == 0) return;
|
|
126
|
+
loading = new CancellationTokenSource(); add.IsEnabled = false; cancel.Visibility = Visibility.Visible;
|
|
127
|
+
int added = 0, failed = 0; string lastFailure = "";
|
|
128
|
+
try {
|
|
129
|
+
for (int i = 0; i < files.Length; i++) {
|
|
130
|
+
loading.Token.ThrowIfCancellationRequested(); string path = files[i];
|
|
131
|
+
status("Reading " + Path.GetFileName(path) + " for the Media Player…");
|
|
132
|
+
try {
|
|
133
|
+
var clip = await Task.Run(() => Audio.Load(path, cache, loading.Token, null));
|
|
134
|
+
playlist.Add(clip); added++; if (list.SelectedItem == null) list.SelectedItem = clip;
|
|
135
|
+
}
|
|
136
|
+
catch (OperationCanceledException) { throw; }
|
|
137
|
+
catch (Exception ex) { failed++; lastFailure = Path.GetFileName(path) + ": " + ex.Message; }
|
|
138
|
+
}
|
|
139
|
+
status(added + " file(s) ready in Media Player." + (failed > 0 ? " " + failed + " failed. " + lastFailure : ""));
|
|
140
|
+
}
|
|
141
|
+
catch (OperationCanceledException) { status("Loading stopped. Files already opened are kept in the playlist."); }
|
|
142
|
+
finally { loading.Dispose(); loading = null; add.IsEnabled = true; cancel.Visibility = Visibility.Collapsed; ShowClip(); }
|
|
143
|
+
}
|
|
144
|
+
private void Remove()
|
|
145
|
+
{
|
|
146
|
+
if (IsBusy || Selected == null) return; int index = list.SelectedIndex; var clip = Selected;
|
|
147
|
+
Stop(); playlist.Remove(clip); if (playlist.Count > 0) list.SelectedIndex = Math.Min(index, playlist.Count - 1); ShowClip();
|
|
148
|
+
}
|
|
149
|
+
private float PlaybackGain()
|
|
150
|
+
{ return (float)(volume.Value / 100 * Math.Pow(10, Selected == null ? 0 : Selected.AppliedGain(protectGain.IsChecked == true) / 20)); }
|
|
151
|
+
private void UpdateGain()
|
|
152
|
+
{
|
|
153
|
+
var clip = Selected; if (gainText == null || gainHint == null || protectGain == null) return;
|
|
154
|
+
double desired = clip == null ? 0 : clip.Gain, actual = clip == null ? 0 : clip.AppliedGain(protectGain.IsChecked == true);
|
|
155
|
+
gainText.Text = (desired > 0 ? "+" : "") + desired.ToString("0.0", CultureInfo.InvariantCulture) + " dB";
|
|
156
|
+
gainHint.Text = clip == null ? "Gain changes playback and saved copies. The original stays unchanged."
|
|
157
|
+
: actual < desired - .01 ? "Using " + actual.ToString("0.0", CultureInfo.InvariantCulture) + " dB to prevent clipping. The original stays unchanged."
|
|
158
|
+
: "Copy peak: " + (clip.PeakDb + actual).ToString("0.0", CultureInfo.InvariantCulture) + " dBFS. The original stays unchanged.";
|
|
159
|
+
output.Volume(PlaybackGain()); DrawWave();
|
|
160
|
+
}
|
|
161
|
+
private Clip ExportSnapshot()
|
|
162
|
+
{
|
|
163
|
+
var clip = Selected;
|
|
164
|
+
return new Clip { Source = clip.Source, Name = clip.Name, SourceFormat = clip.SourceFormat, Channels = clip.Channels, SourceRate = clip.SourceRate, Duration = clip.Duration, Peak = clip.Peak, Gain = clip.Gain, Ready = true };
|
|
165
|
+
}
|
|
166
|
+
private ConversionOptions ExportOptions(string stem)
|
|
167
|
+
{ return new ConversionOptions { Format = saveFormat.SelectedIndex == 2 ? "WAV float" : "WAV PCM", Bits = saveFormat.SelectedIndex == 1 ? 16 : 24, Protect = protectGain.IsChecked == true, OutputStem = stem }; }
|
|
168
|
+
private async Task SaveAdjustedCopy()
|
|
169
|
+
{
|
|
170
|
+
if (Selected == null || IsBusy) return;
|
|
171
|
+
var dialog = new Microsoft.Win32.SaveFileDialog { Title = "Save an adjusted audio copy", Filter = "WAV audio|*.wav", DefaultExt = ".wav", FileName = Path.GetFileNameWithoutExtension(Selected.Name) + " adjusted.wav", OverwritePrompt = false };
|
|
172
|
+
if (dialog.ShowDialog(owner) != true) return;
|
|
173
|
+
var snapshot = ExportSnapshot(); var options = ExportOptions(Path.GetFileNameWithoutExtension(dialog.FileName));
|
|
174
|
+
Stop(); loading = new CancellationTokenSource(); add.IsEnabled = false; cancel.Content = "Cancel save"; cancel.Visibility = Visibility.Visible; ShowClip();
|
|
175
|
+
try {
|
|
176
|
+
string result = await Task.Run(() => ConversionFormats.Export(snapshot, Path.GetDirectoryName(dialog.FileName), options, loading.Token, null));
|
|
177
|
+
status("Saved adjusted copy: " + result + ". Original audio kept.");
|
|
178
|
+
}
|
|
179
|
+
catch (OperationCanceledException) { status("Saving cancelled. Original audio kept."); }
|
|
180
|
+
catch (Exception ex) { status("Could not save the adjusted copy: " + ex.Message); }
|
|
181
|
+
finally { loading.Dispose(); loading = null; add.IsEnabled = true; cancel.Content = "Cancel loading"; cancel.Visibility = Visibility.Collapsed; ShowClip(); }
|
|
182
|
+
}
|
|
183
|
+
public async Task<bool> ExecuteAction(string action)
|
|
184
|
+
{
|
|
185
|
+
switch (action) {
|
|
186
|
+
case "save": case "saveAs": case "export": await SaveAdjustedCopy(); break;
|
|
187
|
+
case "import": await ChooseFiles(); break;
|
|
188
|
+
case "play": PlayPause(); break;
|
|
189
|
+
case "stop": Stop(); break;
|
|
190
|
+
case "start": Seek(0); break;
|
|
191
|
+
case "end": if (Selected != null) Seek(Selected.Duration); break;
|
|
192
|
+
default: return false;
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
private void PlayPause()
|
|
197
|
+
{
|
|
198
|
+
if (Selected == null || IsBusy) return;
|
|
199
|
+
if (output.Playing) { output.Pause(); play.Content = "Resume"; return; }
|
|
200
|
+
if (output.Paused) { output.Resume(); play.Content = "Pause"; return; }
|
|
201
|
+
try {
|
|
202
|
+
beforePlay(); if (stoppedAt >= Selected.Duration) stoppedAt = 0;
|
|
203
|
+
reader = new WaveFileReader(Selected.Cache);
|
|
204
|
+
reader.Position = Math.Min(reader.Length, (long)Math.Round(stoppedAt * Audio.Rate) * reader.WaveFormat.BlockAlign);
|
|
205
|
+
output.Open(reader.ToSampleProvider(), stoppedAt, PlaybackGain(), Ended, outputPicker.Selected); play.Content = "Pause";
|
|
206
|
+
hint.Text = "Playing through " + output.Description + ". The original file stays unchanged.";
|
|
207
|
+
}
|
|
208
|
+
catch (Exception ex) { Stop(); status("Could not play this file: " + ex.Message); }
|
|
209
|
+
}
|
|
210
|
+
private void Ended(Exception error)
|
|
211
|
+
{
|
|
212
|
+
if (reader != null) { reader.Dispose(); reader = null; }
|
|
213
|
+
play.Content = "Play";
|
|
214
|
+
if (error != null) { stoppedAt = 0; status("Playback stopped: " + error.Message); return; }
|
|
215
|
+
stoppedAt = Selected == null ? 0 : Selected.Duration;
|
|
216
|
+
if (repeat.IsChecked == true) { stoppedAt = 0; PlayPause(); }
|
|
217
|
+
else if (list.SelectedIndex + 1 < playlist.Count) Next(1);
|
|
218
|
+
else UpdatePosition();
|
|
219
|
+
}
|
|
220
|
+
public void Stop()
|
|
221
|
+
{
|
|
222
|
+
if (output.Playing || output.Paused) stoppedAt = output.Position;
|
|
223
|
+
output.Stop(); if (reader != null) { reader.Dispose(); reader = null; } play.Content = "Play";
|
|
224
|
+
}
|
|
225
|
+
private void Seek(double seconds)
|
|
226
|
+
{
|
|
227
|
+
if (Selected == null) return; bool playing = output.Playing;
|
|
228
|
+
Stop(); stoppedAt = Math.Max(0, Math.Min(Selected.Duration, seconds)); UpdatePosition(); if (playing) PlayPause();
|
|
229
|
+
}
|
|
230
|
+
private void Next(int direction)
|
|
231
|
+
{
|
|
232
|
+
int next = list.SelectedIndex + direction; if (next < 0 || next >= playlist.Count) return;
|
|
233
|
+
Stop(); list.SelectedIndex = next; stoppedAt = 0; PlayPause();
|
|
234
|
+
}
|
|
235
|
+
private void ShowClip()
|
|
236
|
+
{
|
|
237
|
+
var clip = Selected; title.Text = clip == null ? "Open a recording or a song" : clip.Name;
|
|
238
|
+
info.Text = clip == null ? "PCM and float WAV, FLAC, MP3, AIFF, and audio from videos." : clip.SourceFormat + " | " + WorkspaceUi.Clock(clip.Duration) + " | " + (clip.Channels == 1 ? "mono" : "stereo playback");
|
|
239
|
+
seek.IsEnabled = play.IsEnabled = clip != null && !IsBusy;
|
|
240
|
+
gain.IsEnabled = protectGain.IsEnabled = saveCopy.IsEnabled = saveFormat.IsEnabled = clip != null && !IsBusy;
|
|
241
|
+
updating = true; gain.Value = clip == null ? 0 : clip.Gain; updating = false; UpdateGain();
|
|
242
|
+
updating = true; seek.Maximum = clip == null ? 1 : Math.Max(.001, clip.Duration); updating = false;
|
|
243
|
+
DrawWave(); UpdatePosition();
|
|
244
|
+
}
|
|
245
|
+
private void UpdatePosition()
|
|
246
|
+
{
|
|
247
|
+
if (Selected == null) { clock.Text = "00:00.00 / 00:00.00"; return; }
|
|
248
|
+
double seconds = Math.Max(0, Math.Min(Selected.Duration, output.Playing || output.Paused ? output.Position : stoppedAt));
|
|
249
|
+
if (!dragging) { updating = true; seek.Value = seconds; updating = false; }
|
|
250
|
+
clock.Text = WorkspaceUi.Clock(seconds) + " / " + WorkspaceUi.Clock(Selected.Duration);
|
|
251
|
+
playLine.X1 = playLine.X2 = seconds / Math.Max(.001, Selected.Duration) * waveform.ActualWidth;
|
|
252
|
+
}
|
|
253
|
+
private void DrawWave()
|
|
254
|
+
{
|
|
255
|
+
waveform.Children.Clear(); double w = waveform.ActualWidth, h = waveform.Height;
|
|
256
|
+
if (Selected != null && Selected.Waveform != null && w > 0) {
|
|
257
|
+
var shape = new Polygon { Fill = WorkspaceUi.Color("#16887D"), Opacity = .85 }; var data = Selected.Waveform;
|
|
258
|
+
double scale = Math.Pow(10, Selected.AppliedGain(protectGain.IsChecked == true) / 20);
|
|
259
|
+
for (int i = 0; i < data.Length; i++) shape.Points.Add(new Point(i * w / (data.Length - 1), h / 2 - Math.Min(1, data[i] * scale) * h * .45));
|
|
260
|
+
for (int i = data.Length - 1; i >= 0; i--) shape.Points.Add(new Point(i * w / (data.Length - 1), h / 2 + Math.Min(1, data[i] * scale) * h * .45));
|
|
261
|
+
waveform.Children.Add(shape);
|
|
262
|
+
}
|
|
263
|
+
playLine.Y1 = 0; playLine.Y2 = h; waveform.Children.Add(playLine);
|
|
264
|
+
}
|
|
265
|
+
public void Dispose()
|
|
266
|
+
{ if (disposed) return; disposed = true; if (loading != null) loading.Cancel(); timer.Stop(); Stop(); output.Dispose(); }
|
|
267
|
+
public void CancelWork() { if (loading != null) loading.Cancel(); }
|
|
268
|
+
internal void SetForTest(Clip clip) { playlist.Add(clip); list.SelectedItem = clip; ShowClip(); }
|
|
269
|
+
internal void GainForTest(double value, bool protect) { protectGain.IsChecked = protect; gain.Value = value; }
|
|
270
|
+
internal float PlaybackGainForTest { get { return PlaybackGain(); } }
|
|
271
|
+
internal string SaveCopyForTest(string folder, string name) { return ConversionFormats.Export(ExportSnapshot(), folder, ExportOptions(name), CancellationToken.None, null); }
|
|
272
|
+
internal DockablePane GainPaneForTest { get { return gainPane; } }
|
|
273
|
+
}
|
|
274
|
+
}
|