diff --git a/XCoder/Properties/Resources.Designer.cs b/XCoder/Properties/Resources.Designer.cs
index a8b9c21..57cb355 100644
--- a/XCoder/Properties/Resources.Designer.cs
+++ b/XCoder/Properties/Resources.Designer.cs
@@ -59,5 +59,15 @@ namespace CrazyCoder.Properties {
resourceCulture = value;
}
}
+
+ /// <summary>
+ /// 查找 System.Byte[] 类型的本地化资源。
+ /// </summary>
+ internal static byte[] playTest {
+ get {
+ object obj = ResourceManager.GetObject("playTest", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
}
}
diff --git a/XCoder/Properties/Resources.resx b/XCoder/Properties/Resources.resx
index 1af7de1..687b470 100644
--- a/XCoder/Properties/Resources.resx
+++ b/XCoder/Properties/Resources.resx
@@ -117,4 +117,8 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
+ <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+ <data name="playTest" type="System.Resources.ResXFileRef, System.Windows.Forms">
+ <value>..\Windows\playTest.wav;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </data>
</root>
\ No newline at end of file
diff --git a/XCoder/Windows/AudioHelper.cs b/XCoder/Windows/AudioHelper.cs
new file mode 100644
index 0000000..12ccc24
--- /dev/null
+++ b/XCoder/Windows/AudioHelper.cs
@@ -0,0 +1,187 @@
+
+using Microsoft.Win32;
+using NAudio.CoreAudioApi;
+using NewLife;
+using NewLife.Collections;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Configuration;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace XCoder
+{
+ public class AudioDevice
+ {
+ /// <summary>设备名称</summary>
+ public string Name { get; set; }
+
+ /// <summary>ContainerID</summary>
+ public string ContainerId { get; set; }
+
+ /// <summary>播放设备</summary>
+ public MMDevice PlaybackDevice;
+
+ /// <summary>录音设备</summary>
+ public MMDevice RecordingDevice;
+
+ public override string ToString()
+ {
+ var sb = Pool.StringBuilder.Get();
+
+ sb.AppendLine($"设备:{Name}");
+ sb.AppendLine($"ContainerID:{ContainerId}");
+
+ sb.Append("播放:");
+ if (PlaybackDevice != null)
+ {
+ sb.Append($" 声道:{PlaybackDevice.AudioClient.MixFormat.Channels}");
+ sb.Append($" 采样率:{PlaybackDevice.AudioClient.MixFormat.SampleRate}");
+ }
+ else
+ {
+ sb.Append("无");
+ }
+ sb.AppendLine();
+
+ sb.Append("录音:");
+ if (RecordingDevice != null)
+ {
+ sb.Append($" 声道:{RecordingDevice.AudioClient.MixFormat.Channels}");
+ sb.Append($" 采样率:{RecordingDevice.AudioClient.MixFormat.SampleRate}");
+ }
+ else
+ {
+ sb.Append("无");
+ }
+ sb.AppendLine();
+
+ return sb.Return(true);
+ }
+
+ /// <summary>字符串截取括号内内容</summary>
+ /// <remarks>FriendlyName 中截取括号内声卡名称</remarks>
+ /// <param name="str"></param>
+ /// <returns></returns>
+ private string getCardName(string str)
+ {
+ if (str == null) return null;
+
+ int start = str.IndexOf('(') + 1;
+ int end = str.IndexOf(')', start);
+ if (start > 0 && end > start)
+ {
+ return str.Substring(start, end - start);
+ }
+
+ return null;
+ }
+
+ /// <summary>获取声卡名称</summary>
+ /// <returns></returns>
+ public string GetCardName()
+ {
+ if (PlaybackDevice != null)
+ {
+ return PlaybackDevice.DeviceFriendlyName;
+ //return getCardName(PlaybackDevice.DeviceFriendlyName);
+ }
+ if (RecordingDevice != null)
+ {
+ return RecordingDevice.DeviceFriendlyName;
+ //return getCardName(RecordingDevice.DeviceFriendlyName);
+ }
+ return null;
+ }
+
+ }
+
+ public static class AudioHelper
+ {
+ /// <summary>从注册表获取音频设备ContainerID</summary>
+ /// <param name="deviceId"></param>
+ /// <param name="basepath"></param>
+ /// <returns></returns>
+ public static string GetAudioContainerID(string deviceId, string basepath = "SYSTEM\\CurrentControlSet\\Enum\\SWD\\MMDEVAPI\\")
+ {
+ string registryPath = basepath + deviceId;
+
+ using (RegistryKey deviceKey = Registry.LocalMachine.OpenSubKey(registryPath))
+ {
+ if (deviceKey == null) return null;
+
+ string containerIdStr = deviceKey.GetValue("ContainerID") as string;
+ if (!string.IsNullOrEmpty(containerIdStr))
+ {
+ return containerIdStr;
+ // Guid.TryParse(containerIdStr, out Guid containerId);
+ // return containerId;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>获取所有声卡</summary>
+ /// <returns></returns>
+ public static Dictionary<string, AudioDevice> GetAudioDevices()
+ {
+ var dic = new Dictionary<string, AudioDevice>();
+ var enumerator = new MMDeviceEnumerator();
+ var playbackDevices = enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active);
+ var recordingDevices = enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active);
+
+ foreach (var dev in playbackDevices)
+ {
+ var containerId = GetAudioContainerID(dev.ID);
+ if (containerId != null)
+ {
+ if (!dic.TryGetValue(containerId, out var audioDevice))
+ {
+ audioDevice = new AudioDevice { ContainerId = containerId };
+ dic[containerId] = audioDevice;
+ }
+ audioDevice.PlaybackDevice = dev;
+ }
+ }
+ foreach (var dev in recordingDevices)
+ {
+ var containerId = GetAudioContainerID(dev.ID);
+ if (containerId != null)
+ {
+ if (!dic.TryGetValue(containerId, out var audioDevice))
+ {
+ audioDevice = new AudioDevice { ContainerId = containerId };
+ dic[containerId] = audioDevice;
+ }
+ audioDevice.RecordingDevice = dev;
+ }
+ }
+
+ foreach (var item in dic)
+ {
+ var dev = item.Value;
+ // 尝试从ContainerID获取设备名称
+ dev.Name = dev.ContainerId;
+ var name = dev.GetCardName();
+ if (!name.IsNullOrEmpty()) dev.Name = name;
+ }
+
+ return dic;
+ }
+
+ /// <summary>从USB设备获取声卡</summary>
+ /// <param name="dev"></param>
+ /// <returns></returns>
+ public static AudioDevice GetAudioDevice(this UsbDevice dev)
+ {
+ var devs = AudioHelper.GetAudioDevices();
+ if (devs.ContainsKey(dev.ContainerId)) { return devs[dev.ContainerId]; }
+
+ return null;
+ }
+ }
+}
diff --git a/XCoder/Windows/AudioSelect.cs b/XCoder/Windows/AudioSelect.cs
new file mode 100644
index 0000000..8be9501
--- /dev/null
+++ b/XCoder/Windows/AudioSelect.cs
@@ -0,0 +1,196 @@
+
+using NAudio.CoreAudioApi;
+using NAudio.Utils;
+using NAudio.Wave;
+using NewLife;
+using NewLife.Log;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace XCoder
+{
+ [DisplayName("声卡选择器")]
+ public partial class AudioSelect : Form,IXForm
+ {
+ public AudioSelect()
+ {
+ InitializeComponent();
+ }
+
+ /// <summary>选中的声卡</summary>
+ public String SelectedDevice { get; set; } = String.Empty;
+
+ /// <summary>音量</summary>
+ public float Volume { get; set; } = 0.5f;
+
+ /// <summary>声卡列表</summary>
+ public Dictionary<String, AudioDevice> Devices { get; set; }
+
+ private void AudioSelect_Load(object sender, EventArgs e)
+ {
+ rtb_Info.MouseWheel += Rtb_Show_MouseWheel;
+
+ Devices = AudioHelper.GetAudioDevices();
+ // 如果没有声卡,直接退出
+ if (Devices == null || Devices.Count < 1)
+ {
+ WriteLog("没有找到任何声卡");
+ MessageBox.Show("没有找到任何声卡", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ this.Close();
+ return;
+ }
+
+ var cids = Devices.Select(d => d.Key).ToList();
+ cb_Dev.DataSource = cids;
+ }
+
+ private void cb_Dev_SelectedIndexChanged(object sender, EventArgs e)
+ {
+ // 清空信息
+ rtb_Info.Clear();
+ btn_ok.Enabled = false;
+ // 默认 50%
+ tb_Volume.Value = tb_Volume.Maximum / 2;
+
+ // 选择的设备ID不能为空
+ var cid = cb_Dev.SelectedItem as String;
+ if (cid.IsNullOrEmpty()) return;
+
+ // 打印设备信息
+ var dev = Devices[cid];
+ rtb_Info.Text = dev.ToString();
+
+ /*
+ // 需要有播放和录音设备,且至少是立体声,才能启用确定按钮
+ bool en = true;
+ if (dev.PlaybackDevice == null) en = false;
+ if (dev.RecordingDevice == null) en = false;
+ if (dev.PlaybackDevice?.AudioClient.MixFormat.Channels < 2) en = false;
+ if (dev.RecordingDevice?.AudioClient.MixFormat.Channels < 2) en = false;
+ btn_ok.Enabled = en;
+ */
+
+ // 如果有播放设备,启用测试按钮
+ btn_Spk.Enabled = dev.PlaybackDevice != null;
+ }
+
+ private void btn_ok_Click(object sender, EventArgs e)
+ {
+ var cid = cb_Dev.SelectedItem as String;
+
+ SelectedDevice = cid;
+ Volume = tb_Volume.Value * 0.02f;
+
+ this.Close();
+ }
+
+ private void btn_Spk_Click(object sender, EventArgs e)
+ {
+ // 选择的设备ID不能为空
+ var cid = cb_Dev.SelectedItem as String;
+ if (cid.IsNullOrEmpty()) return;
+ var dev = Devices[cid];
+
+ // 设置音量
+ dev.PlaybackDevice.AudioEndpointVolume.MasterVolumeLevelScalar = tb_Volume.Value * 0.02f;
+ // 确保未静音
+ dev.PlaybackDevice.AudioEndpointVolume.Mute = false;
+
+ // 指定声卡播放测试音频
+ // var af = new AudioFileReader(@"playTest.wav");
+ var af = new WaveFileReader(new MemoryStream(CrazyCoder.Properties.Resources.playTest));
+ // af.TotalTime;
+ var waveOut = new WasapiOut(dev.PlaybackDevice, AudioClientShareMode.Shared, false, 100);
+ waveOut.Init(af);
+ waveOut.PlaybackStopped += (s, a) =>
+ {
+ af.Dispose();
+ waveOut.Dispose();
+ this.Invoke(() => { btn_Spk.Enabled = true; });
+ };
+ btn_Spk.Enabled = false;
+ waveOut.Play();
+ }
+
+ protected override void WndProc(ref Message m)
+ {
+ const int WM_MOUSEWHEEL = 0x020A;
+
+ if (m.Msg == WM_MOUSEWHEEL)
+ {
+ // 在这里处理你的全局滚轮事件
+ short delta = 0;
+ if (IntPtr.Size == 8)
+ {
+ // 64位系统
+ delta = (short)(m.WParam.ToInt64() >> 16);
+ }
+ else
+ {
+ // 32位系统
+ delta = (short)(m.WParam.ToInt32() >> 16);
+ }
+
+ // int delta = (short)(m.WParam.ToInt32() >> 16);
+ // Console.WriteLine("Mouse wheel moved: " + delta);
+
+ if (delta > 0)
+ {
+ // 向上滚动
+ if (cb_Dev.SelectedIndex > 0)
+ {
+ cb_Dev.SelectedIndex--;
+ }
+ }
+ else if (delta < 0)
+ {
+ // 向下滚动
+ if (cb_Dev.SelectedIndex < cb_Dev.Items.Count - 1)
+ {
+ cb_Dev.SelectedIndex++;
+ }
+ }
+
+ // 如果你想阻止事件传递给其他控件,可以在这里返回
+ return;
+ }
+
+ base.WndProc(ref m);
+ }
+
+ private void Rtb_Show_MouseWheel(object sender, MouseEventArgs e)
+ {
+ if (e.Delta > 0)
+ {
+ // 向上滚动
+ if (cb_Dev.SelectedIndex > 0)
+ {
+ cb_Dev.SelectedIndex--;
+ }
+ }
+ else if (e.Delta < 0)
+ {
+ // 向下滚动
+ if (cb_Dev.SelectedIndex < cb_Dev.Items.Count - 1)
+ {
+ cb_Dev.SelectedIndex++;
+ }
+ }
+ }
+
+ #region 日志
+
+ public ILog Log { get; set; } = Logger.Null;
+
+ public void WriteLog(String format, params Object[] args) => Log?.Info(format, args);
+
+ #endregion
+ }
+}
diff --git a/XCoder/Windows/AudioSelect.Designer.cs b/XCoder/Windows/AudioSelect.Designer.cs
new file mode 100644
index 0000000..ed3b0b6
--- /dev/null
+++ b/XCoder/Windows/AudioSelect.Designer.cs
@@ -0,0 +1,142 @@
+namespace XCoder
+{
+ partial class AudioSelect
+ {
+ /// <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()
+ {
+ cb_Dev = new ComboBox();
+ label1 = new Label();
+ btn_Spk = new Button();
+ btn_ok = new Button();
+ rtb_Info = new RichTextBox();
+ tb_Volume = new TrackBar();
+ lb_Volume = new Label();
+ ((System.ComponentModel.ISupportInitialize)tb_Volume).BeginInit();
+ SuspendLayout();
+ //
+ // cb_Dev
+ //
+ cb_Dev.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
+ cb_Dev.FormattingEnabled = true;
+ cb_Dev.Location = new Point(109, 38);
+ cb_Dev.Name = "cb_Dev";
+ cb_Dev.Size = new Size(374, 25);
+ cb_Dev.TabIndex = 0;
+ cb_Dev.SelectedIndexChanged += cb_Dev_SelectedIndexChanged;
+ //
+ // label1
+ //
+ label1.AutoSize = true;
+ label1.Location = new Point(57, 41);
+ label1.Name = "label1";
+ label1.Size = new Size(46, 17);
+ label1.TabIndex = 1;
+ label1.Text = "Device";
+ //
+ // btn_Spk
+ //
+ btn_Spk.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
+ btn_Spk.Location = new Point(57, 254);
+ btn_Spk.Name = "btn_Spk";
+ btn_Spk.Size = new Size(91, 34);
+ btn_Spk.TabIndex = 2;
+ btn_Spk.Text = "TEST";
+ btn_Spk.UseVisualStyleBackColor = true;
+ btn_Spk.Click += btn_Spk_Click;
+ //
+ // btn_ok
+ //
+ btn_ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
+ btn_ok.Location = new Point(392, 254);
+ btn_ok.Name = "btn_ok";
+ btn_ok.Size = new Size(91, 34);
+ btn_ok.TabIndex = 3;
+ btn_ok.Text = "OK";
+ btn_ok.UseVisualStyleBackColor = true;
+ btn_ok.Click += btn_ok_Click;
+ //
+ // rtb_Info
+ //
+ rtb_Info.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
+ rtb_Info.Location = new Point(57, 72);
+ rtb_Info.Name = "rtb_Info";
+ rtb_Info.ReadOnly = true;
+ rtb_Info.Size = new Size(426, 125);
+ rtb_Info.TabIndex = 4;
+ rtb_Info.Text = "";
+ //
+ // tb_Volume
+ //
+ tb_Volume.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
+ tb_Volume.Location = new Point(109, 203);
+ tb_Volume.Maximum = 50;
+ tb_Volume.Name = "tb_Volume";
+ tb_Volume.Size = new Size(374, 45);
+ tb_Volume.SmallChange = 5;
+ tb_Volume.TabIndex = 5;
+ //
+ // lb_Volume
+ //
+ lb_Volume.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
+ lb_Volume.AutoSize = true;
+ lb_Volume.Location = new Point(57, 212);
+ lb_Volume.Name = "lb_Volume";
+ lb_Volume.Size = new Size(52, 17);
+ lb_Volume.TabIndex = 6;
+ lb_Volume.Text = "Volume";
+ //
+ // AudioSelect
+ //
+ AutoScaleDimensions = new SizeF(7F, 17F);
+ AutoScaleMode = AutoScaleMode.Font;
+ ClientSize = new Size(545, 311);
+ Controls.Add(lb_Volume);
+ Controls.Add(tb_Volume);
+ Controls.Add(rtb_Info);
+ Controls.Add(btn_ok);
+ Controls.Add(btn_Spk);
+ Controls.Add(label1);
+ Controls.Add(cb_Dev);
+ Name = "AudioSelect";
+ Text = "AudioSelect";
+ Load += AudioSelect_Load;
+ ((System.ComponentModel.ISupportInitialize)tb_Volume).EndInit();
+ ResumeLayout(false);
+ PerformLayout();
+ }
+
+ #endregion
+
+ private ComboBox cb_Dev;
+ private Label label1;
+ private Button btn_Spk;
+ private Button btn_ok;
+ private RichTextBox rtb_Info;
+ private TrackBar tb_Volume;
+ private Label lb_Volume;
+ }
+}
\ No newline at end of file
diff --git a/XCoder/Windows/AudioSelect.resx b/XCoder/Windows/AudioSelect.resx
new file mode 100644
index 0000000..8b2ff64
--- /dev/null
+++ b/XCoder/Windows/AudioSelect.resx
@@ -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=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root>
\ No newline at end of file
diff --git a/XCoder/Windows/playTest.wav b/XCoder/Windows/playTest.wav
new file mode 100644
index 0000000..fb80ced
Binary files /dev/null and b/XCoder/Windows/playTest.wav differ