Migrating from NAudio 2 to NAudio 3
NAudio 3 is a major release. The single NAudio assembly has been split into
focused packages, the minimum target framework is now net9.0, the core is
cross-platform and Native-AOT compatible, and several APIs have been modernised.
This guide walks through the breaking changes and how to update your code.
Most applications that reference the NAudio meta-package and use the common
playback/recording/file APIs will need only small changes — usually just
re-targeting to net9.0 and adjusting any custom IWaveProvider /
ISampleProvider implementations to the new Span<T> Read signature.
Tip: build with warnings visible. Removed members fail to compile, and almost everything that is deprecated rather than removed produces an
[Obsolete]warning that points you at the replacement.
If you only read one section
For a typical NAudio 2 app, these five are what you will actually hit:
- Re-target to
net9.0(or later). WaveOutEventis now calledWaveOut, andWaveInEventis nowWaveIn. The old names still work as[Obsolete]subclasses, so this is a warning, not a break.WaveOut.DesiredLatencyis gone — useBufferMilliseconds, which sizes each individual buffer rather than the total across all of them.- Custom
IWaveProvider/ISampleProviderimplementations need theirReadoverride changed to take a singleSpan<T>. WasapiOut/WasapiCapture/WasapiLoopbackCaptureare[Obsolete]in favour ofWasapiPlayer/WasapiRecorder. They still ship and still work.
Everything else is detailed below.
Target framework and packages
- Minimum target framework is now
net9.0. Legacy .NET Framework and .NET Standard 2.0 are no longer supported. Re-target your project tonet9.0(or later) before upgrading the package. NAudiois now a set of focused packages. The shipping libraries areNAudio.Core,NAudio.Midi,NAudio.WinMM,NAudio.Wasapi,NAudio.Asio,NAudio.WinFormsandNAudio.Dmo, alongside the newNAudio.Effects(inNAudio.Core),NAudio.Sampler,NAudio.Vst3,NAudio.AlsaandNAudio.SoundFile. TheNAudiometa-package still pulls the Windows stack together, so if you referenceNAudioyou generally don't need to change your package references. If you reference individual packages, you may need to add one or two (see the type moves below). See the assembly layout plan.- The
NAudio.Uappackage is removed. The UWP/WinRT audio backend is gone; useWasapiPlayerBuilder/WasapiRecorderBuilderfromNAudio.Wasapiinstead. NAudio.WinFormsno longer supportsnet472, andNAudio.WinMMno longer supportsnetstandard2.0. If you need .NET Framework, stay on the 2.x packages.
The Read signature change (Span<T>)
This is the change most likely to affect custom code.
IWaveProvider.Read(byte[] buffer, int offset, int count)is nowRead(Span<byte> buffer).ISampleProvider.Read(float[] buffer, int offset, int count)is nowRead(Span<float> buffer).
Calling a provider:
// before
int read = source.Read(buffer, offset, count);
// after
int read = source.Read(buffer.AsSpan(offset, count));
Implementing a provider — change the override and index from the start of the span:
// before
public int Read(byte[] buffer, int offset, int count) { ... buffer[offset + i] ... }
// after
public int Read(Span<byte> buffer) { ... buffer[i] ... }
The same pattern applies to the new Span<T> overloads added on
BiQuadFilter.Transform, ALawDecoder.Decode, MuLawDecoder.Decode and
IMp3FrameDecompressor.DecompressFrame (the last has a default interface method
so existing third-party decoders such as NLayer keep working).
The abstract base classes changed to match: WaveProvider32 now overrides
Read(Span<float>) and WaveProvider16 overrides Read(Span<short>).
One related overload was dropped: Init(IWavePlayer, ISampleProvider, bool convertTo16Bit)
is removed. Use Init(IWavePlayer, ISampleProvider), which always initialises with IEEE
float, or convert upstream with SampleToWaveProvider16 if you specifically need 16-bit:
// before
player.Init(sampleProvider, convertTo16Bit: true);
// after
player.Init(new SampleToWaveProvider16(sampleProvider));
WASAPI
WasapiOut,WasapiCaptureandWasapiLoopbackCaptureare now[Obsolete]in favour of the newWasapiPlayer/WasapiRecorderAPIs (built viaWasapiPlayerBuilder/WasapiRecorderBuilder). The legacy types still ship and continue to work, so this is a warning, not a break. See the WasapiPlayer and WasapiRecorder tutorials.WasapiOut's embedded DMO resampler was removed. In exclusive mode, if your source format is not natively supported by the device you now get aNotSupportedExceptionfromInitinstead of silent on-the-fly resampling. Resample upstream (for example withMediaFoundationResampler), use shared mode (which still auto-converts viaAutoConvertPcm), or switch toWasapiPlayerBuilder.WaveInEventArgsnow fires one event per WASAPI packet (previously batched). A newBufferSpanproperty exposes the data without copying through theBufferbyte array.MMDevice.AudioClientis[Obsolete]because it created a new instance per access — useMMDevice.CreateAudioClient().PropertyStore's raw-PropVariantindexer is[Obsolete]. ThePropertyStore[int]indexer now resolvesPropVariantvalues safely.Device notifications are now event-based. Implementing
IMMNotificationClientand callingMMDeviceEnumerator.RegisterEndpointNotificationCallback/UnregisterEndpointNotificationCallbackis no longer the way — the interface and those methods are nowinternal. CallMMDeviceEnumerator.CreateNotificationClient()and subscribe to the events on the returnedMMDeviceNotificationClientinstead. This removes the need to implement a COM interface (and, under NAudio 3, a[GeneratedComClass]and<AllowUnsafeBlocks>), and the enumerator manages the callback lifetime for you. Events marshal to theSynchronizationContextcaptured when the client is created (passuseSynchronizationContext: falseto receive them on the audio worker thread instead).// before class MyClient : IMMNotificationClient { /* implement all five methods */ } enumerator.RegisterEndpointNotificationCallback(new MyClient()); // after var notifications = enumerator.CreateNotificationClient(); notifications.DefaultDeviceChanged += (s, e) => Console.WriteLine(e.DeviceId); notifications.DeviceStateChanged += (s, e) => Console.WriteLine($"{e.DeviceId} {e.NewState}"); // ... dispose notifications (or the enumerator) to unsubscribeThe raw Core Audio COM interfaces are now
internal—IAudioClient,IAudioClient2,IAudioSessionControl,IAudioSessionControl2,IAudioSessionNotification,IControlInterfaceand friends. Use the wrapper classes (AudioClient,AudioSessionControl, …), which is what almost all NAudio 2 code already did.The
AudioClientconstructor isinternal. Obtain one fromMMDevice.CreateAudioClient()orAudioClient.ActivateAsync().Core Audio errors now throw
CoreAudioException, a subclass ofCOMException. Existingcatch (COMException)still works; new code can catch the specific type.PropertyStoreProperty.Valuechanged type fromPropVarianttoobject. It now exposes the resolved managed value (string,uint,byte[],Guid, …) — cast to what you expect. The oldPropVariantexposed pointer fields (LPWSTR/BLOB/CLSID) that were unsafe to read once the COM-allocated memory had been cleared. Relatedly,PropVariant.DataTypenow returnsNAudio.CoreAudioApi.Interfaces.VarTyperather than the deprecatedSystem.Runtime.InteropServices.VarEnum; the numericVT_*values are unchanged, so bitwise tests keep working.WasapiPlayer.Volumeis session volume, not device volume. UnlikeWasapiOut, it moves your application's slider in the Windows volume mixer (viaSimpleAudioVolume) rather than the system-wide endpoint volume. For endpoint-wide control useDeviceVolume.MasterVolumeLevelScalar; for per-channel control of your own stream useStreamVolume(shared mode only).AudioEndpointVolumenotifications may arrive on a different thread. If the object was constructed on the UI thread, notifications are posted back to it via the capturedSynchronizationContext.
Media Foundation
All the MF COM interfaces are now
internal—IMFSourceReader,IMFSinkWriter,IMFTransform,IMFMediaType,IMFAttributes,IMFByteStreamand the rest — and so are the low-levelMf*wrappers around them (MfSourceReader,MfSinkWriter,MfTransform,MfSample,MfMediaBuffer, …). Of theMf*types onlyMfActivatestays public. Work through the high-level classes instead:MediaFoundationReader,StreamMediaFoundationReader,MediaFoundationEncoder,MediaFoundationResampler,MediaFoundationTransform,MediaFoundationApiandMediaType.MediaFoundationInteropisinternal— useMediaFoundationApiinstead.MediaTypenow implementsIDisposable, and itsIMFMediaTypeconstructor andMediaFoundationObjectproperty areinternal. Construct withMediaType()orMediaType(WaveFormat), read via the properties (SampleRate,SubType, …), and dispose it (or useusing).Finalizers were removed from
MediaFoundationTransformandMediaFoundationEncoder. These no longer clean themselves up on the finalizer thread, so you must callDispose()— a missedusingis now a leak rather than a delayed release.MF errors throw
MediaFoundationException(a subclass ofCOMException).The underscore-prefixed enums and ALL_CAPS structs were renamed to PascalCase, with PascalCase members. The ones you are most likely to have named:
NAudio 2 NAudio 3 _MFT_ENUM_FLAGMftEnumFlagsMFT_MESSAGE_TYPEMftMessageType(MFT_MESSAGE_COMMAND_FLUSH→Flush)MF_SOURCE_READER_FLAGSourceReaderFlags(MF_SOURCE_READERF_ENDOFSTREAM→EndOfStream)MFT_INPUT_STREAM_INFOMftInputStreamInfoMFT_OUTPUT_STREAM_INFOMftOutputStreamInfoMFT_REGISTER_TYPE_INFOMftRegisterTypeInfo(now a class)MF_SINK_WRITER_STATISTICSSinkWriterStatisticsThe remaining
_MFT_*_FLAGSenums follow the same pattern —MftInputStatusFlags,MftInputStreamInfoFlags,MftOutputDataBufferFlags,MftOutputStatusFlags,MftOutputStreamInfoFlags,MftProcessOutputFlags,MftProcessOutputStatusandMftSetTypeFlags.MediaFoundationApi.EnumerateTransformsnow returnsMfActivatewrappers, which exposeAttributeCount,GetAttributeByIndex,GetString,GetUInt32,GetGuidandActivateTransform.
WaveOut / WaveIn
WaveOutEventis renamed toWaveOut, andWaveInEventtoWaveIn. In NAudio 2 the plainWaveOut/WaveInnames belonged to the window-callback classes andWaveOutEvent/WaveInEventwere the recommended ones; in NAudio 3 the recommended classes get the plain names.WaveOutEventandWaveInEventstill exist as[Obsolete]subclasses so existing code keeps compiling with a warning:// before using var player = new WaveOutEvent(); // after using var player = new WaveOut();WaveOutandWaveInnow default to event-driven callbacks. The legacy window-based variants are renamedWaveOutWindow/WaveInWindowand live inNAudio.WinForms. If you relied on the window-callback behaviour (for example pumping a UI message loop), referenceNAudio.WinFormsand use the*Windowtypes.WaveCallbackInfoand theWaveCallbackStrategyenum are removed. The old three-way strategy is now expressed by picking a class and a constructor:NAudio 2 NAudio 3 new WaveOut(WaveCallbackInfo.NewWindow())new WaveOutWindow()(NAudio.WinForms)new WaveOut(WaveCallbackInfo.ExistingWindow(hwnd))new WaveOutWindow(hwnd)(NAudio.WinForms)new WaveOut(WaveCallbackInfo.FunctionCallback())no equivalent — function-callback mode was never reliable and is gone for good; use new WaveOut()The same applies to
WaveIn/WaveInWindow.WaveWindowandWaveWindowNativeare no longer exposed — the message pump is an internal detail of the*Windowclasses.DesiredLatencyis replaced byBufferMilliseconds. This is a compile break with no shim, and the meaning changed:DesiredLatencywas the total across all buffers, whereasBufferMilliseconds(default 100) sizes each individual buffer. With the defaultNumberOfBuffers = 2,DesiredLatency = 300becomesBufferMilliseconds = 150.// before var player = new WaveOutEvent { DesiredLatency = 300, NumberOfBuffers = 2 }; // after var player = new WaveOut { BufferMilliseconds = 150, NumberOfBuffers = 2 };WaveIn's default record format changed from 8 kHz 16-bit mono to 44.1 kHz 16-bit stereo. This is a silent behaviour change — if you relied on the old default, setWaveFormatexplicitly:var recorder = new WaveIn { WaveFormat = new WaveFormat(8000, 16, 1) };The WinMM interop types are now
internal:WaveInterop,WaveHeader,WaveHeaderFlags,MmTime,WaveOutBuffer,WaveInBufferandWaveOutUtils. These were never intended for direct use — go throughWaveOut/WaveIn.BufferedWaveProviderbuffer duration is now set in the constructor (default 5 seconds);BufferLengthandBufferDurationare read-only.
MIDI and WinMM
MidiIn,MidiOut,MidiInCapabilitiesandMidiOutCapabilitiesmoved fromNAudio.MiditoNAudio.WinMM.NAudio.Midiis now cross-platform — itsnet9.0target no longer P/Invokeswinmm.dll. If you use the classic Windows MIDI I/O classes, add a reference toNAudio.WinMM(theNAudiometa-package already includes it).MmResult,MmExceptionandManufacturersmoved fromNAudio.CoretoNAudio.WinMM.MidiInMessageEventArgs.Timestamp/MidiInSysexMessageEventArgs.Timestampare nowTimeSpan(previouslyintmilliseconds), preserving the WinRT 100 ns resolution.MidiIn.CreateSysexBufferswas removed —MidiInnow allocates sysex receive buffers automatically insideStart().
New (non-breaking) additions worth knowing about: WinRT WinRTMidiIn /
WinRTMidiOut in NAudio.Midi (Windows build), the backend-agnostic IMidiInput /
IMidiOutput interfaces, and the IMidiInstrument MIDI-file → audio pipeline.
DMO and DirectSound
- New
NAudio.Dmopackage. The DMO effects, the DMO MP3 decoder (DmoMp3FrameDecompressor), the DMO resampler (ResamplerDmoStream) andDirectSoundOuthave been carved out ofNAudio.Wasapi/NAudio.Core. Namespaces are preserved (NAudio.Dmo,NAudio.Dmo.Effect, andNAudio.WaveforDirectSoundOut). Meta-package consumers see no change —NAudio.Dmocomes in transitively. DirectNAudio.Wasapiconsumers who use the DMO/DirectSound types now need an explicit<PackageReference Include="NAudio.Dmo" />. DmoMp3FrameDecompressormoved fromNAudio.FileFormats.Mp3toNAudio.Dmo(update yourusing).- The DMO interop enums are now
internal(DmoInputStatusFlags,DmoEnumFlags,MediaParamCurveType, …). Go throughDmoEnumerator,MediaObjectand theDmoEffectWaveProviderwrappers. MediaBuffer's finalizer was removed — callDispose()(orusing) rather than relying on finalization.- DMO errors throw
MediaFoundationException(a subclass ofCOMException), so existingcatch (COMException)still works. WindowsMediaMp3Decoderhas lost its old "DO NOT USE" label and is properly documented, butDmoMp3FrameDecompressorremains the class you want for high-level MP3 decoding.- For new code, prefer
MediaFoundationResampleroverResamplerDmoStream, andWasapiPlayerBuilderoverDirectSoundOut.
Effects (removed types and replacements)
The old ad-hoc effect types were removed in favour of the new
NAudio.Effects framework:
SimpleCompressorStream(nowSimpleCompressorEffect) was removed along with the internal ChunkWare DSP — use the newCompressorEffect(and the wider dynamics suite:LimiterEffect,GateEffect,MultibandCompressorEffect, etc.).ImpulseResponseConvolutionwas removed (it was an unusable O(n²) stub) — useConvolutionReverbEffect(partitioned FFT convolution).NAudio.Extras.EqualizerandNAudio.Extras.EqualizerBandwere removed — useNAudio.Effects.Equalizer/EqualizerBand(inNAudio.Core). The new EQ is per-channel and click-free when retuned, and adds shelf/pass/notch/ band-pass/all-pass shapes. The band API changed:Bandwidth/GainbecameQ/GainDb(orShelfSlope), and the equaliser is now anIAudioEffect(wrap it withEffectSampleProviderinstead of passing a source to the constructor).
Stream ownership in file writers (WaveFileWriter / AiffFileWriter)
WaveFileWriter and AiffFileWriter now follow the same stream-ownership rule the
readers (WaveFileReader, AiffFileReader, Mp3FileReader) already use, and which the
.NET BCL follows: you dispose what you own.
- The filename constructors (
new WaveFileWriter("out.wav", format)) open the underlyingFileStreamthemselves, so they still own and close it onDispose— unchanged behaviour. - The stream constructors (
new WaveFileWriter(stream, format)) now treat the stream as caller-owned. Disposing the writer still finalizes the header and flushes so the file is valid, but it no longer disposes the stream you passed in — that is left for you to dispose.
Previously the stream constructor disposed the caller's stream unconditionally, which is
why IgnoreDisposeStream was needed to write to a MemoryStream you wanted to keep
(new WaveFileWriter(new IgnoreDisposeStream(ms), format)). That wrapper is no longer
necessary — passing the stream directly leaves it open. (IgnoreDisposeStream still
exists and existing code that uses it keeps working.)
What to check when upgrading. The one case that changes behaviour is passing a throwaway stream you didn't keep a reference to and relying on the writer to close it, classically:
// before: the writer closed this FileStream for you
new WaveFileWriter(File.Create(path), format); // <-- handle now leaks
After the upgrade that FileStream handle is left open. Either use the filename overload
(which owns the file), or dispose the stream yourself:
// preferred - the writer owns the file
using var writer = new WaveFileWriter(path, format);
// or keep and dispose the stream yourself
using var stream = File.Create(path);
using var writer = new WaveFileWriter(stream, format);
The common new WaveFileWriter(path, format) filename usage is unaffected.
Reading and writing WAV chunks
The scattered "one subclass per chunk type" reader/writer pair has been replaced by a single
chunk model hanging off WaveFileReader.Chunks and WaveFileWriter, so cue points, BWF
bext and LIST/INFO metadata all work on an ordinary reader or writer.
Reading
WaveFileReader.ExtraChunkswas removed — useWaveFileReader.Chunks, which returns aWaveChunkscollection of the sameRiffChunkelements plusFind(id),FindAll(id)andContains(id).WaveFileReader.GetChunkData(RiffChunk)was removed — useWaveFileReader.Chunks.GetData(chunk), with the same lazy-read semantics.WaveFileChunkReaderis nowinternal(and moved toNAudio.Wave).CueWaveFileReaderwas removed. No subclass is needed any more:// before var reader = new CueWaveFileReader("file.wav"); CueList cues = reader.Cues; // after using var reader = new WaveFileReader("file.wav"); CueList cues = reader.Chunks.ReadCueList(); // null if the file has no cuesIWaveChunkInterpreter<T>is the extension point for chunk types NAudio doesn't know about; built-in interpreters cover cue lists, BWFbext(BextInterpreter) and LIST/INFO (InfoListInterpreter→InfoMetadata).
Writing
CueWaveFileWriterwas removed — add cues to an ordinaryWaveFileWriter:// before var writer = new CueWaveFileWriter("out.wav", format); writer.AddCue(1000, "marker"); // after using var writer = new WaveFileWriter("out.wav", format); writer.AddCue(1000, "marker"); // or AddCue(position, label, length) // or, if you already have a populated CueList: writer.WriteCueList(cues);BwfWriterwas removed, and RF64 promotion now belongs toWaveFileWriterrather than being tied to Broadcast Wave. PassWaveFileWriterOptionsand write thebextchunk as an extension:// before var writer = new BwfWriter("out.wav", format, bextChunkInfo); // after using var writer = new WaveFileWriter("out.wav", format, new WaveFileWriterOptions { EnableRf64 = true }); writer.WriteBroadcastExtension(broadcastExtension);EnableRf64reserves aJUNKplaceholder up front and promotes the file toRF64+ds64on close once the data chunk exceeds 4 GB (tunable viaRf64PromotionThreshold).BextChunkInfowas removed — useBroadcastExtension, which is now the DTO for both reading and writing, adds BWF v2 loudness fields and aToChunkData()serialiser. One field-level change:OriginationDateTimeis replaced by separateOriginationDateandOriginationTimestrings, withBroadcastExtension.FormatOriginationDate(DateTime)/FormatOriginationTime(DateTime)producing the BWFyyyy-MM-dd/HH:mm:ssforms.WaveFileWriter.AddChunk(string, byte[], ChunkPosition)andAddChunk(IWaveChunkWriter)are the low-level entry points for arbitrary RIFF chunks before or after the data chunk.
Long-deprecated members removed
These were already [Obsolete] throughout NAudio 2, so if you build with warnings visible
you have been told about them for years. NAudio 3 removes them. Every one has a direct
replacement on the same class, so the fix is mechanical.
| Removed | Use instead |
|---|---|
WaveFileWriter.WriteData(byte[], int, int) |
Write(byte[], int, int) — or the new Write(ReadOnlySpan<byte>) |
WaveFileWriter.WriteData(short[], int, int) |
WriteSamples(short[], int, int) |
WaveFileReader.TryReadFloat(out float) |
ReadNextSampleFrame() |
AcmStream.Convert(int) |
Convert(int bytesToConvert, out int sourceBytesConverted) |
WaveFormatConversionStream.SourceToDest(int) |
(no replacement — see below) |
WaveFormatConversionStream.DestToSource(int) |
(no replacement — see below) |
AsioAudioAvailableEventArgs.GetAsInterleavedSamples() |
GetAsInterleavedSamples(float[] samples) |
Both WaveFileWriter.WriteData overloads were pure forwarders, so this is a rename and
nothing more:
// before
writer.WriteData(buffer, 0, count);
writer.WriteData(shortSamples, 0, count);
// after
writer.Write(buffer, 0, count);
writer.WriteSamples(shortSamples, 0, count);
TryReadFloat silently dropped channels. It returned only the first sample of each
frame, so on stereo it read at half rate and discarded the right channel. ReadNextSampleFrame
returns the whole frame:
// before — mono only, silently wrong on stereo
while (reader.TryReadFloat(out var sample)) { ... }
// after
float[] frame;
while ((frame = reader.ReadNextSampleFrame()) != null) { /* frame[0], frame[1], ... */ }
AcmStream.Convert(int) threw when the driver didn't consume everything. It called the
two-argument overload and raised MmException if sourceBytesConverted != bytesToConvert,
turning an ordinary partial conversion into an error. The two-argument version tells you
how much was consumed so you can carry the remainder:
// after
int destBytes = stream.Convert(bytesToConvert, out int sourceBytesConverted);
SourceToDest / DestToSource on WaveFormatConversionStream have no replacement, which
is why their obsolete message never named one — by the end they were a block-aligned estimate
derived from AverageBytesPerSecond, not a real ACM query, and were documented as unreliable.
Use the stream's own Position and Length, which apply the same estimate internally, or do
the ratio yourself. Note the identically-named AcmStream.SourceToDest / DestToSource are
not affected — those are real ACM queries and remain.
GetAsInterleavedSamples() allocated on the audio thread. It allocated a fresh
float[SamplesPerBuffer * channels] on every ASIO buffer callback. Allocate once and reuse:
// before
void OnAudioAvailable(object s, AsioAudioAvailableEventArgs e) {
var samples = e.GetAsInterleavedSamples(); // allocates every callback
}
// after — hoist the buffer out of the callback
float[] samples = new float[maxSamplesPerBuffer * channels];
void OnAudioAvailable(object s, AsioAudioAvailableEventArgs e) {
int count = e.GetAsInterleavedSamples(samples);
}
AsioOut.Volume was not removed despite a similar long-standing [Obsolete] notice. It is
an IWavePlayer interface member, so it has to stay on the class; it still returns 1.0f and
still throws if you set anything else. ASIO does not expose device volume — adjust gain in
your signal chain instead (for example with VolumeSampleProvider).
Other type moves and API changes
AudioMediaSubtypesmoved from theNAudio.Dmonamespace toNAudio.Wave. It ships inNAudio.Coreand always did, despite the DMO-sounding namespace — so cross-platform code had to writeusing NAudio.Dmo;to name the media subtype GUIDs even on Linux, and even without theNAudio.Dmopackage installed. It now sits inNAudio.WavealongsideWaveFormatExtensible, its main consumer. If you compared aWaveFormatExtensible.SubFormatagainstMEDIASUBTYPE_PCM/MEDIASUBTYPE_IEEE_FLOAT, swap theusing:// before using NAudio.Dmo; if (fmt.SubFormat == AudioMediaSubtypes.MEDIASUBTYPE_PCM) { ... } // after — NAudio.Wave, which you almost certainly have already using NAudio.Wave; if (fmt.SubFormat == AudioMediaSubtypes.MEDIASUBTYPE_PCM) { ... }The type, its 70 GUID constants and
GetAudioSubtypeNameare otherwise unchanged. Real DMO code in theNAudio.Dmopackage is unaffected.AudioVolumeLevelmoved fromNAudio.Wasapi.CoreAudioApitoNAudio.CoreAudioApi(alongsideMMDevice,Part,DeviceTopology, …).CaptureStatemoved fromNAudio.CoreAudioApitoNAudio.Wave(it is a backend-agnostic capture state used byWaveIn,WasapiCaptureandWasapiRecorder). Code that named it viausing NAudio.CoreAudioApi;now needsusing NAudio.Wave;.SoundFont.SampleHeader's public fields are now properties. This is source-compatible for normal reads/writes but binary-breaking for compiled consumers and source-breaking forref/outaccess to the old fields.MixingWaveProvider32was removed — useMixingSampleProviderinstead. It was an untested work-in-progress that accepted only 32-bit IEEE-float inputs, so it offered nothing overMixingSampleProvider, which mixes in float, converts PCM inputs for you (waveProvider.ToSampleProvider()), and adds dynamic add/remove, an input-ended event andReadFully. If you need anIWaveProviderout of it, call.ToWaveProvider():// before var mixer = new MixingWaveProvider32(); mixer.AddInputStream(floatWaveProvider); // after var mixer = new MixingSampleProvider(new[] { waveProvider.ToSampleProvider() }); mixer.AddMixerInput(anotherProvider.ToSampleProvider()); IWaveProvider output = mixer.ToWaveProvider(); // if you need IWaveProviderImaAdpcmWaveFormatwas removed — it was a non-functional "work in progress" stub (it left block align, average bytes per second and samples-per-block at zero and never serialized itssamplesPerBlockextension field, so it produced an invalid header on every path) and was referenced nowhere. TheWaveFormatEncoding.ImaAdpcm/DviAdpcmconstants are unchanged; if you need an IMA/DVI ADPCM header, declare your ownWaveFormatsubclass that sets the fields and overridesSerialize(seeAdpcmWaveFormatfor the pattern).WaveBufferis deprecated — useMemoryMarshal.Castto reinterpret buffers.StreamMediaFoundationReadernow throwsArgumentExceptionfor non-readable or non-seekable streams instead of failing later (#1288).HResult.E_INVALIDARGwas corrected to0x80070057(it was the legacy0x80000003), andHResult.MAKE_HRESULTis deprecated in favour ofMakeHResult(#1288).
See also
- Release notes — the full list of what's new in NAudio 3.
- Migrating from
AsioOuttoAsioDevice— the ASIO API is redesigned;AsioOutis preserved as a facade, so this is optional. - Audio effects, the sampler, cross-platform audio files — guides to the major new subsystems.