NewLife/XCoder

声卡选择
JiuHuan authored at 2025-07-24 13:24:07
2915bf5
Tree
1 Parent(s) 3c6d5ef
Summary: 7 changed files with 659 additions and 0 deletions.
Modified +10 -0
Modified +4 -0
Added +187 -0
Added +196 -0
Added +142 -0
Added +120 -0
Added +0 -0
Modified +10 -0
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));
+            }
+        }
     }
 }
Modified +4 -0
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
Added +187 -0
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;
+        }
+    }
+}
Added +196 -0
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
+    }
+}
Added +142 -0
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
Added +120 -0
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
Added +0 -0
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