Added C# example application that processes mp3 files with SoundTouch

This commit is contained in:
oparviai 2017-07-25 12:40:11 +00:00
parent 92bfdd1e8d
commit 59e6726118
18 changed files with 1487 additions and 0 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>

View File

@ -0,0 +1,9 @@
<Application x:Class="csharp_example.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:csharp_example"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View File

@ -0,0 +1,25 @@
//////////////////////////////////////////////////////////////////////////////
///
/// C# example that manipulates mp3 audio files with SoundTouch library.
///
/// Author : Copyright (c) Olli Parviainen
/// Author e-mail : oparviai 'at' iki.fi
/// SoundTouch WWW: http://www.surina.net/soundtouch
///
////////////////////////////////////////////////////////////////////////////////
//
// License for this source code file: Microsoft Public License(Ms-PL)
//
////////////////////////////////////////////////////////////////////////////////
using System.Windows;
namespace csharp_example
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@ -0,0 +1,31 @@
<Window x:Class="csharp_example.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:csharp_example"
mc:Ignorable="d"
Title="C# SoundTouch example" Height="250 " Width="400">
<Grid Margin="0,0,0,-3">
<TextBlock HorizontalAlignment="Left" Margin="10,21,0,0" Text="Input audio file:" VerticalAlignment="Top"/>
<TextBox x:Name="textBox_filename" Height="23" Margin="107,20,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="180" IsEnabled="False"/>
<Button x:Name="button_browse" Content="_Browse" HorizontalAlignment="Left" Margin="292,19,0,0" VerticalAlignment="Top" Width="75" Height="24" Click="button_browse_Click"/>
<TextBlock HorizontalAlignment="Left" Margin="54,55,0,0" Text="Tempo:" VerticalAlignment="Top" TextAlignment="Right"/>
<TextBox x:Name="textBox_tempo" HorizontalAlignment="Left" Height="23" Margin="107,55,0,0" Text="0" VerticalAlignment="Top" Width="75" TextAlignment="Center" LostFocus="textBox_tempo_LostFocus" KeyDown="textBox_tempo_KeyDown"/>
<TextBlock HorizontalAlignment="Left" Margin="185,55,0,0" Text="%" VerticalAlignment="Top" TextAlignment="Right"/>
<TextBlock HorizontalAlignment="Left" Margin="64,85,0,0" Text="Pitch:" VerticalAlignment="Top" TextAlignment="Right"/>
<TextBox x:Name="textBox_pitch" HorizontalAlignment="Left" Height="23" Margin="107,85,0,0" Text="0" VerticalAlignment="Top" Width="75" TextAlignment="Center" LostFocus="textBox_pitch_LostFocus" KeyDown="textBox_pitch_KeyDown"/>
<TextBlock HorizontalAlignment="Left" Margin="185,85,0,0" Text="semitones" VerticalAlignment="Top" TextAlignment="Right"/>
<TextBlock HorizontalAlignment="Left" Margin="66,116,0,0" Text="Rate:" VerticalAlignment="Top" TextAlignment="Right"/>
<TextBox x:Name="textBox_rate" HorizontalAlignment="Left" Height="23" Margin="107,116,0,0" Text="0" VerticalAlignment="Top" Width="75" TextAlignment="Center" LostFocus="textBox_rate_LostFocus" KeyDown="textBox_rate_KeyDown"/>
<TextBlock HorizontalAlignment="Left" Margin="185,116,0,0" Text="%" VerticalAlignment="Top" TextAlignment="Right"/>
<Button x:Name="button_play" Content="_Play" Margin="107,150,0,0" VerticalAlignment="Top" Height="24" HorizontalAlignment="Left" Width="75" Click="button_play_Click" IsEnabled="False"/>
<Button x:Name="button_stop" Content="_Stop" Margin="200,150,0,0" VerticalAlignment="Top" Height="24" HorizontalAlignment="Left" Width="75" Click="button_stop_Click" IsEnabled="False"/>
<StatusBar VerticalAlignment="Bottom">
<TextBlock x:Name="text_status" HorizontalAlignment="Left" Margin="2,0,0,2"/>
</StatusBar>
</Grid>
</Window>

View File

@ -0,0 +1,225 @@
//////////////////////////////////////////////////////////////////////////////
///
/// C# example that manipulates mp3 audio files with SoundTouch library.
///
/// Author : Copyright (c) Olli Parviainen
/// Author e-mail : oparviai 'at' iki.fi
/// SoundTouch WWW: http://www.surina.net/soundtouch
///
////////////////////////////////////////////////////////////////////////////////
//
// License for this source code file: Microsoft Public License(Ms-PL)
//
////////////////////////////////////////////////////////////////////////////////
using soundtouch;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace csharp_example
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
protected SoundProcessor processor = new SoundProcessor();
public MainWindow()
{
InitializeComponent();
StatusMessage.statusEvent += StatusEventHandler;
processor.PlaybackStopped += EventHandler_playbackStopped;
DisplaySoundTouchVersion();
}
/// <summary>
/// Display SoundTouch library version string in status bar. This also indicates whether the DLL was loaded succesfully or not ...
/// </summary>
private void DisplaySoundTouchVersion()
{
string status;
try
{
status = String.Format("SoundTouch version: {0}", SoundTouch.GetVersionString());
}
catch (Exception exp)
{
status = exp.Message;
}
text_status.Text = status;
}
private void StatusEventHandler(object sender, string msg)
{
text_status.Text = msg;
}
private void button_browse_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog openDialog = new Microsoft.Win32.OpenFileDialog();
openDialog.Filter = "MP3 files (*.mp3)|*.mp3";
if (openDialog.ShowDialog() == true)
{
if (processor.OpenMp3File(openDialog.FileName) == true)
{
textBox_filename.Text = openDialog.FileName;
button_play.IsEnabled = true;
button_stop.IsEnabled = true;
// Parse adjustment settings
ParseTempoTextBox();
ParsePitchTextBox();
ParseRateTextBox();
}
else
{
MessageBox.Show("Coudln't open audio file " + openDialog.FileName);
}
}
}
private void setPlayButtonMode(bool play)
{
button_play.Content = play ? "_Play" : "_Pause";
}
private void EventHandler_playbackStopped(object sender, bool hasReachedEnd)
{
if (hasReachedEnd)
{
text_status.Text = "Stopped";
} // otherwise paused
setPlayButtonMode(true);
}
private void button_play_Click(object sender, RoutedEventArgs e)
{
if (button_play.Content == "_Pause")
{
// Pause
if (processor.Pause())
{
text_status.Text = "Paused";
}
setPlayButtonMode(true);
}
else
{
// Play
if (processor.Play())
{
text_status.Text = "Playing";
setPlayButtonMode(false);
}
}
}
private void button_stop_Click(object sender, RoutedEventArgs e)
{
if (processor.Stop())
{
text_status.Text = "Stopped";
}
setPlayButtonMode(true);
}
private bool parse_percentValue(TextBox box, out double value)
{
if (double.TryParse(box.Text, out value) == false) return false;
if (value < -99.0) value = -99.0; // don't allow more than -100% slowdown ... :)
box.Text = value.ToString();
return true;
}
private void ParsePitchTextBox()
{
double pitchValue;
if (double.TryParse(textBox_pitch.Text, out pitchValue))
{
if (processor.streamProcessor != null) processor.streamProcessor.st.SetPitchSemiTones((float)pitchValue);
}
}
private void ParseTempoTextBox()
{
double tempoValue;
if (parse_percentValue(textBox_tempo, out tempoValue))
{
if (processor.streamProcessor != null) processor.streamProcessor.st.SetTempoChange((float)tempoValue);
}
}
private void ParseRateTextBox()
{
double rateValue;
if (parse_percentValue(textBox_rate, out rateValue))
{
if (processor.streamProcessor != null) processor.streamProcessor.st.SetRateChange((float)rateValue);
}
}
private void textBox_tempo_LostFocus(object sender, RoutedEventArgs e)
{
ParseTempoTextBox();
}
private void textBox_tempo_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
// enter pressed -- parse value
ParseTempoTextBox();
}
}
private void textBox_pitch_LostFocus(object sender, RoutedEventArgs e)
{
ParsePitchTextBox();
}
private void textBox_pitch_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
// enter pressed -- parse value
ParsePitchTextBox();
}
}
private void textBox_rate_LostFocus(object sender, RoutedEventArgs e)
{
ParseRateTextBox();
}
private void textBox_rate_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
// enter pressed -- parse value
ParseRateTextBox();
}
}
}
}

View File

@ -0,0 +1,31 @@
Microsoft Public License (Ms-PL)
This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
1. Definitions
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or changes to the software.
A "contributor" is any person that distributes its contribution under this license.
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
3. Conditions and Limitations
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.

View File

@ -0,0 +1,92 @@
NAudio is an open source .NET audio library written by Mark Heath (mark.heath@gmail.com)
For more information, visit http://naudio.codeplex.com
NAudio is now being hosted on GitHub http://github.com/naudio/NAudio
THANKS
======
The following list includes some of the people who have contributed in various ways to NAudio, such as code contributions,
bug fixes, documentation, helping out on the forums and even donations. I haven't finished compiling this list yet, so
if your name should be on it but isn't please let me know and I will include it. Also, some people I only know by their forum
id, so if you want me to put your full name here, please also get in touch.
in alphabetical order:
Alan Jordan
Alexandre Mutel
Alexander Binkert
AmandaTarafaMas
balistof
biermeester
borman11
bradb
Brandon Hansen (kg6ypi)
csechet
ChunkWare Music Software
CKing
DaMacc
Dirk Eckhardt
Du10
eejake52
Florian Rosmann (filoe)
Freefall
Giawa
Harald Petrovitsch
Hfuy
Iain McCowan
Idael Cardaso
ioctlLR
Ivan Kochurkin (KvanTTT)
Jamie Michael Ewins
jannera
jbaker8935
jcameron23
JoeGaggler
jonahoffmann
jontdelorme
Jospin Software
Justin Frankel
K24A3
Kamen Lichev
Kassoul
kevinxxx
kzych
LionCash
Lustild
Lucian Wischik (ljw1004)
ManuN
MeelMarcel
Michael Chadwick
Michael Feld
Michael J
Michael Lehenbauer
milligan22963
myrkle
nelsonkidd
Nigel Redmon
Nikolaos Georgiou
Owen Skriloff
owoudenb
painmailer
PPavan
Pygmy
Ray Molenkamp
Roadz
Robert Bristow-Johnson
Scott Fleischman
Simon Clark
Sirish Bajpai
sporn
Steve Underwood
Ted Murphy
Tiny Simple Tools
Tobias Fleming
TomBogle
Tony Cabello
Tony Sistemas
TuneBlade
topher3683
volmart
Vladimir Rokovanov
Ville Koskinen
Wyatt Rice
Yuval Naveh
Zsb

Binary file not shown.

View File

@ -0,0 +1,55 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// 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("csharp-example")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("csharp-example")]
[assembly: AssemblyCopyright("Copyright Olli Parviainen © 2017")]
[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)]
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
//the NeutralResourceLanguage attribute below. Update the "en-US" in
//the line below to match the UICulture setting in the project file.
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,71 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace csharp_example.Properties
{
/// <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", "4.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 ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("csharp_example.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;
}
}
}
}

View File

@ -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>

View File

@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace csharp_example.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.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;
}
}
}
}

View File

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@ -0,0 +1,292 @@
//////////////////////////////////////////////////////////////////////////////
///
/// WaveStream processor class for manipulating audio stream in C# with
/// SoundTouch library.
///
/// This module uses NAudio library for C# audio file input / output
///
/// Author : Copyright (c) Olli Parviainen
/// Author e-mail : oparviai 'at' iki.fi
/// SoundTouch WWW: http://www.surina.net/soundtouch
///
////////////////////////////////////////////////////////////////////////////////
//
// License for this source code file: Microsoft Public License(Ms-PL)
//
////////////////////////////////////////////////////////////////////////////////
using NAudio.Wave;
using soundtouch;
using System;
namespace csharp_example
{
/// <summary>
/// Helper class that allow writing status texts to the host application
/// </summary>
public class StatusMessage
{
/// <summary>
/// Handler for status message events. Subscribe this from the host application
/// </summary>
public static event EventHandler<string> statusEvent;
/// <summary>
/// Pass a status message to the host application
/// </summary>
public static void Write(string msg)
{
if (statusEvent != null)
{
statusEvent(null, msg);
}
}
}
/// <summary>
/// NAudui WaveStream class for processing audio stream with SoundTouch effects
/// </summary>
public class WaveStreamProcessor : WaveStream
{
private WaveChannel32 inputStr;
public SoundTouch st;
private byte[] bytebuffer = new byte[4096];
private float[] floatbuffer = new float[1024];
bool endReached = false;
/// <summary>
/// Constructor
/// </summary>
/// <param name="input">WaveChannel32 stream used for processor stream input</param>
public WaveStreamProcessor(WaveChannel32 input)
{
inputStr = input;
st = new SoundTouch();
st.SetChannels((uint)input.WaveFormat.Channels);
st.SetSampleRate((uint)input.WaveFormat.SampleRate);
}
/// <summary>
/// True if end of stream reached
/// </summary>
public bool EndReached
{
get { return endReached; }
}
public override long Length
{
get
{
return inputStr.Length;
}
}
public override long Position
{
get
{
return inputStr.Position;
}
set
{
inputStr.Position = value;
}
}
public override WaveFormat WaveFormat
{
get
{
return inputStr.WaveFormat;
}
}
/// <summary>
/// Overridden Read function that returns samples processed with SoundTouch. Returns data in same format as
/// WaveChannel32 i.e. stereo float samples.
/// </summary>
/// <param name="buffer">Buffer where to return sample data</param>
/// <param name="offset">Offset from beginning of the buffer</param>
/// <param name="count">Number of bytes to return</param>
/// <returns>Number of bytes copied to buffer</returns>
public override int Read(byte[] buffer, int offset, int count)
{
try
{
// Iterate until enough samples available for output:
// - read samples from input stream
// - put samples to SoundStretch processor
while (st.NumSamples() < count)
{
int nbytes = inputStr.Read(bytebuffer, 0, bytebuffer.Length);
if (nbytes == 0)
{
// end of stream. flush final samples from SoundTouch buffers to output
if (endReached == false)
{
endReached = true; // do only once to avoid continuous flushing
st.Flush();
}
break;
}
// binary copy data from "byte[]" to "float[]" buffer
Buffer.BlockCopy(bytebuffer, 0, floatbuffer, 0, nbytes);
st.PutSamples(floatbuffer, (uint)(nbytes / 8));
}
// ensure that buffer is large enough to receive desired amount of data out
if (floatbuffer.Length < count / 4)
{
floatbuffer = new float[count / 4];
}
// get processed output samples from SoundTouch
int numsamples = (int)st.ReceiveSamples(floatbuffer, (uint)(count / 8));
// binary copy data from "float[]" to "byte[]" buffer
Buffer.BlockCopy(floatbuffer, 0, buffer, offset, numsamples * 8);
return numsamples * 8; // number of bytes
}
catch (Exception exp)
{
StatusMessage.Write("exception in WaveStreamProcessor.Read: " + exp.Message);
return 0;
}
}
/// <summary>
/// Clear the internal processor buffers. Call this if seeking or rewinding to new position within the stream.
/// </summary>
public void Clear()
{
st.Clear();
endReached = false;
}
}
/// <summary>
/// Class that opens & plays MP3 file and allows real-time audio processing with SoundTouch
/// while playing
/// </summary>
public class SoundProcessor
{
Mp3FileReader mp3File;
WaveOut waveOut;
public WaveStreamProcessor streamProcessor;
/// <summary>
/// Start / resume playback
/// </summary>
/// <returns>true if succesful, false if audio file not open</returns>
public bool Play()
{
if (waveOut == null) return false;
if (waveOut.PlaybackState != PlaybackState.Playing)
{
waveOut.Play();
}
return true;
}
/// <summary>
/// Pause playback
/// </summary>
/// <returns>true if succesful, false if audio not playing</returns>
public bool Pause()
{
if (waveOut == null) return false;
if (waveOut.PlaybackState == PlaybackState.Playing)
{
waveOut.Stop();
return true;
}
return false;
}
/// <summary>
/// Stop playback
/// </summary>
/// <returns>true if succesful, false if audio file not open</returns>
public bool Stop()
{
if (waveOut == null) return false;
waveOut.Stop();
mp3File.Position = 0;
streamProcessor.Clear();
return true;
}
/// <summary>
/// Event for "playback stopped" event. 'bool' argument is true if playback has reached end of stream.
/// </summary>
public event EventHandler<bool> PlaybackStopped;
/// <summary>
/// Proxy event handler for receiving playback stoped event from WaveOut
/// </summary>
protected void EventHandler_stopped(object sender, StoppedEventArgs args)
{
bool isEnd = streamProcessor.EndReached;
if (isEnd)
{
Stop();
}
if (PlaybackStopped != null)
{
PlaybackStopped(sender, isEnd);
}
}
/// <summary>
/// Open MP3 file
/// </summary>
/// <param name="filePath">Path to file to open</param>
/// <returns>true if successful</returns>
public bool OpenMp3File(string filePath)
{
try
{
mp3File = new Mp3FileReader(filePath);
WaveChannel32 inputStream = new WaveChannel32(mp3File);
inputStream.PadWithZeroes = false; // don't pad, otherwise the stream never ends
streamProcessor = new WaveStreamProcessor(inputStream);
waveOut = new WaveOut()
{
DesiredLatency = 100
};
waveOut.Init(streamProcessor); // inputStream);
waveOut.PlaybackStopped += EventHandler_stopped;
StatusMessage.Write("Opened file " + filePath);
return true;
}
catch (Exception exp)
{
// Error in opening file
waveOut = null;
StatusMessage.Write("Can't open file: " + exp.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,351 @@
//////////////////////////////////////////////////////////////////////////////
///
/// C# wrapper to access SoundTouch APIs from an external SoundTouch.dll library
///
/// Author : Copyright (c) Olli Parviainen
/// Author e-mail : oparviai 'at' iki.fi
/// SoundTouch WWW: http://www.surina.net/soundtouch
///
////////////////////////////////////////////////////////////////////////////////
//
// License :
//
// SoundTouch audio processing library
// Copyright (c) Olli Parviainen
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
////////////////////////////////////////////////////////////////////////////////
using System;
using System.Runtime.InteropServices;
namespace soundtouch
{
public class SoundTouch
{
private IntPtr handle;
public SoundTouch()
{
handle = soundtouch_createInstance();
}
~SoundTouch()
{
soundtouch_destroyInstance(handle);
}
/// <summary>
/// Get SoundTouch version string
/// </summary>
public static String GetVersionString()
{
// convert "char *" data to c# string
return Marshal.PtrToStringAnsi(soundtouch_getVersionString());
}
/// <summary>
/// Returns number of processed samples currently available in SoundTouch for immediate output.
/// </summary>
public uint NumSamples()
{
return soundtouch_numSamples(handle);
}
/// <summary>
/// Adds 'numSamples' pcs of samples from the 'samples' memory position into
/// the input of the object. Notice that sample rate _has_to_ be set before
/// calling this function, otherwise throws a runtime_error exception.
/// </summary>
/// <param name="samples">Sample buffer to input</param>
/// <param name="numSamples">Number of sample frames in buffer. Notice
/// that in case of multi-channel sound a single sample frame contains
/// data for all channels</param>
public void PutSamples(float[] samples, uint numSamples)
{
soundtouch_putSamples(handle, samples, numSamples);
}
/// <summary>
/// Sets the number of channels
/// </summary>
/// <param name="numChannels">1 = mono, 2 = stereo, n = multichannel</param>
public void SetChannels(uint numChannels)
{
soundtouch_setChannels(handle, numChannels);
}
/// <summary>
/// Sets sample rate.
/// </summary>
/// <param name="srate">Samplerate, e.g. 44100</param>
public void SetSampleRate(uint srate)
{
soundtouch_setSampleRate(handle, srate);
}
/// <summary>
/// Receive processed samples from the processor.
/// </summary>
/// <param name="outBuffer">Buffer where to copy output samples</param>
/// <param name="maxSamples">Max number of sample frames to receive</param>
/// <returns></returns>
public uint ReceiveSamples(float[] outBuffer, uint maxSamples)
{
return soundtouch_receiveSamples(handle, outBuffer, maxSamples);
}
/// <summary>
/// Flushes the last samples from the processing pipeline to the output.
/// Clears also the internal processing buffers.
//
/// Note: This function is meant for extracting the last samples of a sound
/// stream. This function may introduce additional blank samples in the end
/// of the sound stream, and thus it's not recommended to call this function
/// in the middle of a sound stream.
/// </summary>
public void Flush()
{
soundtouch_flush(handle);
}
/// <summary>
/// Clears all the samples in the object's output and internal processing
/// buffers.
/// </summary>
public void Clear()
{
soundtouch_clear(handle);
}
/// <summary>
/// Sets new tempo control value.
/// </summary>
/// <param name="newTempo">Tempo setting. Normal tempo = 1.0, smaller values
/// represent slower tempo, larger faster tempo.</param>
public void SetTempo(float newTempo)
{
soundtouch_setTempo(handle, newTempo);
}
/// <summary>
/// Sets new tempo control value as a difference in percents compared
/// to the original tempo (-50 .. +100 %);
/// </summary>
/// <param name="newTempo">Tempo setting in %</param>
public void SetTempoChange(float newTempo)
{
soundtouch_setTempoChange(handle, newTempo);
}
/// <summary>
/// Sets new rate control value.
/// </summary>
/// <param name="newRate">Rate setting. Normal rate = 1.0, smaller values
/// represent slower rate, larger faster rate.</param>
public void SetRate(float newRate)
{
soundtouch_setTempo(handle, newRate);
}
/// <summary>
/// Sets new rate control value as a difference in percents compared
/// to the original rate (-50 .. +100 %);
/// </summary>
/// <param name="newRate">Rate setting in %</param>
public void SetRateChange(float newRate)
{
soundtouch_setRateChange(handle, newRate);
}
/// <summary>
/// Sets new pitch control value.
/// </summary>
/// <param name="newPitch">Pitch setting. Original pitch = 1.0, smaller values
/// represent lower pitches, larger values higher pitch.</param>
public void SetPitch(float newPitch)
{
soundtouch_setPitch(handle, newPitch);
}
/// <summary>
/// Sets pitch change in octaves compared to the original pitch
/// (-1.00 .. +1.00 for +- one octave);
/// </summary>
/// <param name="newPitch">Pitch setting in octaves</param>
public void SetPitchOctaves(float newPitch)
{
soundtouch_setPitchOctaves(handle, newPitch);
}
/// <summary>
/// Sets pitch change in semi-tones compared to the original pitch
/// (-12 .. +12 for +- one octave);
/// </summary>
/// <param name="newPitch">Pitch setting in semitones</param>
public void SetPitchSemiTones(float newPitch)
{
soundtouch_setPitchSemiTones(handle, newPitch);
}
/// <summary>
/// int16 version of soundtouch_putSamples(): This accept int16 (short) sample data
/// and internally converts it to float format before processing
/// </summary>
/// <param name="samples">Sample input buffer.</param>
/// <param name="numSamples">Number of sample frames in buffer. Notice
/// that in case of multi-channel sound a single
/// sample frame contains data for all channels.</param>
public void PutSamples_i16(short[] samples, uint numSamples)
{
soundtouch_putSamples_i16(handle, samples, numSamples);
}
/// <summary>
/// Changes a setting controlling the processing system behaviour. See the
/// 'SETTING_...' defines for available setting ID's.
/// </summary>
/// <param name="settingId">Setting ID number. see SETTING_... defines.</param>
/// <param name="value"New setting value></param>
/// <returns>nonzero if successful, otherwise zero</returns>
public int SetSetting(int settingId, int value)
{
return soundtouch_setSetting(handle, settingId, value);
}
/// <summary>
/// Reads a setting controlling the processing system behaviour. See the
/// 'SETTING_...' defines for available setting ID's.
/// </summary>
/// <param name="settingId">Setting ID number</param>
/// <returns>The setting value</returns>
public int soundtouch_getSetting(int settingId)
{
return soundtouch_getSetting(handle, settingId);
}
/// <summary>
/// Returns number of samples currently unprocessed in SoundTouch internal buffer
/// </summary>
/// <returns>Number of sample frames</returns>
public uint NumUnprocessedSamples()
{
return soundtouch_numUnprocessedSamples(handle);
}
/// <summary>
/// int16 version of soundtouch_receiveSamples(): This converts internal float samples
/// into int16 (short) return data type
/// </summary>
/// <param name="outBuffer">Buffer where to copy output samples.</param>
/// <param name="maxSamples">How many samples to receive at max.</param>
/// <returns>Number of received sample frames</returns>
public uint soundtouch_receiveSamples_i16(short[] outBuffer, uint maxSamples)
{
return soundtouch_receiveSamples_i16(handle, outBuffer, maxSamples);
}
/// <summary>
/// Check if there aren't any samples available for outputting.
/// </summary>
/// <returns>nonzero if there aren't any samples available for outputting</returns>
public int IsEmpty()
{
return soundtouch_isEmpty(handle);
}
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "soundtouch_getVersionId")]
/// <summary>
/// Get SoundTouch library version Id
/// </summary>
public static extern int GetVersionId();
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr soundtouch_createInstance();
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_destroyInstance(IntPtr h);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr soundtouch_getVersionString();
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_setRate(IntPtr h, float newRate);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_setTempo(IntPtr h, float newTempo);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_setRateChange(IntPtr h, float newRate);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_setTempoChange(IntPtr h, float newTempo);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_setPitch(IntPtr h, float newPitch);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_setPitchOctaves(IntPtr h, float newPitch);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_setPitchSemiTones(IntPtr h, float newPitch);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_setChannels(IntPtr h, uint numChannels);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_setSampleRate(IntPtr h, uint srate);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_flush(IntPtr h);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_putSamples(IntPtr h, float[] samples, uint numSamples);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_putSamples_i16(IntPtr h, short[] samples, uint numSamples);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void soundtouch_clear(IntPtr h);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int soundtouch_setSetting(IntPtr h, int settingId, int value);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int soundtouch_getSetting(IntPtr h, int settingId);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern uint soundtouch_numUnprocessedSamples(IntPtr h);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern uint soundtouch_receiveSamples(IntPtr h, float[] outBuffer, uint maxSamples);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern uint soundtouch_receiveSamples_i16(IntPtr h, short[] outBuffer, uint maxSamples);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern uint soundtouch_numSamples(IntPtr h);
[DllImport("SoundTouch.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int soundtouch_isEmpty(IntPtr h);
}
}

Binary file not shown.

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1BE6EF4F-6A62-447B-A863-E918D68BDAD7}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>csharp_example</RootNamespace>
<AssemblyName>csharp-example</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<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' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="NAudio, Version=1.8.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>lib\NAudio.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="SoundProcessor.cs" />
<Compile Include="SoundTouch.cs" />
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Content Include="NAudio.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="SoundTouch.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Service References\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\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>

View File

@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csharp-example", "csharp-example.csproj", "{1BE6EF4F-6A62-447B-A863-E918D68BDAD7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1BE6EF4F-6A62-447B-A863-E918D68BDAD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1BE6EF4F-6A62-447B-A863-E918D68BDAD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1BE6EF4F-6A62-447B-A863-E918D68BDAD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1BE6EF4F-6A62-447B-A863-E918D68BDAD7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal