mirror of https://github.com/naudio/NAudio.git
Browse Source
Added the AudioFileInspector project and updated NoteEvent to cope with MIDI files with invalid Note Off velocity (apparently Cubase can export these)
pull/1/head
Added the AudioFileInspector project and updated NoteEvent to cope with MIDI files with invalid Note Off velocity (apparently Cubase can export these)
pull/1/head

29 changed files with 2136 additions and 12 deletions
-
126AudioFileInspector/AudioFileInspector.csproj
-
203AudioFileInspector/AudioFileInspectorForm.Designer.cs
-
226AudioFileInspector/AudioFileInspectorForm.cs
-
123AudioFileInspector/AudioFileInspectorForm.resx
-
30AudioFileInspector/CakewalkMapInspector.cs
-
88AudioFileInspector/FindForm.Designer.cs
-
29AudioFileInspector/FindForm.cs
-
120AudioFileInspector/FindForm.resx
-
13AudioFileInspector/IAudioFileInspector.cs
-
154AudioFileInspector/InstallScript.nsi
-
147AudioFileInspector/MidiFileInspector.cs
-
94AudioFileInspector/OptionsForm.Designer.cs
-
87AudioFileInspector/OptionsForm.cs
-
120AudioFileInspector/OptionsForm.resx
-
62AudioFileInspector/Program.cs
-
71AudioFileInspector/Properties/AssemblyInfo.cs
-
63AudioFileInspector/Properties/Resources.Designer.cs
-
117AudioFileInspector/Properties/Resources.resx
-
26AudioFileInspector/Properties/Settings.Designer.cs
-
7AudioFileInspector/Properties/Settings.settings
-
67AudioFileInspector/SoundFontInspector.cs
-
49AudioFileInspector/WaveFileInspector.cs
-
84AudioFileInspector/audio_file_inspector.html
-
2MidiFileSplitter/InstallScript.nsi
-
1MidiFileSplitter/midi_file_splitter.html
-
6NAudio.sln
-
5NAudio/Changes.xml
-
24NAudio/Midi/NoteEvent.cs
-
4NAudio/Properties/AssemblyInfo.cs
@ -0,0 +1,126 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<PropertyGroup> |
|||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
|||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
|||
<ProductVersion>8.0.50727</ProductVersion> |
|||
<SchemaVersion>2.0</SchemaVersion> |
|||
<ProjectGuid>{D29C1659-635C-497B-847E-FE9A5A69ED03}</ProjectGuid> |
|||
<OutputType>WinExe</OutputType> |
|||
<AppDesignerFolder>Properties</AppDesignerFolder> |
|||
<RootNamespace>AudioFileInspector</RootNamespace> |
|||
<AssemblyName>AudioFileInspector</AssemblyName> |
|||
<SccProjectName> |
|||
</SccProjectName> |
|||
<SccLocalPath> |
|||
</SccLocalPath> |
|||
<SccAuxPath> |
|||
</SccAuxPath> |
|||
<SccProvider> |
|||
</SccProvider> |
|||
</PropertyGroup> |
|||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
|||
<DebugSymbols>true</DebugSymbols> |
|||
<DebugType>full</DebugType> |
|||
<Optimize>false</Optimize> |
|||
<OutputPath>bin\Debug\</OutputPath> |
|||
<DefineConstants>DEBUG;TRACE</DefineConstants> |
|||
<ErrorReport>prompt</ErrorReport> |
|||
<WarningLevel>4</WarningLevel> |
|||
</PropertyGroup> |
|||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
|||
<DebugType>pdbonly</DebugType> |
|||
<Optimize>true</Optimize> |
|||
<OutputPath>bin\Release\</OutputPath> |
|||
<DefineConstants>TRACE</DefineConstants> |
|||
<ErrorReport>prompt</ErrorReport> |
|||
<WarningLevel>4</WarningLevel> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<Reference Include="System" /> |
|||
<Reference Include="System.Data" /> |
|||
<Reference Include="System.Deployment" /> |
|||
<Reference Include="System.Drawing" /> |
|||
<Reference Include="System.Windows.Forms" /> |
|||
<Reference Include="System.Xml" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<Compile Include="AudioFileInspectorForm.cs"> |
|||
<SubType>Form</SubType> |
|||
</Compile> |
|||
<Compile Include="AudioFileInspectorForm.Designer.cs"> |
|||
<DependentUpon>AudioFileInspectorForm.cs</DependentUpon> |
|||
</Compile> |
|||
<Compile Include="CakewalkMapInspector.cs" /> |
|||
<Compile Include="FindForm.cs"> |
|||
<SubType>Form</SubType> |
|||
</Compile> |
|||
<Compile Include="FindForm.Designer.cs"> |
|||
<DependentUpon>FindForm.cs</DependentUpon> |
|||
</Compile> |
|||
<Compile Include="IAudioFileInspector.cs" /> |
|||
<Compile Include="MidiFileInspector.cs" /> |
|||
<Compile Include="OptionsForm.cs"> |
|||
<SubType>Form</SubType> |
|||
</Compile> |
|||
<Compile Include="OptionsForm.Designer.cs"> |
|||
<DependentUpon>OptionsForm.cs</DependentUpon> |
|||
</Compile> |
|||
<Compile Include="Program.cs" /> |
|||
<Compile Include="Properties\AssemblyInfo.cs" /> |
|||
<EmbeddedResource Include="AudioFileInspectorForm.resx"> |
|||
<SubType>Designer</SubType> |
|||
<DependentUpon>AudioFileInspectorForm.cs</DependentUpon> |
|||
</EmbeddedResource> |
|||
<EmbeddedResource Include="FindForm.resx"> |
|||
<SubType>Designer</SubType> |
|||
<DependentUpon>FindForm.cs</DependentUpon> |
|||
</EmbeddedResource> |
|||
<EmbeddedResource Include="OptionsForm.resx"> |
|||
<SubType>Designer</SubType> |
|||
<DependentUpon>OptionsForm.cs</DependentUpon> |
|||
</EmbeddedResource> |
|||
<EmbeddedResource Include="Properties\Resources.resx"> |
|||
<Generator>ResXFileCodeGenerator</Generator> |
|||
<LastGenOutput>Resources.Designer.cs</LastGenOutput> |
|||
<SubType>Designer</SubType> |
|||
</EmbeddedResource> |
|||
<Compile Include="Properties\Resources.Designer.cs"> |
|||
<AutoGen>True</AutoGen> |
|||
<DependentUpon>Resources.resx</DependentUpon> |
|||
<DesignTime>True</DesignTime> |
|||
</Compile> |
|||
<None Include="InstallScript.nsi"> |
|||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> |
|||
</None> |
|||
<None Include="Properties\Settings.settings"> |
|||
<Generator>SettingsSingleFileGenerator</Generator> |
|||
<LastGenOutput>Settings.Designer.cs</LastGenOutput> |
|||
</None> |
|||
<Compile Include="Properties\Settings.Designer.cs"> |
|||
<AutoGen>True</AutoGen> |
|||
<DependentUpon>Settings.settings</DependentUpon> |
|||
<DesignTimeSharedInput>True</DesignTimeSharedInput> |
|||
</Compile> |
|||
<Compile Include="SoundFontInspector.cs" /> |
|||
<Compile Include="WaveFileInspector.cs" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\nAudio\nAudio.csproj"> |
|||
<Project>{9CABA8DA-CCF0-4808-9AA7-75F269DB2D7C}</Project> |
|||
<Name>nAudio</Name> |
|||
</ProjectReference> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<Content Include="audio_file_inspector.html"> |
|||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> |
|||
</Content> |
|||
</ItemGroup> |
|||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> |
|||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. |
|||
Other similar extension points exist, see Microsoft.Common.targets. |
|||
<Target Name="BeforeBuild"> |
|||
</Target> |
|||
<Target Name="AfterBuild"> |
|||
</Target> |
|||
--> |
|||
</Project> |
@ -0,0 +1,203 @@ |
|||
namespace AudioFileInspector |
|||
{ |
|||
partial class AudioFileInspectorForm |
|||
{ |
|||
/// <summary>
|
|||
/// Required designer variable.
|
|||
/// </summary>
|
|||
private System.ComponentModel.IContainer components = null; |
|||
|
|||
/// <summary>
|
|||
/// Clean up any resources being used.
|
|||
/// </summary>
|
|||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
if (disposing && (components != null)) |
|||
{ |
|||
components.Dispose(); |
|||
} |
|||
base.Dispose(disposing); |
|||
} |
|||
|
|||
#region Windows Form Designer generated code
|
|||
|
|||
/// <summary>
|
|||
/// Required method for Designer support - do not modify
|
|||
/// the contents of this method with the code editor.
|
|||
/// </summary>
|
|||
private void InitializeComponent() |
|||
{ |
|||
this.menuStrip1 = new System.Windows.Forms.MenuStrip(); |
|||
this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); |
|||
this.openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); |
|||
this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); |
|||
this.editToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); |
|||
this.findToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); |
|||
this.toolsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); |
|||
this.optionsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); |
|||
this.saveLogToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); |
|||
this.clearLogToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); |
|||
this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); |
|||
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); |
|||
this.contentsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); |
|||
this.textLog = new System.Windows.Forms.RichTextBox(); |
|||
this.menuStrip1.SuspendLayout(); |
|||
this.SuspendLayout(); |
|||
//
|
|||
// menuStrip1
|
|||
//
|
|||
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { |
|||
this.fileToolStripMenuItem, |
|||
this.editToolStripMenuItem, |
|||
this.toolsToolStripMenuItem, |
|||
this.helpToolStripMenuItem}); |
|||
this.menuStrip1.Location = new System.Drawing.Point(0, 0); |
|||
this.menuStrip1.Name = "menuStrip1"; |
|||
this.menuStrip1.Size = new System.Drawing.Size(513, 24); |
|||
this.menuStrip1.TabIndex = 0; |
|||
this.menuStrip1.Text = "menuStrip1"; |
|||
//
|
|||
// fileToolStripMenuItem
|
|||
//
|
|||
this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { |
|||
this.openToolStripMenuItem, |
|||
this.exitToolStripMenuItem}); |
|||
this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; |
|||
this.fileToolStripMenuItem.Size = new System.Drawing.Size(35, 20); |
|||
this.fileToolStripMenuItem.Text = "&File"; |
|||
//
|
|||
// openToolStripMenuItem
|
|||
//
|
|||
this.openToolStripMenuItem.Name = "openToolStripMenuItem"; |
|||
this.openToolStripMenuItem.Size = new System.Drawing.Size(152, 22); |
|||
this.openToolStripMenuItem.Text = "&Open..."; |
|||
this.openToolStripMenuItem.Click += new System.EventHandler(this.openToolStripMenuItem_Click); |
|||
//
|
|||
// exitToolStripMenuItem
|
|||
//
|
|||
this.exitToolStripMenuItem.Name = "exitToolStripMenuItem"; |
|||
this.exitToolStripMenuItem.Size = new System.Drawing.Size(152, 22); |
|||
this.exitToolStripMenuItem.Text = "E&xit"; |
|||
this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click); |
|||
//
|
|||
// editToolStripMenuItem
|
|||
//
|
|||
this.editToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { |
|||
this.findToolStripMenuItem}); |
|||
this.editToolStripMenuItem.Name = "editToolStripMenuItem"; |
|||
this.editToolStripMenuItem.Size = new System.Drawing.Size(37, 20); |
|||
this.editToolStripMenuItem.Text = "&Edit"; |
|||
//
|
|||
// findToolStripMenuItem
|
|||
//
|
|||
this.findToolStripMenuItem.Name = "findToolStripMenuItem"; |
|||
this.findToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.F))); |
|||
this.findToolStripMenuItem.Size = new System.Drawing.Size(155, 22); |
|||
this.findToolStripMenuItem.Text = "&Find..."; |
|||
this.findToolStripMenuItem.Click += new System.EventHandler(this.findToolStripMenuItem_Click); |
|||
//
|
|||
// toolsToolStripMenuItem
|
|||
//
|
|||
this.toolsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { |
|||
this.optionsToolStripMenuItem, |
|||
this.saveLogToolStripMenuItem, |
|||
this.clearLogToolStripMenuItem}); |
|||
this.toolsToolStripMenuItem.Name = "toolsToolStripMenuItem"; |
|||
this.toolsToolStripMenuItem.Size = new System.Drawing.Size(44, 20); |
|||
this.toolsToolStripMenuItem.Text = "&Tools"; |
|||
//
|
|||
// optionsToolStripMenuItem
|
|||
//
|
|||
this.optionsToolStripMenuItem.Name = "optionsToolStripMenuItem"; |
|||
this.optionsToolStripMenuItem.Size = new System.Drawing.Size(141, 22); |
|||
this.optionsToolStripMenuItem.Text = "&Options..."; |
|||
this.optionsToolStripMenuItem.Click += new System.EventHandler(this.optionsToolStripMenuItem_Click); |
|||
//
|
|||
// saveLogToolStripMenuItem
|
|||
//
|
|||
this.saveLogToolStripMenuItem.Name = "saveLogToolStripMenuItem"; |
|||
this.saveLogToolStripMenuItem.Size = new System.Drawing.Size(141, 22); |
|||
this.saveLogToolStripMenuItem.Text = "&Save Log..."; |
|||
this.saveLogToolStripMenuItem.Click += new System.EventHandler(this.saveLogToolStripMenuItem_Click); |
|||
//
|
|||
// clearLogToolStripMenuItem
|
|||
//
|
|||
this.clearLogToolStripMenuItem.Name = "clearLogToolStripMenuItem"; |
|||
this.clearLogToolStripMenuItem.Size = new System.Drawing.Size(141, 22); |
|||
this.clearLogToolStripMenuItem.Text = "&Clear Log"; |
|||
this.clearLogToolStripMenuItem.Click += new System.EventHandler(this.clearLogToolStripMenuItem_Click); |
|||
//
|
|||
// helpToolStripMenuItem
|
|||
//
|
|||
this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { |
|||
this.aboutToolStripMenuItem, |
|||
this.contentsToolStripMenuItem}); |
|||
this.helpToolStripMenuItem.Name = "helpToolStripMenuItem"; |
|||
this.helpToolStripMenuItem.Size = new System.Drawing.Size(40, 20); |
|||
this.helpToolStripMenuItem.Text = "&Help"; |
|||
//
|
|||
// aboutToolStripMenuItem
|
|||
//
|
|||
this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; |
|||
this.aboutToolStripMenuItem.Size = new System.Drawing.Size(129, 22); |
|||
this.aboutToolStripMenuItem.Text = "&About"; |
|||
this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click); |
|||
//
|
|||
// contentsToolStripMenuItem
|
|||
//
|
|||
this.contentsToolStripMenuItem.Name = "contentsToolStripMenuItem"; |
|||
this.contentsToolStripMenuItem.Size = new System.Drawing.Size(129, 22); |
|||
this.contentsToolStripMenuItem.Text = "&Contents"; |
|||
this.contentsToolStripMenuItem.Click += new System.EventHandler(this.contentsToolStripMenuItem_Click); |
|||
//
|
|||
// textLog
|
|||
//
|
|||
this.textLog.Dock = System.Windows.Forms.DockStyle.Fill; |
|||
this.textLog.HideSelection = false; |
|||
this.textLog.Location = new System.Drawing.Point(0, 24); |
|||
this.textLog.Name = "textLog"; |
|||
this.textLog.Size = new System.Drawing.Size(513, 409); |
|||
this.textLog.TabIndex = 1; |
|||
this.textLog.Text = ""; |
|||
//
|
|||
// AudioFileInspectorForm
|
|||
//
|
|||
this.AllowDrop = true; |
|||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); |
|||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; |
|||
this.ClientSize = new System.Drawing.Size(513, 433); |
|||
this.Controls.Add(this.textLog); |
|||
this.Controls.Add(this.menuStrip1); |
|||
this.MainMenuStrip = this.menuStrip1; |
|||
this.Name = "AudioFileInspectorForm"; |
|||
this.Text = "Audio File Inspector"; |
|||
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.AudioFileInspectorForm_FormClosed); |
|||
this.DragDrop += new System.Windows.Forms.DragEventHandler(this.AudioFileInspectorForm_DragDrop); |
|||
this.DragOver += new System.Windows.Forms.DragEventHandler(this.AudioFileInspectorForm_DragOver); |
|||
this.Load += new System.EventHandler(this.AudioFileInspectorForm_Load); |
|||
this.menuStrip1.ResumeLayout(false); |
|||
this.menuStrip1.PerformLayout(); |
|||
this.ResumeLayout(false); |
|||
this.PerformLayout(); |
|||
|
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
private System.Windows.Forms.MenuStrip menuStrip1; |
|||
private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; |
|||
private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem; |
|||
private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; |
|||
private System.Windows.Forms.ToolStripMenuItem toolsToolStripMenuItem; |
|||
private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem; |
|||
private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem; |
|||
private System.Windows.Forms.ToolStripMenuItem contentsToolStripMenuItem; |
|||
private System.Windows.Forms.ToolStripMenuItem optionsToolStripMenuItem; |
|||
private System.Windows.Forms.ToolStripMenuItem saveLogToolStripMenuItem; |
|||
private System.Windows.Forms.RichTextBox textLog; |
|||
private System.Windows.Forms.ToolStripMenuItem clearLogToolStripMenuItem; |
|||
private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem; |
|||
private System.Windows.Forms.ToolStripMenuItem findToolStripMenuItem; |
|||
} |
|||
} |
@ -0,0 +1,226 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Data; |
|||
using System.Drawing; |
|||
using System.Text; |
|||
using System.Windows.Forms; |
|||
using System.IO; |
|||
|
|||
namespace AudioFileInspector |
|||
{ |
|||
partial class AudioFileInspectorForm : Form |
|||
{ |
|||
List<IAudioFileInspector> inspectors; |
|||
string filterString; |
|||
int filterIndex; |
|||
string[] args; |
|||
string currentFile; |
|||
FindForm findForm; |
|||
|
|||
public AudioFileInspectorForm(List<IAudioFileInspector> inspectors, string[] args) |
|||
{ |
|||
InitializeComponent(); |
|||
this.inspectors = inspectors; |
|||
CreateFilterString(); |
|||
this.args = args; |
|||
|
|||
} |
|||
|
|||
private void DescribeFile(string fileName) |
|||
{ |
|||
currentFile = fileName; |
|||
textLog.Text = String.Format("Opening {0}\r\n", fileName); |
|||
|
|||
try |
|||
{ |
|||
string extension = System.IO.Path.GetExtension(fileName).ToLower(); |
|||
bool described = false; |
|||
foreach (IAudioFileInspector inspector in inspectors) |
|||
{ |
|||
if (extension == inspector.FileExtension) |
|||
{ |
|||
textLog.AppendText(inspector.Describe(fileName)); |
|||
described = true; |
|||
break; |
|||
} |
|||
} |
|||
if (!described) |
|||
{ |
|||
textLog.AppendText("Unrecognised file type"); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
textLog.AppendText(ex.ToString()); |
|||
} |
|||
} |
|||
|
|||
private void CreateFilterString() |
|||
{ |
|||
StringBuilder stringBuilder = new StringBuilder(); |
|||
if (inspectors.Count > 0) |
|||
{ |
|||
stringBuilder.Append("All Supported Files|"); |
|||
foreach (IAudioFileInspector inspector in inspectors) |
|||
{ |
|||
stringBuilder.AppendFormat("*{0};", inspector.FileExtension); |
|||
} |
|||
stringBuilder.Length--; |
|||
stringBuilder.Append("|"); |
|||
foreach (IAudioFileInspector inspector in inspectors) |
|||
{ |
|||
stringBuilder.AppendFormat("{0}|*{1}|", inspector.FileTypeDescription, inspector.FileExtension); |
|||
} |
|||
} |
|||
stringBuilder.Append("All files (*.*)|*.*"); |
|||
filterString = stringBuilder.ToString(); |
|||
filterIndex = 1; |
|||
} |
|||
|
|||
private void openToolStripMenuItem_Click(object sender, EventArgs e) |
|||
{ |
|||
OpenFileDialog ofd = new OpenFileDialog(); |
|||
|
|||
ofd.Filter = filterString; |
|||
ofd.FilterIndex = filterIndex; |
|||
|
|||
if (ofd.ShowDialog() == DialogResult.OK) |
|||
{ |
|||
filterIndex = ofd.FilterIndex; |
|||
DescribeFile(ofd.FileName); |
|||
textLog.SelectionStart = 0; |
|||
textLog.SelectionLength = 0; |
|||
} |
|||
} |
|||
|
|||
private void exitToolStripMenuItem_Click(object sender, EventArgs e) |
|||
{ |
|||
this.Close(); |
|||
} |
|||
|
|||
private void aboutToolStripMenuItem_Click(object sender, EventArgs e) |
|||
{ |
|||
NAudio.Utils.AboutForm aboutForm = new NAudio.Utils.AboutForm(); |
|||
aboutForm.ShowDialog(); |
|||
} |
|||
|
|||
private void AudioFileInspectorForm_Load(object sender, EventArgs e) |
|||
{ |
|||
if (args.Length > 0) |
|||
{ |
|||
DescribeFile(args[0]); |
|||
} |
|||
} |
|||
|
|||
private void optionsToolStripMenuItem_Click(object sender, EventArgs e) |
|||
{ |
|||
OptionsForm optionsForm = new OptionsForm(inspectors); |
|||
optionsForm.ShowDialog(); |
|||
|
|||
} |
|||
|
|||
private void AudioFileInspectorForm_DragOver(object sender, DragEventArgs e) |
|||
{ |
|||
if (e.Data.GetDataPresent(DataFormats.FileDrop)) |
|||
{ |
|||
e.Effect = DragDropEffects.Copy; |
|||
} |
|||
} |
|||
|
|||
private void AudioFileInspectorForm_DragDrop(object sender, DragEventArgs e) |
|||
{ |
|||
if (!e.Data.GetDataPresent(DataFormats.FileDrop)) |
|||
{ |
|||
return; |
|||
} |
|||
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); |
|||
|
|||
if (files.Length > 0) |
|||
{ |
|||
DescribeFile(files[0]); |
|||
} |
|||
} |
|||
|
|||
private void saveLogToolStripMenuItem_Click(object sender, EventArgs args) |
|||
{ |
|||
SaveFileDialog saveFileDialog = new SaveFileDialog(); |
|||
saveFileDialog.DefaultExt = ".txt"; |
|||
if (currentFile != null) |
|||
{ |
|||
saveFileDialog.InitialDirectory = Path.GetDirectoryName(currentFile); |
|||
saveFileDialog.FileName = Path.GetFileNameWithoutExtension(currentFile) + ".txt"; |
|||
} |
|||
saveFileDialog.Filter = "Text Files (*.txt)|*.txt"; |
|||
saveFileDialog.FilterIndex = 1; |
|||
if (saveFileDialog.ShowDialog() == DialogResult.OK) |
|||
{ |
|||
try |
|||
{ |
|||
|
|||
using (StreamWriter writer = new StreamWriter(saveFileDialog.FileName)) |
|||
{ |
|||
string text = textLog.Text; |
|||
if (!text.Contains("\r")) |
|||
{ |
|||
text = text.Replace("\n", "\r\n"); |
|||
} |
|||
writer.Write(text); |
|||
} |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
MessageBox.Show( |
|||
String.Format("Error saving conversion log\r\n{0}", e.Message), |
|||
Application.ProductName, |
|||
MessageBoxButtons.OK, |
|||
MessageBoxIcon.Error); |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
private void contentsToolStripMenuItem_Click(object sender, EventArgs e) |
|||
{ |
|||
string helpFilePath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "audio_file_inspector.html"); |
|||
try |
|||
{ |
|||
System.Diagnostics.Process.Start(helpFilePath); |
|||
} |
|||
catch (Win32Exception) |
|||
{ |
|||
MessageBox.Show("Could not display the help file", Application.ProductName); |
|||
} |
|||
} |
|||
|
|||
private void clearLogToolStripMenuItem_Click(object sender, EventArgs e) |
|||
{ |
|||
textLog.Clear(); |
|||
} |
|||
|
|||
private void findToolStripMenuItem_Click(object sender, EventArgs e) |
|||
{ |
|||
if (findForm == null) |
|||
{ |
|||
findForm = new FindForm(textLog); |
|||
findForm.Disposed += new EventHandler(findForm_Disposed); |
|||
} |
|||
findForm.Show(this); |
|||
} |
|||
|
|||
void findForm_Disposed(object sender, EventArgs e) |
|||
{ |
|||
findForm = null; |
|||
} |
|||
|
|||
private void AudioFileInspectorForm_FormClosed(object sender, FormClosedEventArgs e) |
|||
{ |
|||
if (findForm != null) |
|||
{ |
|||
findForm.Close(); |
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,123 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<root> |
|||
<!-- |
|||
Microsoft ResX Schema |
|||
|
|||
Version 2.0 |
|||
|
|||
The primary goals of this format is to allow a simple XML format |
|||
that is mostly human readable. The generation and parsing of the |
|||
various data types are done through the TypeConverter classes |
|||
associated with the data types. |
|||
|
|||
Example: |
|||
|
|||
... ado.net/XML headers & schema ... |
|||
<resheader name="resmimetype">text/microsoft-resx</resheader> |
|||
<resheader name="version">2.0</resheader> |
|||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> |
|||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> |
|||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> |
|||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> |
|||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> |
|||
<value>[base64 mime encoded serialized .NET Framework object]</value> |
|||
</data> |
|||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> |
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> |
|||
<comment>This is a comment</comment> |
|||
</data> |
|||
|
|||
There are any number of "resheader" rows that contain simple |
|||
name/value pairs. |
|||
|
|||
Each data row contains a name, and value. The row also contains a |
|||
type or mimetype. Type corresponds to a .NET class that support |
|||
text/value conversion through the TypeConverter architecture. |
|||
Classes that don't support this are serialized and stored with the |
|||
mimetype set. |
|||
|
|||
The mimetype is used for serialized objects, and tells the |
|||
ResXResourceReader how to depersist the object. This is currently not |
|||
extensible. For a given mimetype the value must be set accordingly: |
|||
|
|||
Note - application/x-microsoft.net.object.binary.base64 is the format |
|||
that the ResXResourceWriter will generate, however the reader can |
|||
read any of the formats listed below. |
|||
|
|||
mimetype: application/x-microsoft.net.object.binary.base64 |
|||
value : The object must be serialized with |
|||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter |
|||
: and then encoded with base64 encoding. |
|||
|
|||
mimetype: application/x-microsoft.net.object.soap.base64 |
|||
value : The object must be serialized with |
|||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter |
|||
: and then encoded with base64 encoding. |
|||
|
|||
mimetype: application/x-microsoft.net.object.bytearray.base64 |
|||
value : The object must be serialized into a byte array |
|||
: using a System.ComponentModel.TypeConverter |
|||
: and then encoded with base64 encoding. |
|||
--> |
|||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> |
|||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> |
|||
<xsd:element name="root" msdata:IsDataSet="true"> |
|||
<xsd:complexType> |
|||
<xsd:choice maxOccurs="unbounded"> |
|||
<xsd:element name="metadata"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="value" type="xsd:string" minOccurs="0" /> |
|||
</xsd:sequence> |
|||
<xsd:attribute name="name" use="required" type="xsd:string" /> |
|||
<xsd:attribute name="type" type="xsd:string" /> |
|||
<xsd:attribute name="mimetype" type="xsd:string" /> |
|||
<xsd:attribute ref="xml:space" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
<xsd:element name="assembly"> |
|||
<xsd:complexType> |
|||
<xsd:attribute name="alias" type="xsd:string" /> |
|||
<xsd:attribute name="name" type="xsd:string" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
<xsd:element name="data"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
|||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> |
|||
</xsd:sequence> |
|||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> |
|||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> |
|||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> |
|||
<xsd:attribute ref="xml:space" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
<xsd:element name="resheader"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
|||
</xsd:sequence> |
|||
<xsd:attribute name="name" type="xsd:string" use="required" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
</xsd:choice> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
</xsd:schema> |
|||
<resheader name="resmimetype"> |
|||
<value>text/microsoft-resx</value> |
|||
</resheader> |
|||
<resheader name="version"> |
|||
<value>2.0</value> |
|||
</resheader> |
|||
<resheader name="reader"> |
|||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
|||
</resheader> |
|||
<resheader name="writer"> |
|||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
|||
</resheader> |
|||
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> |
|||
<value>17, 17</value> |
|||
</metadata> |
|||
</root> |
@ -0,0 +1,30 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using NAudio.FileFormats.Map; |
|||
|
|||
namespace AudioFileInspector |
|||
{ |
|||
class CakewalkMapInspector : IAudioFileInspector |
|||
{ |
|||
#region IAudioFileInspector Members
|
|||
|
|||
public string FileExtension |
|||
{ |
|||
get { return ".map"; } |
|||
} |
|||
|
|||
public string FileTypeDescription |
|||
{ |
|||
get { return "Cakewalk Drum Map"; } |
|||
} |
|||
|
|||
public string Describe(string fileName) |
|||
{ |
|||
CakewalkMapFile mapFile = new CakewalkMapFile(fileName); |
|||
return mapFile.ToString(); |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
@ -0,0 +1,88 @@ |
|||
namespace AudioFileInspector |
|||
{ |
|||
partial class FindForm |
|||
{ |
|||
/// <summary>
|
|||
/// Required designer variable.
|
|||
/// </summary>
|
|||
private System.ComponentModel.IContainer components = null; |
|||
|
|||
/// <summary>
|
|||
/// Clean up any resources being used.
|
|||
/// </summary>
|
|||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
if (disposing && (components != null)) |
|||
{ |
|||
components.Dispose(); |
|||
} |
|||
base.Dispose(disposing); |
|||
} |
|||
|
|||
#region Windows Form Designer generated code
|
|||
|
|||
/// <summary>
|
|||
/// Required method for Designer support - do not modify
|
|||
/// the contents of this method with the code editor.
|
|||
/// </summary>
|
|||
private void InitializeComponent() |
|||
{ |
|||
this.label1 = new System.Windows.Forms.Label(); |
|||
this.textBoxFind = new System.Windows.Forms.TextBox(); |
|||
this.buttonFind = new System.Windows.Forms.Button(); |
|||
this.SuspendLayout(); |
|||
//
|
|||
// label1
|
|||
//
|
|||
this.label1.AutoSize = true; |
|||
this.label1.Location = new System.Drawing.Point(12, 9); |
|||
this.label1.Name = "label1"; |
|||
this.label1.Size = new System.Drawing.Size(56, 13); |
|||
this.label1.TabIndex = 0; |
|||
this.label1.Text = "Find what:"; |
|||
//
|
|||
// textBoxFind
|
|||
//
|
|||
this.textBoxFind.Location = new System.Drawing.Point(74, 6); |
|||
this.textBoxFind.Name = "textBoxFind"; |
|||
this.textBoxFind.Size = new System.Drawing.Size(206, 20); |
|||
this.textBoxFind.TabIndex = 1; |
|||
//
|
|||
// buttonFind
|
|||
//
|
|||
this.buttonFind.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); |
|||
this.buttonFind.Location = new System.Drawing.Point(205, 32); |
|||
this.buttonFind.Name = "buttonFind"; |
|||
this.buttonFind.Size = new System.Drawing.Size(75, 23); |
|||
this.buttonFind.TabIndex = 2; |
|||
this.buttonFind.Text = "Find Next"; |
|||
this.buttonFind.UseVisualStyleBackColor = true; |
|||
this.buttonFind.Click += new System.EventHandler(this.buttonFind_Click); |
|||
//
|
|||
// FindForm
|
|||
//
|
|||
this.AcceptButton = this.buttonFind; |
|||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); |
|||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; |
|||
this.ClientSize = new System.Drawing.Size(292, 64); |
|||
this.Controls.Add(this.buttonFind); |
|||
this.Controls.Add(this.textBoxFind); |
|||
this.Controls.Add(this.label1); |
|||
this.MaximizeBox = false; |
|||
this.MinimizeBox = false; |
|||
this.Name = "FindForm"; |
|||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; |
|||
this.Text = "Find"; |
|||
this.ResumeLayout(false); |
|||
this.PerformLayout(); |
|||
|
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
private System.Windows.Forms.Label label1; |
|||
private System.Windows.Forms.TextBox textBoxFind; |
|||
private System.Windows.Forms.Button buttonFind; |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Data; |
|||
using System.Drawing; |
|||
using System.Text; |
|||
using System.Windows.Forms; |
|||
|
|||
namespace AudioFileInspector |
|||
{ |
|||
public partial class FindForm : Form |
|||
{ |
|||
RichTextBox richTextBox; |
|||
public FindForm(RichTextBox richTextBox) |
|||
{ |
|||
InitializeComponent(); |
|||
this.richTextBox = richTextBox; |
|||
} |
|||
|
|||
private void buttonFind_Click(object sender, EventArgs e) |
|||
{ |
|||
richTextBox.Find(textBoxFind.Text, |
|||
richTextBox.SelectionStart + |
|||
richTextBox.SelectionLength, |
|||
RichTextBoxFinds.None); |
|||
richTextBox.ScrollToCaret(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,120 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<root> |
|||
<!-- |
|||
Microsoft ResX Schema |
|||
|
|||
Version 2.0 |
|||
|
|||
The primary goals of this format is to allow a simple XML format |
|||
that is mostly human readable. The generation and parsing of the |
|||
various data types are done through the TypeConverter classes |
|||
associated with the data types. |
|||
|
|||
Example: |
|||
|
|||
... ado.net/XML headers & schema ... |
|||
<resheader name="resmimetype">text/microsoft-resx</resheader> |
|||
<resheader name="version">2.0</resheader> |
|||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> |
|||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> |
|||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> |
|||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> |
|||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> |
|||
<value>[base64 mime encoded serialized .NET Framework object]</value> |
|||
</data> |
|||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> |
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> |
|||
<comment>This is a comment</comment> |
|||
</data> |
|||
|
|||
There are any number of "resheader" rows that contain simple |
|||
name/value pairs. |
|||
|
|||
Each data row contains a name, and value. The row also contains a |
|||
type or mimetype. Type corresponds to a .NET class that support |
|||
text/value conversion through the TypeConverter architecture. |
|||
Classes that don't support this are serialized and stored with the |
|||
mimetype set. |
|||
|
|||
The mimetype is used for serialized objects, and tells the |
|||
ResXResourceReader how to depersist the object. This is currently not |
|||
extensible. For a given mimetype the value must be set accordingly: |
|||
|
|||
Note - application/x-microsoft.net.object.binary.base64 is the format |
|||
that the ResXResourceWriter will generate, however the reader can |
|||
read any of the formats listed below. |
|||
|
|||
mimetype: application/x-microsoft.net.object.binary.base64 |
|||
value : The object must be serialized with |
|||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter |
|||
: and then encoded with base64 encoding. |
|||
|
|||
mimetype: application/x-microsoft.net.object.soap.base64 |
|||
value : The object must be serialized with |
|||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter |
|||
: and then encoded with base64 encoding. |
|||
|
|||
mimetype: application/x-microsoft.net.object.bytearray.base64 |
|||
value : The object must be serialized into a byte array |
|||
: using a System.ComponentModel.TypeConverter |
|||
: and then encoded with base64 encoding. |
|||
--> |
|||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> |
|||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> |
|||
<xsd:element name="root" msdata:IsDataSet="true"> |
|||
<xsd:complexType> |
|||
<xsd:choice maxOccurs="unbounded"> |
|||
<xsd:element name="metadata"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="value" type="xsd:string" minOccurs="0" /> |
|||
</xsd:sequence> |
|||
<xsd:attribute name="name" use="required" type="xsd:string" /> |
|||
<xsd:attribute name="type" type="xsd:string" /> |
|||
<xsd:attribute name="mimetype" type="xsd:string" /> |
|||
<xsd:attribute ref="xml:space" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
<xsd:element name="assembly"> |
|||
<xsd:complexType> |
|||
<xsd:attribute name="alias" type="xsd:string" /> |
|||
<xsd:attribute name="name" type="xsd:string" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
<xsd:element name="data"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
|||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> |
|||
</xsd:sequence> |
|||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> |
|||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> |
|||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> |
|||
<xsd:attribute ref="xml:space" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
<xsd:element name="resheader"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
|||
</xsd:sequence> |
|||
<xsd:attribute name="name" type="xsd:string" use="required" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
</xsd:choice> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
</xsd:schema> |
|||
<resheader name="resmimetype"> |
|||
<value>text/microsoft-resx</value> |
|||
</resheader> |
|||
<resheader name="version"> |
|||
<value>2.0</value> |
|||
</resheader> |
|||
<resheader name="reader"> |
|||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
|||
</resheader> |
|||
<resheader name="writer"> |
|||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
|||
</resheader> |
|||
</root> |
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace AudioFileInspector |
|||
{ |
|||
interface IAudioFileInspector |
|||
{ |
|||
string FileExtension { get; } |
|||
string FileTypeDescription { get; } |
|||
string Describe(string fileName); |
|||
} |
|||
} |
@ -0,0 +1,154 @@ |
|||
; Audio File Inspector install script |
|||
; originally based on example2 that comes with nsis |
|||
; |
|||
; This script is based on example1.nsi, but it remember the directory, |
|||
; has uninstall support and (optionally) installs start menu shortcuts. |
|||
; |
|||
; It will install example2.nsi into a directory that the user selects, |
|||
|
|||
;-------------------------------- |
|||
!include WordFunc.nsh |
|||
!insertmacro VersionCompare |
|||
|
|||
!include LogicLib.nsh |
|||
|
|||
!define VERSION "0.1" |
|||
!define FULLVERSION "0.1.1.0" |
|||
!define PRODUCT_NAME "Audio File Inspector" |
|||
!define EXECUTABLE_NAME "AudioFileInspector.exe" |
|||
|
|||
; The name of the installer |
|||
Name "Audio File Inspector" |
|||
|
|||
; The file to write |
|||
;OutFile "${PRODUCT_NAME} v${VERSION} Install.exe" |
|||
OutFile "audio_file_inspector_0_1_install.exe" |
|||
|
|||
; The default installation directory |
|||
InstallDir "$PROGRAMFILES\Mark Heath\${PRODUCT_NAME}" |
|||
|
|||
; Registry key to check for directory (so if you install again, it will |
|||
; overwrite the old one automatically) |
|||
InstallDirRegKey HKLM "Software\${PRODUCT_NAME}" "Install_Dir" |
|||
|
|||
VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductName" "${PRODUCT_NAME}" |
|||
VIAddVersionKey /LANG=${LANG_ENGLISH} "Comments" "" |
|||
VIAddVersionKey /LANG=${LANG_ENGLISH} "CompanyName" "Mark Heath" |
|||
VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "© 2006 Mark Heath" |
|||
VIAddVersionKey /LANG=${LANG_ENGLISH} "FileDescription" "${PRODUCT_NAME} Installer" |
|||
VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "${VERSION}" |
|||
VIProductVersion "${FULLVERSION}" |
|||
|
|||
|
|||
|
|||
;-------------------------------- |
|||
|
|||
; Utils |
|||
|
|||
Function .onInit |
|||
Call GetDotNETVersion |
|||
Pop $0 |
|||
${If} $0 == "not found" |
|||
MessageBox MB_OK|MB_ICONSTOP ".NET runtime library v2.0 or newer is required." |
|||
ExecShell "open" "http://msdn.microsoft.com/netframework/downloads/updates/default.aspx" |
|||
Abort |
|||
${EndIf} |
|||
|
|||
StrCpy $0 $0 "" 1 # skip "v" |
|||
|
|||
${VersionCompare} $0 "2.0" $1 |
|||
${If} $1 == 2 |
|||
MessageBox MB_OK|MB_ICONSTOP ".NET runtime library v2.0 or newer is required. You have $0." |
|||
ExecShell "open" "http://msdn.microsoft.com/netframework/downloads/updates/default.aspx" |
|||
Abort |
|||
${EndIf} |
|||
FunctionEnd |
|||
|
|||
Function GetDotNETVersion |
|||
Push $0 |
|||
Push $1 |
|||
|
|||
System::Call "mscoree::GetCORVersion(w .r0, i ${NSIS_MAX_STRLEN}, *i) i .r1" |
|||
StrCmp $1 "error" 0 +2 |
|||
StrCpy $0 "not found" |
|||
|
|||
Pop $1 |
|||
Exch $0 |
|||
FunctionEnd |
|||
|
|||
;-------------------------------- |
|||
|
|||
; Pages |
|||
|
|||
Page components |
|||
Page directory |
|||
Page instfiles |
|||
|
|||
UninstPage uninstConfirm |
|||
UninstPage instfiles |
|||
|
|||
;-------------------------------- |
|||
|
|||
; The stuff to install |
|||
Section "Program Files (required)" |
|||
|
|||
SectionIn RO |
|||
|
|||
; Set output path to the installation directory. |
|||
SetOutPath $INSTDIR |
|||
|
|||
; Put file there |
|||
File "${EXECUTABLE_NAME}" |
|||
; File "${EXECUTABLE_NAME}.config" |
|||
File "NAudio.dll" |
|||
File "audio_file_inspector.html" |
|||
|
|||
; Write the installation path into the registry |
|||
WriteRegStr HKLM "SOFTWARE\${PRODUCT_NAME}" "Install_Dir" "$INSTDIR" |
|||
|
|||
; Write the uninstall keys for Windows |
|||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayName" "${PRODUCT_NAME}" |
|||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "UninstallString" '"$INSTDIR\uninstall.exe"' |
|||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "NoModify" 1 |
|||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "NoRepair" 1 |
|||
WriteUninstaller "uninstall.exe" |
|||
|
|||
ExecWait '"$INSTDIR\${EXECUTABLE_NAME}" -install' $0 |
|||
DetailPrint "Associating File Types returned $0" |
|||
SectionEnd |
|||
|
|||
; Optional section (can be disabled by the user) |
|||
Section "Start Menu Shortcuts" |
|||
|
|||
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}" |
|||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 |
|||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\${EXECUTABLE_NAME}" "" "$INSTDIR\${EXECUTABLE_NAME}" 0 |
|||
|
|||
SectionEnd |
|||
|
|||
;-------------------------------- |
|||
|
|||
; Uninstaller |
|||
|
|||
Section "Uninstall" |
|||
ExecWait '"$INSTDIR\${EXECUTABLE_NAME}" -uninstall' $0 |
|||
DetailPrint "Removing Explorer Context Action returned $0" |
|||
; Remove registry keys |
|||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" |
|||
DeleteRegKey HKLM "SOFTWARE\${PRODUCT_NAME}" |
|||
|
|||
; Remove files and uninstaller |
|||
Delete "$INSTDIR\uninstall.exe" |
|||
Delete "$INSTDIR\${EXECUTABLE_NAME}" |
|||
Delete "$INSTDIR\${EXECUTABLE_NAME}.config" |
|||
Delete "$INSTDIR\NAudio.dll" |
|||
Delete "$INSTDIR\audio_file_inspector.html" |
|||
|
|||
; Remove shortcuts, if any |
|||
Delete "$SMPROGRAMS\${PRODUCT_NAME}\*.*" |
|||
|
|||
; Remove directories used |
|||
RMDir "$SMPROGRAMS\${PRODUCT_NAME}" |
|||
RMDir "$INSTDIR" |
|||
|
|||
SectionEnd |
@ -0,0 +1,147 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using NAudio.Midi; |
|||
|
|||
namespace AudioFileInspector |
|||
{ |
|||
class MidiFileInspector : IAudioFileInspector |
|||
{ |
|||
#region IAudioFileInspector Members
|
|||
|
|||
public string FileExtension |
|||
{ |
|||
get { return ".mid"; } |
|||
} |
|||
|
|||
public string FileTypeDescription |
|||
{ |
|||
get { return "Standard MIDI File"; } |
|||
} |
|||
|
|||
public string Describe(string fileName) |
|||
{ |
|||
MidiFile mf = new MidiFile(fileName, false); |
|||
|
|||
StringBuilder sb = new StringBuilder(); |
|||
sb.AppendFormat("Format {0}, Tracks {1}, Delta Ticks Per Quarter Note {2}\r\n", |
|||
mf.FileFormat, mf.Tracks, mf.DeltaTicksPerQuarterNote); |
|||
int beatsPerMeasure = FindBeatsPerMeasure(mf.Events[0]); |
|||
for (int n = 0; n < mf.Tracks; n++) |
|||
{ |
|||
foreach (MidiEvent midiEvent in mf.Events[n]) |
|||
{ |
|||
if(!MidiEvent.IsNoteOff(midiEvent)) |
|||
{ |
|||
sb.AppendFormat("{0} {1}\r\n", ToMBT(midiEvent.AbsoluteTime, mf.DeltaTicksPerQuarterNote, beatsPerMeasure), midiEvent); |
|||
} |
|||
} |
|||
} |
|||
return sb.ToString(); |
|||
} |
|||
|
|||
private string ToMBT(long absoluteTime, int ticksPerBeat, int beatsPerMeasure) |
|||
{ |
|||
long measure = (absoluteTime / (ticksPerBeat * beatsPerMeasure)) + 1; |
|||
long beat = ((absoluteTime / ticksPerBeat) % beatsPerMeasure) + 1; |
|||
long tick = absoluteTime % ticksPerBeat; |
|||
return String.Format("{0}:{1}:{2}", measure, beat, tick); |
|||
} |
|||
|
|||
private string ToMBT(MidiEvent midiEvent, int ticksPerBeat, List<TimeSignatureChange> timeSignatures) |
|||
{ |
|||
TimeSignatureChange latestTimeSig = FindLatestTimeSig(midiEvent.AbsoluteTime,timeSignatures); |
|||
long relativeTime = midiEvent.AbsoluteTime - latestTimeSig.AbsoluteTime; |
|||
long measure = (relativeTime / (ticksPerBeat * latestTimeSig.BeatsPerMeasure)) + latestTimeSig.StartMeasureNumber; |
|||
long beat = ((relativeTime / ticksPerBeat) % latestTimeSig.BeatsPerMeasure) + 1; |
|||
long tick = relativeTime % ticksPerBeat; |
|||
return String.Format("{0}:{1}:{2}", measure, beat, tick); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Find the number of beats per measure
|
|||
/// (for now assume just one TimeSignature per MIDI track)
|
|||
/// </summary>
|
|||
private int FindBeatsPerMeasure(IEnumerable<MidiEvent> midiEvents) |
|||
{ |
|||
int beatsPerMeasure = 4; |
|||
foreach (MidiEvent midiEvent in midiEvents) |
|||
{ |
|||
TimeSignatureEvent tse = midiEvent as TimeSignatureEvent; |
|||
if (tse != null) |
|||
{ |
|||
beatsPerMeasure = tse.Numerator; |
|||
} |
|||
} |
|||
return beatsPerMeasure; |
|||
} |
|||
|
|||
private TimeSignatureChange FindLatestTimeSig(long absoluteTime, List<TimeSignatureChange> timeSignatures) |
|||
{ |
|||
TimeSignatureChange latestChange = null; |
|||
foreach (TimeSignatureChange change in timeSignatures) |
|||
{ |
|||
if (absoluteTime >= change.AbsoluteTime) |
|||
latestChange = change; |
|||
else |
|||
break; |
|||
} |
|||
if (latestChange != null) |
|||
{ |
|||
latestChange = new TimeSignatureChange(0, 4, 1); |
|||
} |
|||
return latestChange; |
|||
} |
|||
|
|||
private List<TimeSignatureChange> FindTimeSignatures(List<MidiEvent> midiEvents) |
|||
{ |
|||
long currentTime = -1; |
|||
List<TimeSignatureChange> timeSignatureEvents = new List<TimeSignatureChange>(); |
|||
foreach (MidiEvent midiEvent in midiEvents) |
|||
{ |
|||
TimeSignatureEvent tse = midiEvent as TimeSignatureEvent; |
|||
if (tse != null) |
|||
{ |
|||
if (tse.AbsoluteTime <= currentTime) |
|||
throw new ArgumentException("Unsorted Time Signatures found"); |
|||
// TODO: work out how to get the start measure
|
|||
int startMeasure = 1; |
|||
timeSignatureEvents.Add(new TimeSignatureChange(tse.AbsoluteTime,tse.Numerator,startMeasure)); |
|||
currentTime = tse.AbsoluteTime; |
|||
} |
|||
} |
|||
return timeSignatureEvents; |
|||
} |
|||
|
|||
class TimeSignatureChange |
|||
{ |
|||
long absoluteTime; |
|||
int beatsPerMeasure; |
|||
int startMeasureNumber; |
|||
|
|||
public long AbsoluteTime |
|||
{ |
|||
get { return absoluteTime; } |
|||
} |
|||
|
|||
public int BeatsPerMeasure |
|||
{ |
|||
get { return beatsPerMeasure; } |
|||
} |
|||
|
|||
public int StartMeasureNumber |
|||
{ |
|||
get { return startMeasureNumber; } |
|||
} |
|||
|
|||
public TimeSignatureChange(long absoluteTime, int beatsPerMeasure, int startMeasureNumber) |
|||
{ |
|||
this.absoluteTime = absoluteTime; |
|||
this.beatsPerMeasure = beatsPerMeasure; |
|||
this.startMeasureNumber = startMeasureNumber; |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
@ -0,0 +1,94 @@ |
|||
namespace AudioFileInspector |
|||
{ |
|||
partial class OptionsForm |
|||
{ |
|||
/// <summary>
|
|||
/// Required designer variable.
|
|||
/// </summary>
|
|||
private System.ComponentModel.IContainer components = null; |
|||
|
|||
/// <summary>
|
|||
/// Clean up any resources being used.
|
|||
/// </summary>
|
|||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
if (disposing && (components != null)) |
|||
{ |
|||
components.Dispose(); |
|||
} |
|||
base.Dispose(disposing); |
|||
} |
|||
|
|||
#region Windows Form Designer generated code
|
|||
|
|||
/// <summary>
|
|||
/// Required method for Designer support - do not modify
|
|||
/// the contents of this method with the code editor.
|
|||
/// </summary>
|
|||
private void InitializeComponent() |
|||
{ |
|||
this.buttonAssociate = new System.Windows.Forms.Button(); |
|||
this.buttonDisassociate = new System.Windows.Forms.Button(); |
|||
this.label1 = new System.Windows.Forms.Label(); |
|||
this.SuspendLayout(); |
|||
//
|
|||
// buttonAssociate
|
|||
//
|
|||
this.buttonAssociate.Location = new System.Drawing.Point(73, 64); |
|||
this.buttonAssociate.Name = "buttonAssociate"; |
|||
this.buttonAssociate.Size = new System.Drawing.Size(164, 23); |
|||
this.buttonAssociate.TabIndex = 0; |
|||
this.buttonAssociate.Text = "Recreate File Associations"; |
|||
this.buttonAssociate.UseVisualStyleBackColor = true; |
|||
this.buttonAssociate.Click += new System.EventHandler(this.buttonAssociate_Click); |
|||
//
|
|||
// buttonDisassociate
|
|||
//
|
|||
this.buttonDisassociate.Location = new System.Drawing.Point(73, 93); |
|||
this.buttonDisassociate.Name = "buttonDisassociate"; |
|||
this.buttonDisassociate.Size = new System.Drawing.Size(164, 23); |
|||
this.buttonDisassociate.TabIndex = 1; |
|||
this.buttonDisassociate.Text = "Remove File Associations"; |
|||
this.buttonDisassociate.UseVisualStyleBackColor = true; |
|||
this.buttonDisassociate.Click += new System.EventHandler(this.buttonDisassociate_Click); |
|||
//
|
|||
// label1
|
|||
//
|
|||
this.label1.AutoSize = true; |
|||
this.label1.Location = new System.Drawing.Point(12, 9); |
|||
this.label1.Name = "label1"; |
|||
this.label1.Size = new System.Drawing.Size(290, 52); |
|||
this.label1.TabIndex = 2; |
|||
this.label1.Text = "Audio File Inspector can be set up to appear as a right-click \r\ncontext menu opti" + |
|||
"on in Windows explorer. You can recreate\r\nor remove the file associations at any" + |
|||
" time using the buttons\r\nbelow."; |
|||
//
|
|||
// OptionsForm
|
|||
//
|
|||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); |
|||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; |
|||
this.ClientSize = new System.Drawing.Size(309, 128); |
|||
this.Controls.Add(this.label1); |
|||
this.Controls.Add(this.buttonDisassociate); |
|||
this.Controls.Add(this.buttonAssociate); |
|||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; |
|||
this.MaximizeBox = false; |
|||
this.MinimizeBox = false; |
|||
this.Name = "OptionsForm"; |
|||
this.ShowIcon = false; |
|||
this.ShowInTaskbar = false; |
|||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; |
|||
this.Text = "Options"; |
|||
this.ResumeLayout(false); |
|||
this.PerformLayout(); |
|||
|
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
private System.Windows.Forms.Button buttonAssociate; |
|||
private System.Windows.Forms.Button buttonDisassociate; |
|||
private System.Windows.Forms.Label label1; |
|||
} |
|||
} |
@ -0,0 +1,87 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Data; |
|||
using System.Drawing; |
|||
using System.Text; |
|||
using System.Windows.Forms; |
|||
using NAudio.Utils; |
|||
|
|||
namespace AudioFileInspector |
|||
{ |
|||
partial class OptionsForm : Form |
|||
{ |
|||
List<IAudioFileInspector> inspectors; |
|||
|
|||
public OptionsForm(List<IAudioFileInspector> inspectors) |
|||
{ |
|||
InitializeComponent(); |
|||
this.inspectors = inspectors; |
|||
} |
|||
|
|||
private void buttonAssociate_Click(object sender, EventArgs args) |
|||
{ |
|||
try |
|||
{ |
|||
Associate(inspectors); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
MessageBox.Show( |
|||
String.Format("Unable to create file associations\r\n{0}", e), |
|||
Application.ProductName, |
|||
MessageBoxButtons.OK, |
|||
MessageBoxIcon.Error); |
|||
} |
|||
|
|||
} |
|||
|
|||
private void buttonDisassociate_Click(object sender, EventArgs args) |
|||
{ |
|||
try |
|||
{ |
|||
Disassociate(inspectors); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
MessageBox.Show( |
|||
String.Format("Unable to remove file associations\r\n{0}", e), |
|||
Application.ProductName, |
|||
MessageBoxButtons.OK, |
|||
MessageBoxIcon.Error); |
|||
} |
|||
} |
|||
|
|||
public static void Disassociate(List<IAudioFileInspector> inspectors) |
|||
{ |
|||
foreach (IAudioFileInspector inspector in inspectors) |
|||
{ |
|||
if (!FileAssociations.IsFileTypeRegistered(inspector.FileExtension)) |
|||
{ |
|||
FileAssociations.RegisterFileType(inspector.FileExtension, inspector.FileTypeDescription, null); |
|||
} |
|||
string command = "\"" + Application.ExecutablePath + "\" \"%1\""; |
|||
FileAssociations.RemoveAction( |
|||
inspector.FileExtension, |
|||
"AudioFileInspector"); |
|||
} |
|||
} |
|||
|
|||
public static void Associate(List<IAudioFileInspector> inspectors) |
|||
{ |
|||
foreach (IAudioFileInspector inspector in inspectors) |
|||
{ |
|||
if (!FileAssociations.IsFileTypeRegistered(inspector.FileExtension)) |
|||
{ |
|||
FileAssociations.RegisterFileType(inspector.FileExtension, inspector.FileTypeDescription, null); |
|||
} |
|||
string command = "\"" + Application.ExecutablePath + "\" \"%1\""; |
|||
FileAssociations.AddAction( |
|||
inspector.FileExtension, |
|||
"AudioFileInspector", |
|||
"Describe", |
|||
command); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,120 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<root> |
|||
<!-- |
|||
Microsoft ResX Schema |
|||
|
|||
Version 2.0 |
|||
|
|||
The primary goals of this format is to allow a simple XML format |
|||
that is mostly human readable. The generation and parsing of the |
|||
various data types are done through the TypeConverter classes |
|||
associated with the data types. |
|||
|
|||
Example: |
|||
|
|||
... ado.net/XML headers & schema ... |
|||
<resheader name="resmimetype">text/microsoft-resx</resheader> |
|||
<resheader name="version">2.0</resheader> |
|||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> |
|||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> |
|||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> |
|||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> |
|||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> |
|||
<value>[base64 mime encoded serialized .NET Framework object]</value> |
|||
</data> |
|||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> |
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> |
|||
<comment>This is a comment</comment> |
|||
</data> |
|||
|
|||
There are any number of "resheader" rows that contain simple |
|||
name/value pairs. |
|||
|
|||
Each data row contains a name, and value. The row also contains a |
|||
type or mimetype. Type corresponds to a .NET class that support |
|||
text/value conversion through the TypeConverter architecture. |
|||
Classes that don't support this are serialized and stored with the |
|||
mimetype set. |
|||
|
|||
The mimetype is used for serialized objects, and tells the |
|||
ResXResourceReader how to depersist the object. This is currently not |
|||
extensible. For a given mimetype the value must be set accordingly: |
|||
|
|||
Note - application/x-microsoft.net.object.binary.base64 is the format |
|||
that the ResXResourceWriter will generate, however the reader can |
|||
read any of the formats listed below. |
|||
|
|||
mimetype: application/x-microsoft.net.object.binary.base64 |
|||
value : The object must be serialized with |
|||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter |
|||
: and then encoded with base64 encoding. |
|||
|
|||
mimetype: application/x-microsoft.net.object.soap.base64 |
|||
value : The object must be serialized with |
|||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter |
|||
: and then encoded with base64 encoding. |
|||
|
|||
mimetype: application/x-microsoft.net.object.bytearray.base64 |
|||
value : The object must be serialized into a byte array |
|||
: using a System.ComponentModel.TypeConverter |
|||
: and then encoded with base64 encoding. |
|||
--> |
|||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> |
|||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> |
|||
<xsd:element name="root" msdata:IsDataSet="true"> |
|||
<xsd:complexType> |
|||
<xsd:choice maxOccurs="unbounded"> |
|||
<xsd:element name="metadata"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="value" type="xsd:string" minOccurs="0" /> |
|||
</xsd:sequence> |
|||
<xsd:attribute name="name" use="required" type="xsd:string" /> |
|||
<xsd:attribute name="type" type="xsd:string" /> |
|||
<xsd:attribute name="mimetype" type="xsd:string" /> |
|||
<xsd:attribute ref="xml:space" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
<xsd:element name="assembly"> |
|||
<xsd:complexType> |
|||
<xsd:attribute name="alias" type="xsd:string" /> |
|||
<xsd:attribute name="name" type="xsd:string" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
<xsd:element name="data"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
|||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> |
|||
</xsd:sequence> |
|||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> |
|||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> |
|||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> |
|||
<xsd:attribute ref="xml:space" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
<xsd:element name="resheader"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
|||
</xsd:sequence> |
|||
<xsd:attribute name="name" type="xsd:string" use="required" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
</xsd:choice> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
</xsd:schema> |
|||
<resheader name="resmimetype"> |
|||
<value>text/microsoft-resx</value> |
|||
</resheader> |
|||
<resheader name="version"> |
|||
<value>2.0</value> |
|||
</resheader> |
|||
<resheader name="reader"> |
|||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
|||
</resheader> |
|||
<resheader name="writer"> |
|||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
|||
</resheader> |
|||
</root> |
@ -0,0 +1,62 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Windows.Forms; |
|||
|
|||
namespace AudioFileInspector |
|||
{ |
|||
static class Program |
|||
{ |
|||
/// <summary>
|
|||
/// The main entry point for the application.
|
|||
/// </summary>
|
|||
[STAThread] |
|||
static int Main(string[] args) |
|||
{ |
|||
Application.EnableVisualStyles(); |
|||
Application.SetCompatibleTextRenderingDefault(false); |
|||
|
|||
List<IAudioFileInspector> inspectors = new List<IAudioFileInspector>(); |
|||
inspectors.Add(new WaveFileInspector()); |
|||
inspectors.Add(new MidiFileInspector()); |
|||
inspectors.Add(new SoundFontInspector()); |
|||
inspectors.Add(new CakewalkMapInspector()); |
|||
|
|||
if (args.Length > 0) |
|||
{ |
|||
if (args[0] == "-install") |
|||
{ |
|||
try |
|||
{ |
|||
OptionsForm.Associate(inspectors); |
|||
Console.WriteLine("Created {0} file associations", inspectors.Count); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.WriteLine("Unable to create file associations"); |
|||
Console.WriteLine(e.ToString()); |
|||
return -1; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
else if (args[0] == "-uninstall") |
|||
{ |
|||
try |
|||
{ |
|||
OptionsForm.Disassociate(inspectors); |
|||
Console.WriteLine("Removed {0} file associations", inspectors.Count); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.WriteLine("Unable to remove file associations"); |
|||
Console.WriteLine(e.ToString()); |
|||
return -1; |
|||
} |
|||
return 0; |
|||
} |
|||
} |
|||
Application.Run(new AudioFileInspectorForm(inspectors,args)); |
|||
return 0; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,71 @@ |
|||
using System.Reflection; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
// General Information about an assembly is controlled through the following
|
|||
// set of attributes. Change these attribute values to modify the information
|
|||
// associated with an assembly.
|
|||
[assembly: AssemblyTitle("Audio File Inspector")] |
|||
[assembly: AssemblyDescription("")] |
|||
[assembly: AssemblyConfiguration("")] |
|||
[assembly: AssemblyCompany("Mark Heath")] |
|||
[assembly: AssemblyProduct("Audio File Inspector")] |
|||
[assembly: AssemblyCopyright("Copyright © Mark Heath 2006")] |
|||
[assembly: AssemblyTrademark("")] |
|||
[assembly: AssemblyCulture("")] |
|||
|
|||
// Setting ComVisible to false makes the types in this assembly not visible
|
|||
// to COM components. If you need to access a type in this assembly from
|
|||
// COM, set the ComVisible attribute to true on that type.
|
|||
[assembly: ComVisible(false)] |
|||
|
|||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
|||
[assembly: Guid("8bb855c5-ddfc-4596-a5df-aa9059be7705")] |
|||
|
|||
// Version information for an assembly consists of the following four values:
|
|||
//
|
|||
// Major Version
|
|||
// Minor Version
|
|||
// Build Number
|
|||
// Revision
|
|||
//
|
|||
[assembly: AssemblyVersion("0.1.4.0")] |
|||
[assembly: AssemblyFileVersion("0.1.4.0")] |
|||
|
|||
// build 1 - 1 Nov 2006
|
|||
// initial version - moved out of WavePlayer
|
|||
// extracted file describing logic into classes
|
|||
// drag and drop support
|
|||
// command line support
|
|||
// explorer context menu support
|
|||
// save analysis to text file
|
|||
// installer
|
|||
// command line register and unregister file associations
|
|||
// build 2 - 2 Nov 2006
|
|||
// fixed some installer problems
|
|||
// build 3 - 10 Nov 2006
|
|||
// MIDI note off events are not displayed
|
|||
// Initial M:B:T support for MIDI
|
|||
// build 4 - 26 Apr 2007
|
|||
// Updated to work with latest code in CodePlex
|
|||
// Beginnings of a find feature
|
|||
|
|||
// TODO list
|
|||
// help file
|
|||
|
|||
// better error handling
|
|||
// possibly: a plugin format
|
|||
// options for each inspector
|
|||
// describe on a separate thread
|
|||
|
|||
|
|||
// Enhance existing:
|
|||
// MIDI: riff, M:B:T
|
|||
// WAV: ACID format
|
|||
// Additional formats:
|
|||
// WAV 64
|
|||
// AIFF
|
|||
// REX
|
|||
// MP3
|
|||
// OGG
|
|||
// Project 5 pattern
|
@ -0,0 +1,63 @@ |
|||
//------------------------------------------------------------------------------
|
|||
// <auto-generated>
|
|||
// This code was generated by a tool.
|
|||
// Runtime Version:2.0.50727.42
|
|||
//
|
|||
// Changes to this file may cause incorrect behavior and will be lost if
|
|||
// the code is regenerated.
|
|||
// </auto-generated>
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
namespace AudioFileInspector.Properties { |
|||
using System; |
|||
|
|||
|
|||
/// <summary>
|
|||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
|||
/// </summary>
|
|||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
|||
// class via a tool like ResGen or Visual Studio.
|
|||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
|||
// with the /str option, or rebuild your VS project.
|
|||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] |
|||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] |
|||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] |
|||
internal class Resources { |
|||
|
|||
private static global::System.Resources.ResourceManager resourceMan; |
|||
|
|||
private static global::System.Globalization.CultureInfo resourceCulture; |
|||
|
|||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] |
|||
internal Resources() { |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the cached ResourceManager instance used by this class.
|
|||
/// </summary>
|
|||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] |
|||
internal static global::System.Resources.ResourceManager ResourceManager { |
|||
get { |
|||
if (object.ReferenceEquals(resourceMan, null)) { |
|||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AudioFileInspector.Properties.Resources", typeof(Resources).Assembly); |
|||
resourceMan = temp; |
|||
} |
|||
return resourceMan; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Overrides the current thread's CurrentUICulture property for all
|
|||
/// resource lookups using this strongly typed resource class.
|
|||
/// </summary>
|
|||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] |
|||
internal static global::System.Globalization.CultureInfo Culture { |
|||
get { |
|||
return resourceCulture; |
|||
} |
|||
set { |
|||
resourceCulture = value; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,117 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<root> |
|||
<!-- |
|||
Microsoft ResX Schema |
|||
|
|||
Version 2.0 |
|||
|
|||
The primary goals of this format is to allow a simple XML format |
|||
that is mostly human readable. The generation and parsing of the |
|||
various data types are done through the TypeConverter classes |
|||
associated with the data types. |
|||
|
|||
Example: |
|||
|
|||
... ado.net/XML headers & schema ... |
|||
<resheader name="resmimetype">text/microsoft-resx</resheader> |
|||
<resheader name="version">2.0</resheader> |
|||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> |
|||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> |
|||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> |
|||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> |
|||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> |
|||
<value>[base64 mime encoded serialized .NET Framework object]</value> |
|||
</data> |
|||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> |
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> |
|||
<comment>This is a comment</comment> |
|||
</data> |
|||
|
|||
There are any number of "resheader" rows that contain simple |
|||
name/value pairs. |
|||
|
|||
Each data row contains a name, and value. The row also contains a |
|||
type or mimetype. Type corresponds to a .NET class that support |
|||
text/value conversion through the TypeConverter architecture. |
|||
Classes that don't support this are serialized and stored with the |
|||
mimetype set. |
|||
|
|||
The mimetype is used for serialized objects, and tells the |
|||
ResXResourceReader how to depersist the object. This is currently not |
|||
extensible. For a given mimetype the value must be set accordingly: |
|||
|
|||
Note - application/x-microsoft.net.object.binary.base64 is the format |
|||
that the ResXResourceWriter will generate, however the reader can |
|||
read any of the formats listed below. |
|||
|
|||
mimetype: application/x-microsoft.net.object.binary.base64 |
|||
value : The object must be serialized with |
|||
: System.Serialization.Formatters.Binary.BinaryFormatter |
|||
: and then encoded with base64 encoding. |
|||
|
|||
mimetype: application/x-microsoft.net.object.soap.base64 |
|||
value : The object must be serialized with |
|||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter |
|||
: and then encoded with base64 encoding. |
|||
|
|||
mimetype: application/x-microsoft.net.object.bytearray.base64 |
|||
value : The object must be serialized into a byte array |
|||
: using a System.ComponentModel.TypeConverter |
|||
: and then encoded with base64 encoding. |
|||
--> |
|||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> |
|||
<xsd:element name="root" msdata:IsDataSet="true"> |
|||
<xsd:complexType> |
|||
<xsd:choice maxOccurs="unbounded"> |
|||
<xsd:element name="metadata"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="value" type="xsd:string" minOccurs="0" /> |
|||
</xsd:sequence> |
|||
<xsd:attribute name="name" type="xsd:string" /> |
|||
<xsd:attribute name="type" type="xsd:string" /> |
|||
<xsd:attribute name="mimetype" type="xsd:string" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
<xsd:element name="assembly"> |
|||
<xsd:complexType> |
|||
<xsd:attribute name="alias" type="xsd:string" /> |
|||
<xsd:attribute name="name" type="xsd:string" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
<xsd:element name="data"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
|||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> |
|||
</xsd:sequence> |
|||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" /> |
|||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> |
|||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
<xsd:element name="resheader"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
|||
</xsd:sequence> |
|||
<xsd:attribute name="name" type="xsd:string" use="required" /> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
</xsd:choice> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
</xsd:schema> |
|||
<resheader name="resmimetype"> |
|||
<value>text/microsoft-resx</value> |
|||
</resheader> |
|||
<resheader name="version"> |
|||
<value>2.0</value> |
|||
</resheader> |
|||
<resheader name="reader"> |
|||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
|||
</resheader> |
|||
<resheader name="writer"> |
|||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
|||
</resheader> |
|||
</root> |
@ -0,0 +1,26 @@ |
|||
//------------------------------------------------------------------------------
|
|||
// <auto-generated>
|
|||
// This code was generated by a tool.
|
|||
// Runtime Version:2.0.50727.42
|
|||
//
|
|||
// Changes to this file may cause incorrect behavior and will be lost if
|
|||
// the code is regenerated.
|
|||
// </auto-generated>
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
namespace AudioFileInspector.Properties { |
|||
|
|||
|
|||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] |
|||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "8.0.0.0")] |
|||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { |
|||
|
|||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); |
|||
|
|||
public static Settings Default { |
|||
get { |
|||
return defaultInstance; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,7 @@ |
|||
<?xml version='1.0' encoding='utf-8'?> |
|||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)"> |
|||
<Profiles> |
|||
<Profile Name="(Default)" /> |
|||
</Profiles> |
|||
<Settings /> |
|||
</SettingsFile> |
@ -0,0 +1,67 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using NAudio.SoundFont; |
|||
|
|||
namespace AudioFileInspector |
|||
{ |
|||
class SoundFontInspector : IAudioFileInspector |
|||
{ |
|||
#region IAudioFileInspector Members
|
|||
|
|||
public string FileExtension |
|||
{ |
|||
get { return ".sf2"; } |
|||
} |
|||
|
|||
public string FileTypeDescription |
|||
{ |
|||
get { return "SoundFont File"; } |
|||
} |
|||
|
|||
public string Describe(string fileName) |
|||
{ |
|||
SoundFont sf = new SoundFont(fileName); |
|||
StringBuilder stringBuilder = new StringBuilder(); |
|||
stringBuilder.AppendFormat("{0}\r\n",sf.FileInfo); |
|||
stringBuilder.Append("Presets\r\n"); |
|||
foreach(Preset p in sf.Presets) |
|||
{ |
|||
stringBuilder.AppendFormat("{0}\r\n",p); |
|||
foreach(Zone z in p.Zones) |
|||
{ |
|||
stringBuilder.AppendFormat(" {0}\r\n",z); |
|||
foreach(Generator g in z.Generators) |
|||
{ |
|||
stringBuilder.AppendFormat(" {0}\r\n",g); |
|||
} |
|||
foreach(Modulator m in z.Modulators) |
|||
{ |
|||
stringBuilder.AppendFormat(" {0}\r\n",m); |
|||
} |
|||
} |
|||
} |
|||
|
|||
stringBuilder.Append("Instruments\r\n"); |
|||
foreach(Instrument i in sf.Instruments) |
|||
{ |
|||
stringBuilder.AppendFormat("{0}\r\n",i); |
|||
foreach(Zone z in i.Zones) |
|||
{ |
|||
stringBuilder.AppendFormat(" {0}\r\n",z); |
|||
foreach(Generator g in z.Generators) |
|||
{ |
|||
stringBuilder.AppendFormat(" {0}\r\n",g); |
|||
} |
|||
foreach(Modulator m in z.Modulators) |
|||
{ |
|||
stringBuilder.AppendFormat(" {0}\r\n",m); |
|||
} |
|||
} |
|||
} |
|||
return stringBuilder.ToString(); |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using NAudio.Wave; |
|||
|
|||
namespace AudioFileInspector |
|||
{ |
|||
class WaveFileInspector : IAudioFileInspector |
|||
{ |
|||
#region IAudioFileInspector Members
|
|||
|
|||
public string FileExtension |
|||
{ |
|||
get { return ".wav"; } |
|||
} |
|||
|
|||
public string FileTypeDescription |
|||
{ |
|||
get { return "Wave File"; } |
|||
} |
|||
|
|||
public string Describe(string fileName) |
|||
{ |
|||
StringBuilder stringBuilder = new StringBuilder(); |
|||
using (WaveFileReader wf = new WaveFileReader(fileName)) |
|||
{ |
|||
|
|||
stringBuilder.AppendFormat("{0}\r\n", wf.WaveFormat); |
|||
stringBuilder.AppendFormat("Length: {0}\r\n", wf.Length); |
|||
foreach (RiffChunk chunk in wf.ExtraChunks) |
|||
{ |
|||
stringBuilder.AppendFormat("Chunk: {0}, length {1}\r\n", chunk.IdentifierAsString, chunk.Length); |
|||
byte[] data = wf.GetChunkData(chunk); |
|||
int n = 0; |
|||
foreach (byte b in data) |
|||
{ |
|||
stringBuilder.AppendFormat("{0:X2} ", b); |
|||
if (++n % 8 == 0) |
|||
stringBuilder.Append("\r\n"); |
|||
} |
|||
stringBuilder.Append("\r\n"); |
|||
} |
|||
} |
|||
return stringBuilder.ToString(); |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
@ -0,0 +1,84 @@ |
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> |
|||
<html xmlns="http://www.w3.org/1999/xhtml"> |
|||
<head> |
|||
<title>Audio File Inspector</title> |
|||
</head> |
|||
<body> |
|||
<h2> |
|||
Audio File Inspector</h2> |
|||
<p> |
|||
Audio File Inspector is a simple utility that describes the contents of various |
|||
types of audio file (for example WAV files or MIDI sequences). It started out as |
|||
a developer tool to aid in writing code that reads these various formats, but has |
|||
been released because some people may find it useful for diagnosing problems playing |
|||
back various media files.</p> |
|||
<p> |
|||
Audio File Formats are notoriously vague and it is likely that you will have some |
|||
files on your computer that cause Audio File Inspector to generate an error message. |
|||
If you encounter a problem, then feel free to zip the file up and email it to me |
|||
and I will consider updating the utility to support your file.</p> |
|||
<h2> |
|||
Supported File Types</h2> |
|||
<p> |
|||
The current version of Audio File Inspector supports the following file types:</p> |
|||
<ul> |
|||
<li><b>MID</b> - This will enumerate each event in the MIDI file. At the moment it |
|||
reports the time of the event in terms of "absolute time". A future update will turn |
|||
this into measures, beats and ticks (M:B:T)</li> |
|||
<li><b>SF2</b> - This examines a SoundFont file, reporting all the instruments, zones |
|||
and audio files contained within</li> |
|||
<li><b>WAV</b> - This will report the format of the audio data, as well as providing |
|||
binary dumps of any extra chunks of information.</li> |
|||
<li><b>MAP</b> - This can extract some information from a Cakewalk drum map file. |
|||
</li> |
|||
</ul> |
|||
|
|||
<h2> |
|||
Download</h2> |
|||
|
|||
<p>A link to the latest version of Audio File Inspector can be found <a href="http://www.wordandspirit.co.uk/software.html#afi"> |
|||
here</a>.</p> |
|||
<h2> |
|||
Installation</h2> |
|||
|
|||
<p>Audio File Inspector requires the .NET framework version 2.0 to be installed. |
|||
You can download this <a href="http://msdn.microsoft.com/netframework/downloads/updates/default.aspx"> |
|||
here</a>. Once you have done this you can run the installer which will add a right-click "Describe" |
|||
action to Windows Explorer for each of the supported file types.</p> |
|||
<h2> |
|||
Usage</h2> |
|||
<p> |
|||
There are a variety of ways in which you can load a file into Audio File Inspector for |
|||
analysis.</p> |
|||
<ul> |
|||
<li>Right-click a supported file in Windows explorer and click <b>"Describe"</b></li> |
|||
<li>Use the <b>File|Open</b> menu option to select a file to analyse</li> |
|||
<li>Drag a file from Windows explorer onto the title-bar of Audio File Inspector</li> |
|||
<li>You can also drag a file over the executable itself or run Audio File Inspector |
|||
from the command line with a filename as an argument.</li> |
|||
</ul> |
|||
<h2> |
|||
Tools Menu</h2> |
|||
<p> |
|||
On the tools menu you can find commands that allow you to clear the log or to save its |
|||
contents to disk (you are of course free to edit the contents in any way you like before |
|||
saving it). |
|||
|
|||
There is also an options form, which in this version simply allows you to recreate or remove |
|||
the right-click "Describe" option in Windows Explorer.</p> |
|||
<h2> |
|||
Version History</h2> |
|||
<p> |
|||
The latest version of Audio File Inspector can be found at <a href="http://www.wordandspirit.co.uk/software.html#afi"> |
|||
http://www.wordandspirit.co.uk/software.html</a>. |
|||
</p> |
|||
<ul> |
|||
<li>v0.1 2 Nov 2006 |
|||
<ul> |
|||
<li>First public beta release</li> |
|||
<li>Support for WAV, MID, SF2 and MAP</li> |
|||
</ul> |
|||
</li> |
|||
</ul> |
|||
</body> |
|||
</html> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue