diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..d922db6c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +# runs on mono, so we can't build the Win 8 or Universal projects +# docs here: https://docs.travis-ci.com/user/languages/csharp/ +language: csharp +solution: NAudio.sln +script: + - xbuild /p:Configuration=Release NAudio.sln + - mono ./packages/NUnit.*/tools/nunit-console.exe /exclude=IntegrationTest ./NAudioTests/bin/Release/NAudioTests.dll diff --git a/MidiFileConverter/AboutForm.Designer.cs b/MidiFileConverter/AboutForm.Designer.cs index bd8cd680..d9ff6b1e 100644 --- a/MidiFileConverter/AboutForm.Designer.cs +++ b/MidiFileConverter/AboutForm.Designer.cs @@ -75,14 +75,14 @@ private void InitializeComponent() this.labelCopyright.Name = "labelCopyright"; this.labelCopyright.Size = new System.Drawing.Size(149, 13); this.labelCopyright.TabIndex = 2; - this.labelCopyright.Text = "Copyright © Mark Heath 2007"; + this.labelCopyright.Text = "Copyright © Mark Heath 2016"; // // linkLabelFeedback // this.linkLabelFeedback.AutoSize = true; this.linkLabelFeedback.Location = new System.Drawing.Point(13, 101); this.linkLabelFeedback.Name = "linkLabelFeedback"; - this.linkLabelFeedback.Size = new System.Drawing.Size(150, 13); + this.linkLabelFeedback.Size = new System.Drawing.Size(118, 13); this.linkLabelFeedback.TabIndex = 3; this.linkLabelFeedback.TabStop = true; this.linkLabelFeedback.Text = "mark.heath@gmail.com"; diff --git a/MidiFileConverter/MidiConverter.cs b/MidiFileConverter/MidiConverter.cs index 686cbb53..a7e12881 100644 --- a/MidiFileConverter/MidiConverter.cs +++ b/MidiFileConverter/MidiConverter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using NAudio.Utils; using NAudio.Midi; @@ -191,7 +192,7 @@ private void ConvertMidi(MidiFile midiFile, string target, string[] context) if (midiFile.FileFormat == 0) outputTrackCount = 2; else - outputTrackCount = midiFile.Tracks; + outputTrackCount = Math.Max(midiFile.Tracks,2); // at least two tracks because we'll move notes onto track 1 always } @@ -327,11 +328,8 @@ private void ConvertMidi(MidiFile midiFile, string target, string[] context) { if (outputFileType == 1) { - // if we are converting type 0 to type 1 and recreating end markers, - if (midiFile.FileFormat == 0) - { - AppendEndMarker(events[1]); - } + // make sure track 1 has an end track marker + AppendEndMarker(events[1]); } // make sure that track zero has an end track marker AppendEndMarker(events[0]); @@ -397,20 +395,24 @@ private void ConvertMidi(MidiFile midiFile, string target, string[] context) private bool HasNotes(IList midiEvents) { - foreach (MidiEvent midiEvent in midiEvents) - { - if (midiEvent.CommandCode == MidiCommandCode.NoteOn) - return true; - } - return false; + return midiEvents.Any(midiEvent => midiEvent.CommandCode == MidiCommandCode.NoteOn); + } + + private bool IsEndTrack(MidiEvent midiEvent) + { + var meta = midiEvent as MetaEvent; + return meta?.MetaEventType == MetaEventType.EndTrack; } private void AppendEndMarker(IList eventList) { long absoluteTime = 0; + if (eventList.Count > 0) absoluteTime = eventList[eventList.Count - 1].AbsoluteTime; - eventList.Add(new MetaEvent(MetaEventType.EndTrack, 0, absoluteTime)); + + if (!IsEndTrack(eventList.LastOrDefault())) + eventList.Add(new MetaEvent(MetaEventType.EndTrack, 0, absoluteTime)); } private string CreateEzdName(string[] context) diff --git a/MidiFileConverter/Properties/AssemblyInfo.cs b/MidiFileConverter/Properties/AssemblyInfo.cs index 1452e8bb..6d1ab984 100644 --- a/MidiFileConverter/Properties/AssemblyInfo.cs +++ b/MidiFileConverter/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Mark Heath")] [assembly: AssemblyProduct("MIDI File Converter")] -[assembly: AssemblyCopyright("Copyright © Mark Heath 2007")] +[assembly: AssemblyCopyright("Copyright © Mark Heath 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -29,8 +29,8 @@ // Build Number // Revision // -[assembly: AssemblyVersion("0.3.10.0")] -[assembly: AssemblyFileVersion("0.3.10.0")] +[assembly: AssemblyVersion("0.3.11.0")] +[assembly: AssemblyFileVersion("0.3.11.0")] // build 1 29 Oct 2006 // build 1 is experimental @@ -73,6 +73,8 @@ // and converting from type 0 to type 1 // build 10 5 Apr 2007 // updated to use new MidiEventCollection +// build 11 12 Feb 2016 +// ensuring end track markers persent converting type 1 to 1 // revamp help for advanced options diff --git a/NAudio.Universal/CoreAudioApi/PropVariantNative.cs b/NAudio.Universal/CoreAudioApi/PropVariantNative.cs new file mode 100644 index 00000000..d2202ef8 --- /dev/null +++ b/NAudio.Universal/CoreAudioApi/PropVariantNative.cs @@ -0,0 +1,16 @@ +using System; +using System.Runtime.InteropServices; + +namespace NAudio.CoreAudioApi.Interfaces +{ + class PropVariantNative + { + // Windows 10 requires api-ms-win-core-com-l1-1-1.dll + [DllImport("api-ms-win-core-com-l1-1-1.dll")] + internal static extern int PropVariantClear(ref PropVariant pvar); + + [DllImport("api-ms-win-core-com-l1-1-1.dll")] + internal static extern int PropVariantClear(IntPtr pvar); + + } +} diff --git a/NAudio.Universal/NAudio.Universal.csproj b/NAudio.Universal/NAudio.Universal.csproj index 38fdfaa1..95a543fd 100644 --- a/NAudio.Universal/NAudio.Universal.csproj +++ b/NAudio.Universal/NAudio.Universal.csproj @@ -825,7 +825,9 @@ Wave\WaveStreams\WaveStream.cs + + diff --git a/NAudio.Win8/NAudio.Win8.csproj b/NAudio.Win8/NAudio.Win8.csproj index 448d290e..d11141b2 100644 --- a/NAudio.Win8/NAudio.Win8.csproj +++ b/NAudio.Win8/NAudio.Win8.csproj @@ -310,6 +310,9 @@ CoreAudioApi\PropVariant.cs + + CoreAudioApi\PropVariantNative.cs + CoreAudioApi\Role.cs @@ -586,6 +589,9 @@ Utils\IgnoreDisposeStream.cs + + Utils\MarshalHelpers.cs + Utils\MergeSort.cs diff --git a/NAudio.Win8/Wave/WaveOutputs/WasapiOutRT.cs b/NAudio.Win8/Wave/WaveOutputs/WasapiOutRT.cs index e7c98af2..afe89868 100644 --- a/NAudio.Win8/Wave/WaveOutputs/WasapiOutRT.cs +++ b/NAudio.Win8/Wave/WaveOutputs/WasapiOutRT.cs @@ -10,6 +10,7 @@ using NAudio.Dsp; using NAudio.Wave; using Windows.Media.Devices; +using NAudio.Utils; using NAudio.Wave.SampleProviders; namespace NAudio.Win8.Wave.WaveOutputs @@ -95,7 +96,7 @@ public void SetClientProperties(bool useHardwareOffload, AudioStreamCategory cat { audioClientProperties = new AudioClientProperties() { - cbSize = (uint) Marshal.SizeOf(), + cbSize = (uint) MarshalHelpers.SizeOf(), bIsOffload = Convert.ToInt32(useHardwareOffload), eCategory = category, Options = options diff --git a/NAudio.sln b/NAudio.sln index 84b574a6..c1313afd 100644 --- a/NAudio.sln +++ b/NAudio.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAudio", "NAudio\NAudio.csproj", "{DA4F02E3-0B5E-42CD-B8D9-5583FA51D66E}" EndProject diff --git a/NAudio/CoreAudioApi/AudioEndpointVolume.cs b/NAudio/CoreAudioApi/AudioEndpointVolume.cs index 748e98a1..2060424c 100644 --- a/NAudio/CoreAudioApi/AudioEndpointVolume.cs +++ b/NAudio/CoreAudioApi/AudioEndpointVolume.cs @@ -38,6 +38,22 @@ public class AudioEndpointVolume : IDisposable private readonly EEndpointHardwareSupport hardwareSupport; private AudioEndpointVolumeCallback callBack; + private Guid notificationGuid = Guid.Empty; + + /// + /// GUID to pass to AudioEndpointVolumeCallback + /// + public Guid NotificationGuid { + get + { + return notificationGuid; + } + set + { + notificationGuid = value; + } + } + /// /// On Volume Notification /// @@ -100,7 +116,7 @@ public float MasterVolumeLevel } set { - Marshal.ThrowExceptionForHR(audioEndPointVolume.SetMasterVolumeLevel(value, Guid.Empty)); + Marshal.ThrowExceptionForHR(audioEndPointVolume.SetMasterVolumeLevel(value, ref notificationGuid)); } } @@ -117,7 +133,7 @@ public float MasterVolumeLevelScalar } set { - Marshal.ThrowExceptionForHR(audioEndPointVolume.SetMasterVolumeLevelScalar(value, Guid.Empty)); + Marshal.ThrowExceptionForHR(audioEndPointVolume.SetMasterVolumeLevelScalar(value, ref notificationGuid)); } } @@ -134,7 +150,7 @@ public bool Mute } set { - Marshal.ThrowExceptionForHR(audioEndPointVolume.SetMute(value, Guid.Empty)); + Marshal.ThrowExceptionForHR(audioEndPointVolume.SetMute(value, ref notificationGuid)); } } @@ -143,7 +159,7 @@ public bool Mute /// public void VolumeStepUp() { - Marshal.ThrowExceptionForHR(audioEndPointVolume.VolumeStepUp(Guid.Empty)); + Marshal.ThrowExceptionForHR(audioEndPointVolume.VolumeStepUp(ref notificationGuid)); } /// @@ -151,7 +167,7 @@ public void VolumeStepUp() /// public void VolumeStepDown() { - Marshal.ThrowExceptionForHR(audioEndPointVolume.VolumeStepDown(Guid.Empty)); + Marshal.ThrowExceptionForHR(audioEndPointVolume.VolumeStepDown(ref notificationGuid)); } /// diff --git a/NAudio/CoreAudioApi/AudioEndpointVolumeCallback.cs b/NAudio/CoreAudioApi/AudioEndpointVolumeCallback.cs index f740ad22..26518382 100644 --- a/NAudio/CoreAudioApi/AudioEndpointVolumeCallback.cs +++ b/NAudio/CoreAudioApi/AudioEndpointVolumeCallback.cs @@ -67,7 +67,7 @@ public void OnNotify(IntPtr notifyData) } //Create combined structure and Fire Event in parent class. - var notificationData = new AudioVolumeNotificationData(data.guidEventContext, data.bMuted, data.fMasterVolume, voldata); + var notificationData = new AudioVolumeNotificationData(data.guidEventContext, data.bMuted, data.fMasterVolume, voldata, data.guidEventContext); parent.FireNotification(notificationData); } } diff --git a/NAudio/CoreAudioApi/AudioEndpointVolumeChannel.cs b/NAudio/CoreAudioApi/AudioEndpointVolumeChannel.cs index 5d738c0f..e8b75347 100644 --- a/NAudio/CoreAudioApi/AudioEndpointVolumeChannel.cs +++ b/NAudio/CoreAudioApi/AudioEndpointVolumeChannel.cs @@ -34,6 +34,23 @@ public class AudioEndpointVolumeChannel private readonly uint channel; private readonly IAudioEndpointVolume audioEndpointVolume; + private Guid notificationGuid = Guid.Empty; + + /// + /// GUID to pass to AudioEndpointVolumeCallback + /// + public Guid NotificationGuid + { + get + { + return notificationGuid; + } + set + { + notificationGuid = value; + } + } + internal AudioEndpointVolumeChannel(IAudioEndpointVolume parent, int channel) { this.channel = (uint)channel; @@ -53,7 +70,7 @@ public float VolumeLevel } set { - Marshal.ThrowExceptionForHR(audioEndpointVolume.SetChannelVolumeLevel(channel, value,Guid.Empty)); + Marshal.ThrowExceptionForHR(audioEndpointVolume.SetChannelVolumeLevel(channel, value, ref this.notificationGuid)); } } @@ -70,7 +87,7 @@ public float VolumeLevelScalar } set { - Marshal.ThrowExceptionForHR(audioEndpointVolume.SetChannelVolumeLevelScalar(channel, value, Guid.Empty)); + Marshal.ThrowExceptionForHR(audioEndpointVolume.SetChannelVolumeLevelScalar(channel, value, ref this.notificationGuid)); } } diff --git a/NAudio/CoreAudioApi/AudioSessionControl.cs b/NAudio/CoreAudioApi/AudioSessionControl.cs index 9c4b85c9..e6929b0e 100644 --- a/NAudio/CoreAudioApi/AudioSessionControl.cs +++ b/NAudio/CoreAudioApi/AudioSessionControl.cs @@ -48,6 +48,7 @@ public void Dispose() if (audioSessionEventCallback != null) { Marshal.ThrowExceptionForHR(audioSessionControlInterface.UnregisterAudioSessionNotification(audioSessionEventCallback)); + audioSessionEventCallback = null; } GC.SuppressFinalize(this); } @@ -241,6 +242,7 @@ public void UnRegisterEventClient(IAudioSessionEventsHandler eventClient) if (audioSessionEventCallback != null) { Marshal.ThrowExceptionForHR(audioSessionControlInterface.UnregisterAudioSessionNotification(audioSessionEventCallback)); + audioSessionEventCallback = null; } } } diff --git a/NAudio/CoreAudioApi/AudioVolumeNotificationData.cs b/NAudio/CoreAudioApi/AudioVolumeNotificationData.cs index bbfc363a..33a04dae 100644 --- a/NAudio/CoreAudioApi/AudioVolumeNotificationData.cs +++ b/NAudio/CoreAudioApi/AudioVolumeNotificationData.cs @@ -33,6 +33,7 @@ public class AudioVolumeNotificationData private readonly float masterVolume; private readonly int channels; private readonly float[] channelVolume; + private readonly Guid guid; /// /// Event Context @@ -50,6 +51,14 @@ public bool Muted get { return muted; } } + /// + /// Guid that raised the event + /// + public Guid Guid + { + get { return guid; } + } + /// /// Master Volume /// @@ -81,13 +90,15 @@ public float[] ChannelVolume /// /// /// - public AudioVolumeNotificationData(Guid eventContext, bool muted, float masterVolume, float[] channelVolume) + /// + public AudioVolumeNotificationData(Guid eventContext, bool muted, float masterVolume, float[] channelVolume, Guid guid) { this.eventContext = eventContext; this.muted = muted; this.masterVolume = masterVolume; channels = channelVolume.Length; this.channelVolume = channelVolume; + this.guid = guid; } } } diff --git a/NAudio/CoreAudioApi/Interfaces/IAudioEndpointVolume.cs b/NAudio/CoreAudioApi/Interfaces/IAudioEndpointVolume.cs index 8f0104fb..2e4b9d01 100644 --- a/NAudio/CoreAudioApi/Interfaces/IAudioEndpointVolume.cs +++ b/NAudio/CoreAudioApi/Interfaces/IAudioEndpointVolume.cs @@ -33,19 +33,19 @@ internal interface IAudioEndpointVolume int RegisterControlChangeNotify(IAudioEndpointVolumeCallback pNotify); int UnregisterControlChangeNotify(IAudioEndpointVolumeCallback pNotify); int GetChannelCount(out int pnChannelCount); - int SetMasterVolumeLevel(float fLevelDB, Guid pguidEventContext); - int SetMasterVolumeLevelScalar(float fLevel, Guid pguidEventContext); + int SetMasterVolumeLevel(float fLevelDB, ref Guid pguidEventContext); + int SetMasterVolumeLevelScalar(float fLevel, ref Guid pguidEventContext); int GetMasterVolumeLevel(out float pfLevelDB); int GetMasterVolumeLevelScalar(out float pfLevel); - int SetChannelVolumeLevel(uint nChannel, float fLevelDB, Guid pguidEventContext); - int SetChannelVolumeLevelScalar(uint nChannel, float fLevel, Guid pguidEventContext); + int SetChannelVolumeLevel(uint nChannel, float fLevelDB, ref Guid pguidEventContext); + int SetChannelVolumeLevelScalar(uint nChannel, float fLevel, ref Guid pguidEventContext); int GetChannelVolumeLevel(uint nChannel, out float pfLevelDB); int GetChannelVolumeLevelScalar(uint nChannel, out float pfLevel); - int SetMute([MarshalAs(UnmanagedType.Bool)] Boolean bMute, Guid pguidEventContext); + int SetMute([MarshalAs(UnmanagedType.Bool)] Boolean bMute, ref Guid pguidEventContext); int GetMute(out bool pbMute); int GetVolumeStepInfo(out uint pnStep, out uint pnStepCount); - int VolumeStepUp(Guid pguidEventContext); - int VolumeStepDown(Guid pguidEventContext); + int VolumeStepUp(ref Guid pguidEventContext); + int VolumeStepDown(ref Guid pguidEventContext); int QueryHardwareSupport(out uint pdwHardwareSupportMask); int GetVolumeRange(out float pflVolumeMindB, out float pflVolumeMaxdB, out float pflVolumeIncrementdB); } diff --git a/NAudio/CoreAudioApi/PropVariant.cs b/NAudio/CoreAudioApi/PropVariant.cs index b14c5c63..70232a44 100644 --- a/NAudio/CoreAudioApi/PropVariant.cs +++ b/NAudio/CoreAudioApi/PropVariant.cs @@ -214,10 +214,7 @@ public object Value [Obsolete("Call with pointer instead")] public void Clear() { - var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(this)); - Marshal.StructureToPtr(this, ptr, false); - PropVariantClear(ptr); - Marshal.FreeHGlobal(ptr); + PropVariantNative.PropVariantClear(ref this); } /// @@ -225,10 +222,7 @@ public void Clear() /// public static void Clear(IntPtr ptr) { - PropVariantClear(ptr); + PropVariantNative.PropVariantClear(ptr); } - - [DllImport("ole32.dll")] - private static extern int PropVariantClear(IntPtr pvar); } } diff --git a/NAudio/CoreAudioApi/PropVariantNative.cs b/NAudio/CoreAudioApi/PropVariantNative.cs new file mode 100644 index 00000000..6e6b8103 --- /dev/null +++ b/NAudio/CoreAudioApi/PropVariantNative.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.InteropServices; + +namespace NAudio.CoreAudioApi.Interfaces +{ + class PropVariantNative + { + [DllImport("ole32.dll")] + internal static extern int PropVariantClear(ref PropVariant pvar); + + [DllImport("ole32.dll")] + internal static extern int PropVariantClear(IntPtr pvar); + } +} diff --git a/NAudio/Midi/PitchWheelChangeEvent.cs b/NAudio/Midi/PitchWheelChangeEvent.cs index 4e4dfb14..1570e034 100644 --- a/NAudio/Midi/PitchWheelChangeEvent.cs +++ b/NAudio/Midi/PitchWheelChangeEvent.cs @@ -57,7 +57,7 @@ public override string ToString() } /// - /// Pitch Wheel Value 0 is minimum, 0x2000 (8192) is default, 0x4000 (16384) is maximum + /// Pitch Wheel Value 0 is minimum, 0x2000 (8192) is default, 0x3FFF (16383) is maximum /// public int Pitch { @@ -67,9 +67,9 @@ public int Pitch } set { - if (value < 0 || value > 0x4000) + if (value < 0 || value >= 0x4000) { - throw new ArgumentOutOfRangeException("value", "Pitch value must be in the range 0 - 0x4000"); + throw new ArgumentOutOfRangeException("value", "Pitch value must be in the range 0 - 0x3FFF"); } pitch = value; } diff --git a/NAudio/Midi/TimeSignatureEvent.cs b/NAudio/Midi/TimeSignatureEvent.cs index c3f78d81..3a530ced 100644 --- a/NAudio/Midi/TimeSignatureEvent.cs +++ b/NAudio/Midi/TimeSignatureEvent.cs @@ -49,19 +49,6 @@ public TimeSignatureEvent(long absoluteTime, int numerator, int denominator, int this.no32ndNotesInQuarterNote = (byte)no32ndNotesInQuarterNote; } - /// - /// Creates a new time signature event with the specified parameters - /// - [Obsolete("Use the constructor that has absolute time first")] - public TimeSignatureEvent(int numerator, int denominator, int ticksInMetronomeClick, int no32ndNotesInQuarterNote, long absoluteTime) - : base(MetaEventType.TimeSignature, 4, absoluteTime) - { - this.numerator = (byte) numerator; - this.denominator = (byte) denominator; - this.ticksInMetronomeClick = (byte) ticksInMetronomeClick; - this.no32ndNotesInQuarterNote = (byte) no32ndNotesInQuarterNote; - } - /// /// Numerator (number of beats in a bar) /// diff --git a/NAudio/Mixer/MixerSource.cs b/NAudio/Mixer/MixerSource.cs deleted file mode 100644 index 83c46d3f..00000000 --- a/NAudio/Mixer/MixerSource.cs +++ /dev/null @@ -1,131 +0,0 @@ -// created on 10/12/2002 at 21:00 -using System; -using System.Runtime.InteropServices; -using NAudio.Wave; - -namespace NAudio.Mixer -{ - /// - /// Represents a Mixer source - /// - public class MixerSource - { - private MixerInterop.MIXERLINE mixerLine; - private IntPtr mixerHandle; - private int nDestination; - private int nSource; - - /// - /// Creates a new Mixer Source - /// - /// Mixer ID - /// Destination ID - /// Source ID - public MixerSource(IntPtr mixerHandle, int nDestination, int nSource) - { - mixerLine = new MixerInterop.MIXERLINE(); - mixerLine.cbStruct = Marshal.SizeOf(mixerLine); - mixerLine.dwDestination = nDestination; - mixerLine.dwSource = nSource; - MmException.Try(MixerInterop.mixerGetLineInfo(mixerHandle, ref mixerLine, MixerInterop.MIXER_GETLINEINFOF_SOURCE), "mixerGetLineInfo"); - this.mixerHandle = mixerHandle; - this.nDestination = nDestination; - this.nSource = nSource; - } - - /// - /// Source Name - /// - public String Name - { - get - { - return mixerLine.szName; - } - } - - /// - /// Source short name - /// - public String ShortName - { - get - { - return mixerLine.szShortName; - } - } - - /// - /// Number of controls - /// - public int ControlsCount - { - get - { - return mixerLine.cControls; - } - } - - /// - /// Retrieves the specified control - /// - /// - /// - public MixerControl GetControl(int nControl) - { - if(nControl < 0 || nControl >= ControlsCount) - { - throw new ArgumentOutOfRangeException("nControl"); - } - return MixerControl.GetMixerControl(mixerHandle, (int)mixerLine.dwLineID, nControl, Channels); - } - - /// - /// Number of channels - /// - public int Channels - { - get - { - return mixerLine.cChannels; - } - } - - /// - /// Source type description - /// - public String TypeDescription - { - get - { - switch (mixerLine.dwComponentType) - { - case MixerInterop.MIXERLINE_COMPONENTTYPE.MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED: - return "Undefined"; - case MixerInterop.MIXERLINE_COMPONENTTYPE.MIXERLINE_COMPONENTTYPE_SRC_DIGITAL: - return "Digital"; - case MixerInterop.MIXERLINE_COMPONENTTYPE.MIXERLINE_COMPONENTTYPE_SRC_LINE: - return "Line Level"; - case MixerInterop.MIXERLINE_COMPONENTTYPE.MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE: - return "Microphone"; - case MixerInterop.MIXERLINE_COMPONENTTYPE.MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER: - return "Synthesizer"; - case MixerInterop.MIXERLINE_COMPONENTTYPE.MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC: - return "Compact Disk"; - case MixerInterop.MIXERLINE_COMPONENTTYPE.MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE: - return "Telephone"; - case MixerInterop.MIXERLINE_COMPONENTTYPE.MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER: - return "PC Speaker"; - case MixerInterop.MIXERLINE_COMPONENTTYPE.MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT: - return "Wave Out"; - case MixerInterop.MIXERLINE_COMPONENTTYPE.MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY: - return "Auxiliary"; - case MixerInterop.MIXERLINE_COMPONENTTYPE.MIXERLINE_COMPONENTTYPE_SRC_ANALOG: - return "Analog"; - default: - return "Invalid"; - } - } - } - } -} diff --git a/NAudio/NAudio.csproj b/NAudio/NAudio.csproj index db55a41e..a47f8dd4 100644 --- a/NAudio/NAudio.csproj +++ b/NAudio/NAudio.csproj @@ -119,6 +119,7 @@ + @@ -452,6 +453,7 @@ + @@ -481,6 +483,7 @@ + diff --git a/NAudio/Utils/CircularBuffer.cs b/NAudio/Utils/CircularBuffer.cs index c0e2fdbd..fb4b8d09 100644 --- a/NAudio/Utils/CircularBuffer.cs +++ b/NAudio/Utils/CircularBuffer.cs @@ -109,13 +109,27 @@ public int MaxLength /// public int Count { - get { return byteCount; } + get + { + lock (lockObject) + { + return byteCount; + } + } } /// /// Resets the buffer /// public void Reset() + { + lock (lockObject) + { + ResetInner(); + } + } + + private void ResetInner() { byteCount = 0; readPosition = 0; @@ -128,15 +142,18 @@ public void Reset() /// Bytes to advance public void Advance(int count) { - if (count >= byteCount) - { - Reset(); - } - else + lock (lockObject) { - byteCount -= count; - readPosition += count; - readPosition %= MaxLength; + if (count >= byteCount) + { + ResetInner(); + } + else + { + byteCount -= count; + readPosition += count; + readPosition %= MaxLength; + } } } } diff --git a/NAudio/Wave/SampleProviders/OffsetSampleProvider.cs b/NAudio/Wave/SampleProviders/OffsetSampleProvider.cs index d97189a3..753d3a0f 100644 --- a/NAudio/Wave/SampleProviders/OffsetSampleProvider.cs +++ b/NAudio/Wave/SampleProviders/OffsetSampleProvider.cs @@ -221,12 +221,12 @@ public int Read(float[] buffer, int offset, int count) if (phase == 3) // take { int samplesRequired = count - samplesRead; - if (TakeSamples != 0) - samplesRequired = Math.Min(samplesRequired, TakeSamples - phasePos); + if (takeSamples != 0) + samplesRequired = Math.Min(samplesRequired, takeSamples - phasePos); int read = sourceProvider.Read(buffer, offset + samplesRead, samplesRequired); phasePos += read; samplesRead += read; - if (read < samplesRequired) + if (read < samplesRequired || (takeSamples > 0 && phasePos >= takeSamples)) { phase++; phasePos = 0; diff --git a/NAudio/Wave/WaveExtensionMethods.cs b/NAudio/Wave/WaveExtensionMethods.cs index b3ac5f9b..fd086d67 100644 --- a/NAudio/Wave/WaveExtensionMethods.cs +++ b/NAudio/Wave/WaveExtensionMethods.cs @@ -97,6 +97,17 @@ public static ISampleProvider Skip(this ISampleProvider sampleProvider, TimeSpan return new OffsetSampleProvider(sampleProvider) { SkipOver = skipDuration}; } + /// + /// Takes a specified amount of time from the source stream + /// + /// Source sample provider + /// Duration to take + /// A sample provider that reads up to the specified amount of time + public static ISampleProvider Take(this ISampleProvider sampleProvider, TimeSpan takeDuration) + { + return new OffsetSampleProvider(sampleProvider) { Take = takeDuration }; + } + /// /// Converts a Stereo Sample Provider to mono, allowing mixing of channel volume /// diff --git a/NAudio/Wave/WaveFormats/Gsm610WaveFormat.cs b/NAudio/Wave/WaveFormats/Gsm610WaveFormat.cs index 2321f740..47ea3258 100644 --- a/NAudio/Wave/WaveFormats/Gsm610WaveFormat.cs +++ b/NAudio/Wave/WaveFormats/Gsm610WaveFormat.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Runtime.InteropServices; using System.IO; +// ReSharper disable once CheckNamespace namespace NAudio.Wave { /// @@ -12,7 +11,7 @@ namespace NAudio.Wave [StructLayout(LayoutKind.Sequential, Pack = 2)] public class Gsm610WaveFormat : WaveFormat { - private short samplesPerBlock; + private readonly short samplesPerBlock; /// /// Creates a GSM 610 WaveFormat @@ -20,21 +19,21 @@ public class Gsm610WaveFormat : WaveFormat /// public Gsm610WaveFormat() { - this.waveFormatTag = WaveFormatEncoding.Gsm610; - this.channels = 1; - this.averageBytesPerSecond = 1625; - this.bitsPerSample = 0; // must be zero - this.blockAlign = 65; - this.sampleRate = 8000; + waveFormatTag = WaveFormatEncoding.Gsm610; + channels = 1; + averageBytesPerSecond = 1625; + bitsPerSample = 0; // must be zero + blockAlign = 65; + sampleRate = 8000; - this.extraSize = 2; - this.samplesPerBlock = 320; + extraSize = 2; + samplesPerBlock = 320; } /// /// Samples per block /// - public short SamplesPerBlock { get { return this.samplesPerBlock; } } + public short SamplesPerBlock { get { return samplesPerBlock; } } /// /// Writes this structure to a BinaryWriter diff --git a/NAudio/Wave/WaveInputs/WasapiCapture.cs b/NAudio/Wave/WaveInputs/WasapiCapture.cs index 16bb3573..888bc8fb 100644 --- a/NAudio/Wave/WaveInputs/WasapiCapture.cs +++ b/NAudio/Wave/WaveInputs/WasapiCapture.cs @@ -161,13 +161,13 @@ private void InitializeCaptureDevice() if (ShareMode == AudioClientShareMode.Shared) { // With EventCallBack and Shared, both latencies must be set to 0 - audioClient.Initialize(ShareMode, AudioClientStreamFlags.EventCallback, requestedDuration, 0, + audioClient.Initialize(ShareMode, AudioClientStreamFlags.EventCallback | streamFlags, requestedDuration, 0, waveFormat, Guid.Empty); } else { // With EventCallBack and Exclusive, both latencies must equals - audioClient.Initialize(ShareMode, AudioClientStreamFlags.EventCallback, requestedDuration, requestedDuration, + audioClient.Initialize(ShareMode, AudioClientStreamFlags.EventCallback | streamFlags, requestedDuration, requestedDuration, waveFormat, Guid.Empty); } diff --git a/NAudio/Wave/WaveOutputs/WaveOut.cs b/NAudio/Wave/WaveOutputs/WaveOut.cs index b139ec1d..fffb3f6b 100644 --- a/NAudio/Wave/WaveOutputs/WaveOut.cs +++ b/NAudio/Wave/WaveOutputs/WaveOut.cs @@ -183,6 +183,7 @@ public void Pause() if (playbackState == PlaybackState.Playing) { MmResult result; + playbackState = PlaybackState.Paused; // set this here, to avoid a deadlock with some drivers lock (waveOutLock) { result = WaveInterop.waveOutPause(hWaveOut); @@ -191,7 +192,6 @@ public void Pause() { throw new MmException(result, "waveOutPause"); } - playbackState = PlaybackState.Paused; } } diff --git a/NAudio/Wave/WaveOutputs/WaveOutEvent.cs b/NAudio/Wave/WaveOutputs/WaveOutEvent.cs index 5676de64..f13494f6 100644 --- a/NAudio/Wave/WaveOutputs/WaveOutEvent.cs +++ b/NAudio/Wave/WaveOutputs/WaveOutEvent.cs @@ -187,6 +187,7 @@ public void Pause() if (playbackState == PlaybackState.Playing) { MmResult result; + playbackState = PlaybackState.Paused; // set this here to avoid a deadlock problem with some drivers lock (waveOutLock) { result = WaveInterop.waveOutPause(hWaveOut); @@ -195,7 +196,6 @@ public void Pause() { throw new MmException(result, "waveOutPause"); } - playbackState = PlaybackState.Paused; } } @@ -283,7 +283,6 @@ public PlaybackState PlaybackState /// /// Obsolete property /// - [Obsolete] public float Volume { get { return volume; } diff --git a/NAudio/Wave/WaveProviders/SilenceWaveProvider.cs b/NAudio/Wave/WaveProviders/SilenceWaveProvider.cs new file mode 100644 index 00000000..5bc57d5f --- /dev/null +++ b/NAudio/Wave/WaveProviders/SilenceWaveProvider.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; + +// ReSharper disable once CheckNamespace +namespace NAudio.Wave +{ + /// + /// Silence producing wave provider + /// Useful for playing silence when doing a WASAPI Loopback Capture + /// + public class SilenceProvider : IWaveProvider + { + /// + /// Creates a new silence producing wave provider + /// + /// Desired WaveFormat (should be PCM / IEE float + public SilenceProvider(WaveFormat wf) { WaveFormat = wf; } + + /// + /// Read silence from into the buffer + /// + public int Read(byte[] buffer, int offset, int count) + { + var end = offset + count; + for (var pos = offset; pos < end; pos++) + { + buffer[pos] = 0; + } + return count; + } + + /// + /// WaveFormat of this silence producing wave provider + /// + public WaveFormat WaveFormat { get; private set; } + } +} diff --git a/NAudio/Wave/WaveStreams/WaveFormatConversionProvider.cs b/NAudio/Wave/WaveStreams/WaveFormatConversionProvider.cs new file mode 100644 index 00000000..bb34391d --- /dev/null +++ b/NAudio/Wave/WaveStreams/WaveFormatConversionProvider.cs @@ -0,0 +1,179 @@ +using System; +using System.Diagnostics; +using NAudio.Wave.Compression; + +// ReSharper disable once CheckNamespace +namespace NAudio.Wave +{ + /// + /// IWaveProvider that passes through an ACM Codec + /// + public class WaveFormatConversionProvider : IWaveProvider, IDisposable + { + private readonly AcmStream conversionStream; + private readonly IWaveProvider sourceProvider; + private readonly WaveFormat targetFormat; + private readonly int preferredSourceReadSize; + private int leftoverDestBytes; + private int leftoverDestOffset; + private int leftoverSourceBytes; + private bool isDisposed; + /// + /// Create a new WaveFormat conversion stream + /// + /// Desired output format + /// Source Provider + public WaveFormatConversionProvider(WaveFormat targetFormat, IWaveProvider sourceProvider) + { + this.sourceProvider = sourceProvider; + this.targetFormat = targetFormat; + + conversionStream = new AcmStream(sourceProvider.WaveFormat, targetFormat); + + preferredSourceReadSize = Math.Min(sourceProvider.WaveFormat.AverageBytesPerSecond, conversionStream.SourceBuffer.Length); + preferredSourceReadSize -= (preferredSourceReadSize% sourceProvider.WaveFormat.BlockAlign); + } + + /// + /// Gets the WaveFormat of this stream + /// + public WaveFormat WaveFormat + { + get + { + return targetFormat; + } + } + + /// + /// Indicates that a reposition has taken place, and internal buffers should be reset + /// + public void Reposition() + { + leftoverDestBytes = 0; + leftoverDestOffset = 0; + leftoverSourceBytes = 0; + conversionStream.Reposition(); + } + + /// + /// Reads bytes from this stream + /// + /// Buffer to read into + /// Offset in buffer to read into + /// Number of bytes to read + /// Number of bytes read + public int Read(byte[] buffer, int offset, int count) + { + int bytesRead = 0; + if (count % WaveFormat.BlockAlign != 0) + { + //throw new ArgumentException("Must read complete blocks"); + count -= (count % WaveFormat.BlockAlign); + } + + while (bytesRead < count) + { + // first copy in any leftover destination bytes + int readFromLeftoverDest = Math.Min(count - bytesRead, leftoverDestBytes); + if (readFromLeftoverDest > 0) + { + Array.Copy(conversionStream.DestBuffer, leftoverDestOffset, buffer, offset+bytesRead, readFromLeftoverDest); + leftoverDestOffset += readFromLeftoverDest; + leftoverDestBytes -= readFromLeftoverDest; + bytesRead += readFromLeftoverDest; + } + if (bytesRead >= count) + { + // we've fulfilled the request from the leftovers alone + break; + } + + // now we'll convert one full source buffer + var sourceReadSize = Math.Min(preferredSourceReadSize, + conversionStream.SourceBuffer.Length - leftoverSourceBytes); + + // always read our preferred size, we can always keep leftovers for the next call to Read if we get + // too much + int sourceBytesRead = sourceProvider.Read(conversionStream.SourceBuffer, leftoverSourceBytes, sourceReadSize); + int sourceBytesAvailable = sourceBytesRead + leftoverSourceBytes; + if (sourceBytesAvailable == 0) + { + // we've reached the end of the input + break; + } + + int sourceBytesConverted; + int destBytesConverted = conversionStream.Convert(sourceBytesAvailable, out sourceBytesConverted); + if (sourceBytesConverted == 0) + { + Debug.WriteLine(String.Format("Warning: couldn't convert anything from {0}", sourceBytesAvailable)); + // no point backing up in this case as we're not going to manage to finish playing this + break; + } + leftoverSourceBytes = sourceBytesAvailable - sourceBytesConverted; + + if (leftoverSourceBytes > 0) + { + // buffer.blockcopy is safe for overlapping copies + Buffer.BlockCopy(conversionStream.SourceBuffer, sourceBytesConverted, conversionStream.SourceBuffer, + 0, leftoverSourceBytes); + } + + if (destBytesConverted > 0) + { + int bytesRequired = count - bytesRead; + int toCopy = Math.Min(destBytesConverted, bytesRequired); + + // save leftovers + if (toCopy < destBytesConverted) + { + leftoverDestBytes = destBytesConverted - toCopy; + leftoverDestOffset = toCopy; + } + Array.Copy(conversionStream.DestBuffer, 0, buffer, bytesRead + offset, toCopy); + bytesRead += toCopy; + } + else + { + // possible error here + Debug.WriteLine(string.Format("sourceBytesRead: {0}, sourceBytesConverted {1}, destBytesConverted {2}", + sourceBytesRead, sourceBytesConverted, destBytesConverted)); + //Debug.Assert(false, "conversion stream returned nothing at all"); + break; + } + } + return bytesRead; + } + + /// + /// Disposes this stream + /// + /// true if the user called this + protected virtual void Dispose(bool disposing) + { + if (!isDisposed) + { + isDisposed = true; + conversionStream.Dispose(); + } + } + + /// + /// Disposes this resource + /// + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + /// + /// Finalizer + /// + ~WaveFormatConversionProvider() + { + Dispose(false); + } + } +} \ No newline at end of file diff --git a/NAudio/Wave/WaveStreams/WaveFormatConversionStream.cs b/NAudio/Wave/WaveStreams/WaveFormatConversionStream.cs index 3d2191e6..6145a295 100644 --- a/NAudio/Wave/WaveStreams/WaveFormatConversionStream.cs +++ b/NAudio/Wave/WaveStreams/WaveFormatConversionStream.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using NAudio.Wave.Compression; +// ReSharper disable once CheckNamespace namespace NAudio.Wave { /// @@ -9,12 +10,26 @@ namespace NAudio.Wave /// public class WaveFormatConversionStream : WaveStream { - private AcmStream conversionStream; - private WaveStream sourceStream; - private WaveFormat targetFormat; - private long length; + private readonly WaveFormatConversionProvider conversionProvider; + private readonly WaveFormat targetFormat; + private readonly long length; private long position; - private int preferredSourceReadSize; + private readonly WaveStream sourceStream; + private bool isDisposed; + + /// + /// Create a new WaveFormat conversion stream + /// + /// Desired output format + /// Source stream + public WaveFormatConversionStream(WaveFormat targetFormat, WaveStream sourceStream) + { + this.sourceStream = sourceStream; + this.targetFormat = targetFormat; + conversionProvider = new WaveFormatConversionProvider(targetFormat, sourceStream); + length = EstimateSourceToDest((int)sourceStream.Length); + position = 0; + } /// /// Creates a stream that can convert to PCM @@ -27,7 +42,7 @@ public static WaveStream CreatePcmStream(WaveStream sourceStream) { return sourceStream; } - WaveFormat pcmFormat = AcmStream.SuggestPcmFormat(sourceStream.WaveFormat); + var pcmFormat = AcmStream.SuggestPcmFormat(sourceStream.WaveFormat); if (pcmFormat.SampleRate < 8000) { if (sourceStream.WaveFormat.Encoding == WaveFormatEncoding.G723) @@ -43,31 +58,25 @@ public static WaveStream CreatePcmStream(WaveStream sourceStream) } /// - /// Create a new WaveFormat conversion stream + /// Gets or sets the current position in the stream /// - /// Desired output format - /// Source stream - public WaveFormatConversionStream(WaveFormat targetFormat, WaveStream sourceStream) + public override long Position { - this.sourceStream = sourceStream; - this.targetFormat = targetFormat; - - conversionStream = new AcmStream(sourceStream.WaveFormat, targetFormat); - /*try + get { - // work out how many bytes the entire input stream will convert to - length = conversionStream.SourceToDest((int)sourceStream.Length); + return position; } - catch + set { - Dispose(); - throw; - }*/ - length = EstimateSourceToDest((int)sourceStream.Length); + // make sure we don't get out of sync + value -= (value % BlockAlign); - position = 0; - preferredSourceReadSize = Math.Min(sourceStream.WaveFormat.AverageBytesPerSecond, conversionStream.SourceBuffer.Length); - preferredSourceReadSize -= (preferredSourceReadSize%sourceStream.WaveFormat.BlockAlign); + // this relies on conversionStream DestToSource and SourceToDest being reliable + var desiredSourcePosition = EstimateDestToSource(value); //conversionStream.DestToSource((int) value); + sourceStream.Position = desiredSourcePosition; + position = EstimateSourceToDest(sourceStream.Position); //conversionStream.SourceToDest((int)sourceStream.Position); + conversionProvider.Reposition(); + } } /// @@ -76,7 +85,7 @@ public WaveFormatConversionStream(WaveFormat targetFormat, WaveStream sourceStre [Obsolete("can be unreliable, use of this method not encouraged")] public int SourceToDest(int source) { - return (int) EstimateSourceToDest(source); + return (int)EstimateSourceToDest(source); //return conversionStream.SourceToDest(source); } @@ -114,30 +123,6 @@ public override long Length } } - /// - /// Gets or sets the current position in the stream - /// - public override long Position - { - get - { - return position; - } - set - { - // make sure we don't get out of sync - value -= (value % BlockAlign); - - // this relies on conversionStream DestToSource and SourceToDest being reliable - var desiredSourcePosition = EstimateDestToSource(value); //conversionStream.DestToSource((int) value); - sourceStream.Position = desiredSourcePosition; - position = EstimateSourceToDest(sourceStream.Position); //conversionStream.SourceToDest((int)sourceStream.Position); - leftoverDestBytes = 0; - leftoverDestOffset = 0; - conversionStream.Reposition(); - } - } - /// /// Gets the WaveFormat of this stream /// @@ -149,96 +134,16 @@ public override WaveFormat WaveFormat } } - private int leftoverDestBytes = 0; - private int leftoverDestOffset = 0; - private int leftoverSourceBytes = 0; - //private int leftoverSourceOffset = 0; - /// - /// Reads bytes from this stream + /// /// /// Buffer to read into - /// Offset in buffer to read into + /// Offset within buffer to write to /// Number of bytes to read - /// Number of bytes read + /// Bytes read public override int Read(byte[] buffer, int offset, int count) { - int bytesRead = 0; - if (count % BlockAlign != 0) - { - //throw new ArgumentException("Must read complete blocks"); - count -= (count % BlockAlign); - } - - while (bytesRead < count) - { - // first copy in any leftover destination bytes - int readFromLeftoverDest = Math.Min(count - bytesRead, leftoverDestBytes); - if (readFromLeftoverDest > 0) - { - Array.Copy(conversionStream.DestBuffer, leftoverDestOffset, buffer, offset+bytesRead, readFromLeftoverDest); - leftoverDestOffset += readFromLeftoverDest; - leftoverDestBytes -= readFromLeftoverDest; - bytesRead += readFromLeftoverDest; - } - if (bytesRead >= count) - { - // we've fulfilled the request from the leftovers alone - break; - } - - // now we'll convert one full source buffer - if (leftoverSourceBytes > 0) - { - // TODO: still to be implemented: see moving the source position back below: - } - - // always read our preferred size, we can always keep leftovers for the next call to Read if we get - // too much - int sourceBytesRead = sourceStream.Read(conversionStream.SourceBuffer, 0, preferredSourceReadSize); - if (sourceBytesRead == 0) - { - // we've reached the end of the input - break; - } - - int sourceBytesConverted; - int destBytesConverted = conversionStream.Convert(sourceBytesRead, out sourceBytesConverted); - if (sourceBytesConverted == 0) - { - Debug.WriteLine(String.Format("Warning: couldn't convert anything from {0}", sourceBytesRead)); - // no point backing up in this case as we're not going to manage to finish playing this - break; - } - else if (sourceBytesConverted < sourceBytesRead) - { - // cheat by backing up in the source stream (better to save the lefto - sourceStream.Position -= (sourceBytesRead - sourceBytesConverted); - } - - if (destBytesConverted > 0) - { - int bytesRequired = count - bytesRead; - int toCopy = Math.Min(destBytesConverted, bytesRequired); - - // save leftovers - if (toCopy < destBytesConverted) - { - leftoverDestBytes = destBytesConverted - toCopy; - leftoverDestOffset = toCopy; - } - Array.Copy(conversionStream.DestBuffer, 0, buffer, bytesRead + offset, toCopy); - bytesRead += toCopy; - } - else - { - // possible error here - Debug.WriteLine(string.Format("sourceBytesRead: {0}, sourceBytesConverted {1}, destBytesConverted {2}", - sourceBytesRead, sourceBytesConverted, destBytesConverted)); - //Debug.Assert(false, "conversion stream returned nothing at all"); - break; - } - } + var bytesRead = conversionProvider.Read(buffer, offset, count); position += bytesRead; return bytesRead; } @@ -249,24 +154,20 @@ public override int Read(byte[] buffer, int offset, int count) /// true if the user called this protected override void Dispose(bool disposing) { - if (disposing) + if (!isDisposed) { - // Release managed resources. - if (conversionStream != null) + isDisposed = true; + if (disposing) { - conversionStream.Dispose(); - conversionStream = null; + sourceStream.Dispose(); + conversionProvider.Dispose(); } - if (sourceStream != null) + else { - sourceStream.Dispose(); - sourceStream = null; + // we've been called by the finalizer + Debug.Assert(false, "WaveFormatConversionStream was not disposed"); } } - else - { - System.Diagnostics.Debug.Assert(false, "WaveFormatConversionStream was not disposed"); - } // Release unmanaged resources. // Set large fields to null. // Call Dispose on your base class. diff --git a/NAudioTests/Acm/GsmEncodeTest.cs b/NAudioTests/Acm/GsmEncodeTest.cs index 0e8e1e1e..c60ae38e 100644 --- a/NAudioTests/Acm/GsmEncodeTest.cs +++ b/NAudioTests/Acm/GsmEncodeTest.cs @@ -1,25 +1,41 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using NUnit.Framework; using NAudio.Wave; using System.IO; +using NAudio.Wave.SampleProviders; namespace NAudioTests.Acm { [TestFixture] public class GsmEncodeTest { - [Test] - public void CanEncodeGsm() + private static WaveStream CreatePcmTestStream() { - var testFile = @"C:\Users\Mark\Code\CodePlex\AudioTestFiles\WAV\PCM 16 bit\pcm mono 16 bit 8kHz.wav"; - if (!File.Exists(testFile)) + var testFile = @"C:\Users\Mark\Code\CodePlex\pcm mono 16 bit 8kHz.wav"; + if (File.Exists(testFile)) { - Assert.Ignore("Missing test file"); + return new WaveFileReader(testFile); } - using (var reader = new WaveFileReader(testFile)) + var outFormat = new WaveFormat(8000, 16, 1); + const int durationInSeconds = 5; + var sg = new SignalGenerator(outFormat.SampleRate, outFormat.Channels) + { + Frequency = 1000, + Gain = 0.25, + Type = SignalGeneratorType.Sin + }; + var sp = sg.ToWaveProvider16(); + byte[] data = new byte[outFormat.AverageBytesPerSecond * durationInSeconds]; + var bytesRead = sp.Read(data, 0, data.Length); + Assert.AreEqual(bytesRead, data.Length); + return new RawSourceWaveStream(new MemoryStream(data), outFormat); + } + + [Test] + public void CanEncodeGsm() + { + using (var reader = CreatePcmTestStream()) { using (var gsm = new WaveFormatConversionStream(new Gsm610WaveFormat(), reader)) { diff --git a/NAudioTests/Acm/WaveFormatConversionStreamTests.cs b/NAudioTests/Acm/WaveFormatConversionStreamTests.cs index c6762c23..a3290351 100644 --- a/NAudioTests/Acm/WaveFormatConversionStreamTests.cs +++ b/NAudioTests/Acm/WaveFormatConversionStreamTests.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; -using System.Text; using NUnit.Framework; using NAudio.Wave; using NAudio.Wave.Compression; using System.Diagnostics; -using System.Runtime.InteropServices; +using System.Linq; using NAudioTests.Utils; namespace NAudioTests.Acm @@ -81,16 +79,16 @@ public void CanConvertAdpcmToPcm() [Test] public void CanConvertAdpcmToSuggestedPcm() { - using(WaveStream stream = WaveFormatConversionStream.CreatePcmStream( + using (WaveFormatConversionStream.CreatePcmStream( new NullWaveStream(new AdpcmWaveFormat(8000, 1),1000))) - { + { } } [Test] public void CanConvertALawToSuggestedPcm() { - using (WaveStream stream = WaveFormatConversionStream.CreatePcmStream( + using (WaveFormatConversionStream.CreatePcmStream( new NullWaveStream(WaveFormat.CreateALawFormat(8000,1),1000))) { } @@ -99,7 +97,7 @@ public void CanConvertALawToSuggestedPcm() [Test] public void CanConvertMuLawToSuggestedPcm() { - using (WaveStream stream = WaveFormatConversionStream.CreatePcmStream( + using (WaveFormatConversionStream.CreatePcmStream( new NullWaveStream(WaveFormat.CreateMuLawFormat(8000, 1), 1000))) { } @@ -122,19 +120,15 @@ public void CanConvertImeAdpcmToPcm() driver.Open(); try { - foreach (AcmFormatTag formatTag in driver.FormatTags) + foreach (var format in driver.FormatTags + .SelectMany(formatTag => driver.GetFormats(formatTag) + .Where(format => format.FormatTag == WaveFormatEncoding.DviAdpcm || + format.FormatTag == WaveFormatEncoding.ImaAdpcm))) { - foreach (AcmFormat format in driver.GetFormats(formatTag)) - { - if (format.FormatTag == WaveFormatEncoding.DviAdpcm || - format.FormatTag == WaveFormatEncoding.ImaAdpcm) - { - // see if we can convert it to 16 bit PCM - Debug.WriteLine(String.Format("Converting {0} to PCM", format.WaveFormat)); - CanCreateConversionStream(format.WaveFormat, - new WaveFormat(format.WaveFormat.SampleRate, 16, format.WaveFormat.Channels)); - } - } + // see if we can convert it to 16 bit PCM + Debug.WriteLine(String.Format("Converting {0} to PCM", format.WaveFormat)); + CanCreateConversionStream(format.WaveFormat, + new WaveFormat(format.WaveFormat.SampleRate, 16, format.WaveFormat.Channels)); } } finally @@ -145,8 +139,8 @@ public void CanConvertImeAdpcmToPcm() private void CanCreateConversionStream(WaveFormat inputFormat, WaveFormat outputFormat) { - WaveStream inputStream = new NullWaveStream(inputFormat, 10000); - using (WaveFormatConversionStream stream = new WaveFormatConversionStream( + var inputStream = new NullWaveStream(inputFormat, 10000); + using (var stream = new WaveFormatConversionStream( outputFormat, inputStream)) { byte[] buffer = new byte[stream.WaveFormat.AverageBytesPerSecond]; diff --git a/NAudioTests/NAudioTests.csproj b/NAudioTests/NAudioTests.csproj index 9b0ca4cf..c3da9277 100644 --- a/NAudioTests/NAudioTests.csproj +++ b/NAudioTests/NAudioTests.csproj @@ -120,6 +120,7 @@ + diff --git a/NAudioTests/WaveStreams/OffsetSampleProviderTests.cs b/NAudioTests/WaveStreams/OffsetSampleProviderTests.cs index 7adaf67e..23662359 100644 --- a/NAudioTests/WaveStreams/OffsetSampleProviderTests.cs +++ b/NAudioTests/WaveStreams/OffsetSampleProviderTests.cs @@ -114,6 +114,20 @@ public void CanAddLeadOut() osp.AssertReadsExpected(expected2, 100); } + [Test] + public void LeadOutWithoutTakeOnlyBeginsAfterSourceIsCompletelyRead() + { + var source = new TestSampleProvider(32000, 1, 10); + var osp = new OffsetSampleProvider(source) { LeadOutSamples = 5 }; + + var expected = new float[] { 0, 1, 2, 3, 4, 5, 6 }; + osp.AssertReadsExpected(expected, 7); + var expected2 = new float[] { 7, 8, 9, 0, 0, 0 }; + osp.AssertReadsExpected(expected2, 6); + var expected3 = new float[] { 0, 0 }; + osp.AssertReadsExpected(expected3, 6); + } + [Test] public void WaveFormatIsSampeAsSource() { @@ -137,6 +151,17 @@ public void MaintainsPredelayState() osp.AssertReadsExpected(expected3); } + [Test] + public void CanFollowTakeWithLeadout() + { + var source = new TestSampleProvider(32000, 1) { Position = 10 }; + var osp = new OffsetSampleProvider(source) { TakeSamples = 10, LeadOutSamples = 5}; + + + var expected = new float[] { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 0 }; + osp.AssertReadsExpected(expected); + } + [Test] public void MaintainsTakeState() { diff --git a/NAudioTests/WaveStreams/SilenceProviderTests.cs b/NAudioTests/WaveStreams/SilenceProviderTests.cs new file mode 100644 index 00000000..4ce14d87 --- /dev/null +++ b/NAudioTests/WaveStreams/SilenceProviderTests.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq; +using NAudio.Wave; +using NUnit.Framework; + +namespace NAudioTests.WaveStreams +{ + [TestFixture] + public class SilenceProviderTests + { + [Test] + public void CanReadSilence() + { + var sp = new SilenceProvider(new WaveFormat(44100, 2)); + var length = 1000; + var b = Enumerable.Range(1, length).Select(n => (byte) 1).ToArray(); + var read = sp.Read(b, 0, length); + Assert.AreEqual(length, read); + Assert.AreEqual(new byte[length], b); + } + + [Test] + public void RespectsOffsetAndCount() + { + var sp = new SilenceProvider(new WaveFormat(44100, 2)); + var length = 10; + var b = Enumerable.Range(1, length).Select(n => (byte)1).ToArray(); + var read = sp.Read(b, 2, 4); + Assert.AreEqual(4, read); + Assert.AreEqual(new byte[] { 1, 1, 0, 0, 0, 0, 1, 1, 1, 1}, b); + } + } +} diff --git a/NAudioUniversalDemo/Package.appxmanifest b/NAudioUniversalDemo/Package.appxmanifest index 0887514d..125936de 100644 --- a/NAudioUniversalDemo/Package.appxmanifest +++ b/NAudioUniversalDemo/Package.appxmanifest @@ -20,7 +20,7 @@ - +