You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have searched the existing issues to ensure this bug has not already been reported.
I have provided a clear and concise description of the bug.
I have provided detailed steps to reproduce the bug.
I have included a minimal reproducible example (MRE) if applicable.
I have provided error messages and stack traces if applicable.
I have completed the environment information.
1. Describe the Bug
As I decided to use SoundFlow in the project I'm contributing to. But as I tried to use it and follow the documentation, there was nothing about how to implement QueueDataProvider or RawDataProvider.
So I tried asking here and here for help, haven't got a response so far about it.
So I started to wonder if this actually was a bug in SoundFlow or MiniAudio itself? It's really hard to know.
But seeing as it doesn't occur in PortAudio or NAudio, it really seems to be the case.
Okay, to explain, here's what happens: When playing back any interchanging audio buffer, it won't progress past the first few arrays, and sounds like a game crash on a N64 or GameCube. Despite the audio buffers changing within the MiniAudioEngine, it still can't playback the first few buffers. The audio stays like this until the engine is disposed.
I have tried a similar approach with RawDataProvider, but still causes the exact same issue.
I tried to provide a minimal example, however it only produces a small pop sound and nothing else. It's likely that it needs large audio generation mixing to properly show how the issue occurs in realtime.
As I already said, I have no idea if this is a SoundFlow issue or a MiniAudio issue.
Checkout new-gui-experimental branch, to make sure it's on the correct branch.
Add the SoundFlow NuGet to the project.
Replace Mixer.cs with this:
usingPortAudio;usingSystem;usingSystem.Runtime.InteropServices;usingSystem.Linq;usingSystem.IO;usingKermalis.EndianBinaryIO;usingKermalis.VGMusicStudio.Core.Formats;usingStream=PortAudio.Stream;usingNAudio.Wave;usingNAudio.CoreAudioApi;usingNAudio.CoreAudioApi.Interfaces;usingSoundFlow.Abstracts;usingSoundFlow.Backends.MiniAudio;usingSoundFlow.Components;usingSoundFlow.Enums;usingSoundFlow.Providers;usingSoundFlow.Structs;usingSoundFlow.Abstracts.Devices;usingSoundFlow.Interfaces;namespaceKermalis.VGMusicStudio.Core;publicabstractclassMixer:IAudioSessionEventsHandler,IDisposable{publicreadonlybool[]Mutes;
#region MiniAudio Fields
// MiniAudio FieldsprivateAudioPlaybackDevice?_playbackDevice;internalSoundPlayer?MiniAudioPlayer;internalQueueDataProvider?DataProvider;
#endregion
#region PortAudio Fields
// PortAudio FieldspublicWave?WaveData;internalabstractintSamplesPerBuffer{get;}privatefloatVol=1;publicreadonlyobjectCountLock=new();protectedWave?_waveWriterPortAudio;publicStreamParametersOParams;publicStreamParametersDefaultOutputParams{get;privateset;}publicStream?Stream;publicboolIsDisposing=false;privatebool_isDisposed=false;
#endregion
#region NAudio Fields
// NAudio FieldspublicstaticeventAction<float>?VolumeChanged;privateWasapiOut?_out;privateAudioSessionControl?_appVolume;privatebool_shouldSendVolUpdateEvent=true;protectedWaveFileWriter?_waveWriterNAudio;protectedabstractWaveFormat?WaveFormat{get;}
#endregion
// Audio BackendpublicstaticAudioBackendPlaybackBackend{get;set;}publicenumAudioBackend{PortAudio,MiniAudio,NAudio}protectedMixer(){Mutes=newbool[SongState.MAX_TRACKS];if(PlaybackBackendisAudioBackend.NAudio){_out=null!;_appVolume=null!;}}publicclassMiniAudioBuffer:ISoundDataProvider,IDisposable{publicintPosition{get;set;}publicintLength{get;}publicboolCanSeek{get;}publicSoundFlow.Enums.SampleFormatSampleFormat{get;}publicintSampleRate{get;set;}publicboolIsDisposed{get;privateset;}publiceventEventHandler<EventArgs>EndOfStreamReached;publiceventEventHandler<PositionChangedEventArgs>PositionChanged;publicvoidDispose(){if(!IsDisposed){GC.SuppressFinalize(this);}}publicintReadBytes(Span<float>buffer){thrownewNotImplementedException();}publicvoidSeek(intoffset){thrownewNotImplementedException();}}protectedvoidInit(WavewaveData=null!,PortAudio.SampleFormatsampleFormat=PortAudio.SampleFormat.Float32,intsampleRate=48000,byte[]stream=null!,IWaveProviderwaveProvider=null!){switch(PlaybackBackend){caseAudioBackend.PortAudio:{// First, check if the instance contains somethingif(WaveData==null){_isDisposed=false;Pa.Initialize();WaveData=waveData;// Try setting up an output deviceOParams.Device=Pa.DefaultOutputDevice;if(OParams.Device==Pa.NoDevice){thrownewException("No default audio output device is available.");}OParams.Channels=2;OParams.SampleFormat=sampleFormat;OParams.SuggestedLatency=Pa.GetDeviceInfo(OParams.Device).defaultLowOutputLatency;OParams.HostApiSpecificStreamInfo=IntPtr.Zero;// Set it as the defaultDefaultOutputParams=OParams;}Stream=newStream(null,OParams,WaveData!.SampleRate,(uint)SamplesPerBuffer,StreamFlags.NoFlag,PortAudioPlayer.Play,waveData);varhostApiInfo=Pa.GetHostApiInfo(Pa.DefaultHostApi);Stream!.Start();break;}caseAudioBackend.MiniAudio:{varengine=newMiniAudioEngine();varformat=newAudioFormat{SampleRate=sampleRate,Channels=2,Format=SoundFlow.Enums.SampleFormat.F32};vardefaultDevice=engine.PlaybackDevices.FirstOrDefault(x =>x.IsDefault);_playbackDevice=engine.InitializePlaybackDevice(defaultDevice,format);DataProvider=newQueueDataProvider(format);MiniAudioPlayer=newSoundPlayer(engine,format,DataProvider);_playbackDevice.MasterMixer.AddComponent(MiniAudioPlayer);_playbackDevice.Start();MiniAudioPlayer.IsLooping=true;break;}caseAudioBackend.NAudio:{_out=newWasapiOut();_out.Init(waveProvider);using(varen=newMMDeviceEnumerator()){SessionCollectionsessions=en.GetDefaultAudioEndpoint(DataFlow.Render,Role.Multimedia).AudioSessionManager.Sessions;intid=Environment.ProcessId;for(inti=0;i<sessions.Count;i++){AudioSessionControlsession=sessions[i];if(session.GetProcessID==id){_appVolume=session;_appVolume.RegisterEventClient(this);break;}}}_out.Play();break;}}}internalvoidUpdateStream(Span<byte>stream,intsampleRate){if(MiniAudioPlayer.State!=SoundFlow.Enums.PlaybackState.Playing){MiniAudioPlayer.Play();}}publicfloatVolume{get=>Vol;set=>Vol=Math.Clamp(value,0,1);}publicfloatGetVolume(){returnVol;}publicvoidSetVolume(floatvolume){switch(PlaybackBackend){caseAudioBackend.PortAudio:{Vol=Math.Clamp(volume,0,1);break;}caseAudioBackend.MiniAudio:{_playbackDevice!.MasterMixer.Volume=volume;break;}caseAudioBackend.NAudio:{_shouldSendVolUpdateEvent=false;_appVolume!.SimpleAudioVolume.Volume=volume;break;}}}
#region NAudio Functions
publicvoidOnVolumeChanged(floatvolume,boolisMuted){if(_shouldSendVolUpdateEvent){VolumeChanged?.Invoke(volume);}_shouldSendVolUpdateEvent=true;}publicvoidOnDisplayNameChanged(stringdisplayName){thrownewNotImplementedException();}publicvoidOnIconPathChanged(stringiconPath){thrownewNotImplementedException();}publicvoidOnChannelVolumeChanged(uintchannelCount,IntPtrnewVolumes,uintchannelIndex){thrownewNotImplementedException();}publicvoidOnGroupingParamChanged(refGuidgroupingId){thrownewNotImplementedException();}// Fires on @out.Play() and @out.Stop()publicvoidOnStateChanged(AudioSessionStatestate){if(state==AudioSessionState.AudioSessionStateActive){OnVolumeChanged(_appVolume!.SimpleAudioVolume.Volume,_appVolume.SimpleAudioVolume.Mute);}}publicvoidOnSessionDisconnected(AudioSessionDisconnectReasondisconnectReason){thrownewNotImplementedException();}
#endregion
publicvoidCreateWaveWriter(stringfileName){switch(PlaybackBackend){caseAudioBackend.PortAudio:{_waveWriterPortAudio=WaveData;_waveWriterPortAudio!.CreateFileStream(fileName);break;}caseAudioBackend.NAudio:{_waveWriterNAudio=newWaveFileWriter(fileName,WaveFormat);break;}}}publicvoidCloseWaveWriter(){switch(PlaybackBackend){caseAudioBackend.PortAudio:{_waveWriterPortAudio!.Dispose(true);_waveWriterPortAudio=null;break;}caseAudioBackend.NAudio:{_waveWriterNAudio!.Dispose();_waveWriterNAudio=null;break;}}}publicvirtualvoidDispose(){switch(PlaybackBackend){caseAudioBackend.PortAudio:{if(_isDisposed||Streamisnull){return;}IsDisposing=true;Stream!.Stop();Stream!.Dispose();break;}caseAudioBackend.MiniAudio:{if(MiniAudioPlayeris not null&&_playbackDeviceis not null){MiniAudioPlayer.Stop();_playbackDevice.Stop();_playbackDevice.MasterMixer.RemoveComponent(MiniAudioPlayer);}break;}caseAudioBackend.NAudio:{if(_outis not null){_out!.Stop();_out.Dispose();_appVolume!.Dispose();}break;}}GC.SuppressFinalize(this);_isDisposed=true;}publicinterfaceIAudio{Span<byte>ByteBuffer{get;}Span<short>Int16Buffer{get;}Span<int>Int32Buffer{get;}Span<long>Int64Buffer{get;}Span<Int128>Int128Buffer{get;}Span<Half>Float16Buffer{get;}Span<float>Float32Buffer{get;}Span<double>Float64Buffer{get;}Span<decimal>Float128Buffer{get;}}[StructLayout(LayoutKind.Explicit,Pack=2)]publicclassAudio:IAudio{[FieldOffset(0)]publicintNumberOfBytes;[FieldOffset(8)]publicbyte[]?ByteBuffer;[FieldOffset(8)]publicshort[]?Int16Buffer;[FieldOffset(8)]publicint[]?Int32Buffer;[FieldOffset(8)]publiclong[]?Int64Buffer;[FieldOffset(8)]publicInt128[]?Int128Buffer;[FieldOffset(8)]publicHalf[]?Float16Buffer;[FieldOffset(8)]publicfloat[]?Float32Buffer;[FieldOffset(8)]publicdouble[]?Float64Buffer;[FieldOffset(8)]publicdecimal[]?Float128Buffer;Span<byte>IAudio.ByteBuffer=>ByteBuffer!;Span<short>IAudio.Int16Buffer=>Int16Buffer!;Span<int>IAudio.Int32Buffer=>Int32Buffer!;Span<long>IAudio.Int64Buffer=>Int64Buffer!;Span<Int128>IAudio.Int128Buffer=>Int128Buffer!;Span<Half>IAudio.Float16Buffer=>Float16Buffer!;Span<float>IAudio.Float32Buffer=>Float32Buffer!;Span<double>IAudio.Float64Buffer=>Float64Buffer!;Span<decimal>IAudio.Float128Buffer=>Float128Buffer!;publicintByteBufferCount{get{returnNumberOfBytes;}set{NumberOfBytes=CheckValidityCount("ByteBufferCount",value,1);}}publicintInt16BufferCount{get{returnNumberOfBytes/2;}set{NumberOfBytes=CheckValidityCount("Int16BufferCount",value,2);}}publicintInt32BufferCount{get{returnNumberOfBytes/4;}set{NumberOfBytes=CheckValidityCount("Int32BufferCount",value,4);}}publicintInt64BufferCount{get{returnNumberOfBytes/8;}set{NumberOfBytes=CheckValidityCount("Int64BufferCount",value,8);}}publicintInt128BufferCount{get{returnNumberOfBytes/16;}set{NumberOfBytes=CheckValidityCount("Int128BufferCount",value,16);}}publicintFloat16BufferCount{get{returnNumberOfBytes/2;}set{NumberOfBytes=CheckValidityCount("Float16BufferCount",value,2);}}publicintFloat32BufferCount{get{returnNumberOfBytes/4;}set{NumberOfBytes=CheckValidityCount("Float32BufferCount",value,4);}}publicintFloat64BufferCount{get{returnNumberOfBytes/8;}set{NumberOfBytes=CheckValidityCount("Float64BufferCount",value,8);}}publicintFloat128BufferCount{get{returnNumberOfBytes/16;}set{NumberOfBytes=CheckValidityCount("Float128BufferCount",value,16);}}publicAudio(intsizeToAllocateInBytes){varsizeInBytes=sizeToAllocateInBytes;intaligned32Bits=sizeInBytes%4;sizeToAllocateInBytes=(aligned32Bits==0)?sizeInBytes:(sizeInBytes+4-aligned32Bits);ByteBuffer=newbyte[sizeToAllocateInBytes];NumberOfBytes=0;}publicstaticimplicitoperatorbyte[](AudiowaveBuffer){returnwaveBuffer.ByteBuffer!;}privateintCheckValidityCount(stringargName,intvalue,intsizeOfValue){intnum=value*sizeOfValue;if(num%4!=0){thrownewArgumentOutOfRangeException(argName,$"{argName} cannot set a count ({num}) that is not 4 bytes aligned ");}if(value<0||value>ByteBuffer!.Length/sizeOfValue){thrownewArgumentOutOfRangeException(argName,$"{argName} cannot set a count that exceeds max count of {ByteBuffer!.Length/sizeOfValue}.");}returnnum;}publicvoidClear(){Array.Clear(ByteBuffer!,0,ByteBuffer!.Length);}publicvoidCopy(ArraydestinationArray){Array.Copy(ByteBuffer!,destinationArray,NumberOfBytes);}}}
Replace MP2KMixer.cs with this:
usingKermalis.VGMusicStudio.Core.Formats;usingKermalis.VGMusicStudio.Core.Util;usingNAudio.Wave;usingSoundFlow.Abstracts;usingSoundFlow.Backends.MiniAudio;usingSoundFlow.Components;usingSoundFlow.Enums;usingSoundFlow.Providers;usingSoundFlow.Structs;usingSystem;usingSystem.IO;usingSystem.Linq;namespaceKermalis.VGMusicStudio.Core.GBA.MP2K;publicsealedclassMP2KMixer:Mixer{internalreadonlyintSampleRate;internaloverrideintSamplesPerBuffer{get;}internalreadonlyfloatSampleRateReciprocal;privatereadonlyfloat_samplesReciprocal;internalreadonlyfloatPCM8MasterVolume;privatebool_isFading;privatelong_fadeMicroFramesLeft;privatefloat_fadePos;privatefloat_fadeStepPerMicroframe;internalreadonlyMP2KConfigConfig;privatereadonlyAudioBackendMP2KPlaybackBackend;
#region MiniAudio Fields
privatereadonlyWave?_bufferMiniAudio;privatebyte[]?_byteBufferMiniAudio;privatefloat[]?_floatBufferMiniAudio;
#endregion
#region PortAudio Fields
// PortAudio FieldsprivatereadonlyAudio?_audioPortAudio;privatereadonlyWave?_bufferPortAudio;
#endregion
#region NAudio Fields
// NAudio FieldsprivateWaveBuffer?_audioNAudio;privateBufferedWaveProvider?_bufferNAudio;protectedoverrideWaveFormatWaveFormat=>_bufferNAudio!.WaveFormat;
#endregion
privatereadonlyfloat[][]_trackBuffers;privatereadonlyMP2KPCM8Channel[]_pcm8Channels;privatereadonlyMP2KSquareChannel_sq1;privatereadonlyMP2KSquareChannel_sq2;privatereadonlyMP2KPCM4Channel_pcm4;privatereadonlyMP2KNoiseChannel_noise;privatereadonlyMP2KPSGChannel[]_psgChannels;internalMP2KMixer(MP2KConfigconfig){Config=config;(SampleRate,SamplesPerBuffer)=MP2KUtils.FrequencyTable[config.SampleRate];SampleRateReciprocal=1f/SampleRate;_samplesReciprocal=1f/SamplesPerBuffer;PCM8MasterVolume=config.Volume/15f;_pcm8Channels=newMP2KPCM8Channel[24];for(inti=0;i<_pcm8Channels.Length;i++){_pcm8Channels[i]=newMP2KPCM8Channel(this);}_psgChannels=[_sq1=newMP2KSquareChannel(this),_sq2=newMP2KSquareChannel(this),_pcm4=newMP2KPCM4Channel(this),_noise=newMP2KNoiseChannel(this)];intamt=SamplesPerBuffer*2;_trackBuffers=newfloat[0x10][];for(inti=0;i<_trackBuffers.Length;i++){_trackBuffers[i]=newfloat[amt];}MP2KPlaybackBackend=PlaybackBackend;switch(PlaybackBackend){caseAudioBackend.PortAudio:{_audioPortAudio=newAudio(amt*sizeof(float)){Float32BufferCount=amt};_bufferPortAudio=newWave(){DiscardOnBufferOverflow=true,BufferLength=SamplesPerBuffer*64,};_bufferPortAudio.CreateIeeeFloatWave((uint)SampleRate,2);Init(waveData:_bufferPortAudio);break;}caseAudioBackend.MiniAudio:{intsizeInBytes=amt*sizeof(float);intaligned32Bits=sizeInBytes%4;intsizeToAllocateInBytes=(aligned32Bits==0)?sizeInBytes:(sizeInBytes+4-aligned32Bits);// _bufferMiniAudio = new Wave()// {// DiscardOnBufferOverflow = true,// BufferLength = SamplesPerBuffer * 64,// };// _bufferMiniAudio.CreateIeeeFloatWave((uint)SampleRate, 2);_byteBufferMiniAudio=newbyte[sizeToAllocateInBytes];_floatBufferMiniAudio=newfloat[amt];Init(sampleRate:SampleRate,stream:_byteBufferMiniAudio);break;}caseAudioBackend.NAudio:{_audioNAudio=newWaveBuffer(amt*sizeof(float)){FloatBufferCount=amt};_bufferNAudio=newBufferedWaveProvider(WaveFormat.CreateIeeeFloatWaveFormat(SampleRate,2)){DiscardOnBufferOverflow=true,BufferLength=SamplesPerBuffer*64,};Init(waveProvider:_bufferNAudio);break;}}}internalMP2KPCM8Channel?AllocPCM8Channel(MP2KTrackowner,ADSRenv,NoteInfonote,bytevol,sbytepan,intinstPan,intpitch,boolbFixed,boolbCompressed,intsampleOffset){MP2KPCM8Channel?nChn=null;IOrderedEnumerable<MP2KPCM8Channel>byOwner=_pcm8Channels.OrderByDescending(c =>c.Ownerisnull?0xFF:c.Owner.Index);foreach(MP2KPCM8ChanneliinbyOwner)// Find free{if(i.State==EnvelopeState.Dead||i.Ownerisnull){nChn=i;break;}}if(nChnisnull)// Find releasing{foreach(MP2KPCM8ChanneliinbyOwner){if(i.State==EnvelopeState.Releasing){nChn=i;break;}}}if(nChnisnull)// Find prioritized{foreach(MP2KPCM8ChanneliinbyOwner){if(owner.Priority>i.Owner!.Priority){nChn=i;break;}}}if(nChnisnull)// None available{MP2KPCM8Channellowest=byOwner.First();// Kill lowest track's instrument if the track is lower than this oneif(lowest.Owner!.Index>=owner.Index){nChn=lowest;}}if(nChnis not null)// Could still be null from the above if{nChn.Init(owner,note,env,sampleOffset,vol,pan,instPan,pitch,bFixed,bCompressed);}returnnChn;}internalMP2KPSGChannel?AllocPSGChannel(MP2KTrackowner,ADSRenv,NoteInfonote,bytevol,sbytepan,intinstPan,intpitch,VoiceTypetype,objectarg){MP2KPSGChannelnChn;switch(type){caseVoiceType.Square1:{nChn=_sq1;if(nChn.State<EnvelopeState.Releasing&&nChn.Owner!.Index<owner.Index){returnnull;}_sq1.Init(owner,note,env,instPan,(SquarePattern)arg);break;}caseVoiceType.Square2:{nChn=_sq2;if(nChn.State<EnvelopeState.Releasing&&nChn.Owner!.Index<owner.Index){returnnull;}_sq2.Init(owner,note,env,instPan,(SquarePattern)arg);break;}caseVoiceType.PCM4:{nChn=_pcm4;if(nChn.State<EnvelopeState.Releasing&&nChn.Owner!.Index<owner.Index){returnnull;}_pcm4.Init(owner,note,env,instPan,(int)arg);break;}caseVoiceType.Noise:{nChn=_noise;if(nChn.State<EnvelopeState.Releasing&&nChn.Owner!.Index<owner.Index){returnnull;}_noise.Init(owner,note,env,instPan,(NoisePattern)arg);break;}default:returnnull;}nChn.SetVolume(vol,pan);nChn.SetPitch(pitch);returnnChn;}internalvoidBeginFadeIn(){_fadePos=0f;_fadeMicroFramesLeft=(long)(GlobalConfig.Instance.PlaylistFadeOutMilliseconds/1_000.0*GBAUtils.AGB_FPS);_fadeStepPerMicroframe=1f/_fadeMicroFramesLeft;_isFading=true;}internalvoidBeginFadeOut(){_fadePos=1f;_fadeMicroFramesLeft=(long)(GlobalConfig.Instance.PlaylistFadeOutMilliseconds/1_000.0*GBAUtils.AGB_FPS);_fadeStepPerMicroframe=-1f/_fadeMicroFramesLeft;_isFading=true;}internalboolIsFading(){return_isFading;}internalboolIsFadeDone(){return_isFading&&_fadeMicroFramesLeft==0;}internalvoidResetFade(){_isFading=false;_fadeMicroFramesLeft=0;}internalvoidProcess(booloutput,boolrecording){for(inti=0;i<_trackBuffers.Length;i++){Span<float>buf=_trackBuffers[i];buf.Clear();}switch(MP2KPlaybackBackend){caseAudioBackend.PortAudio:{_audioPortAudio!.Clear();break;}caseAudioBackend.NAudio:{_audioNAudio!.Clear();break;}}for(inti=0;i<_pcm8Channels.Length;i++){MP2KPCM8Channelc=_pcm8Channels[i];if(c.Owneris not null){c.Process(_trackBuffers[c.Owner.Index]);}}for(inti=0;i<_psgChannels.Length;i++){MP2KPSGChannelc=_psgChannels[i];if(c.Owneris not null){c.Process(_trackBuffers[c.Owner.Index]);}}floatmasterStep;floatmasterLevel;if(_isFading&&_fadeMicroFramesLeft==0){masterStep=0;masterLevel=0;}else{floatfromMaster=1f;floattoMaster=1f;if(_fadeMicroFramesLeft>0){constfloatscale=10f/6f;fromMaster*=(_fadePos<0f)?0f:MathF.Pow(_fadePos,scale);_fadePos+=_fadeStepPerMicroframe;toMaster*=(_fadePos<0f)?0f:MathF.Pow(_fadePos,scale);_fadeMicroFramesLeft--;}masterStep=(toMaster-fromMaster)*_samplesReciprocal;masterLevel=fromMaster;}for(inti=0;i<_trackBuffers.Length;i++){if(Mutes[i]){continue;}floatlevel=masterLevel;Span<float>buf=_trackBuffers[i];for(intj=0;j<SamplesPerBuffer;j++){switch(MP2KPlaybackBackend){caseAudioBackend.PortAudio:{_audioPortAudio!.Float32Buffer![j*2]+=buf[j*2]*level;_audioPortAudio.Float32Buffer[(j*2)+1]+=buf[(j*2)+1]*level;break;}caseAudioBackend.MiniAudio:{_floatBufferMiniAudio![j*2]+=buf[j*2]*level;_floatBufferMiniAudio[(j*2)+1]+=buf[(j*2)+1]*level;break;}caseAudioBackend.NAudio:{_audioNAudio!.FloatBuffer![j*2]+=buf[j*2]*level;_audioNAudio.FloatBuffer[(j*2)+1]+=buf[(j*2)+1]*level;break;}}level+=masterStep;}}if(output){switch(MP2KPlaybackBackend){caseAudioBackend.PortAudio:{_bufferPortAudio!.AddSamples(_audioPortAudio!.ByteBuffer,0,_audioPortAudio.ByteBufferCount);break;}caseAudioBackend.MiniAudio:{for(inti=0,b=0;i<_floatBufferMiniAudio!.Length;i++){// Span<byte> bytes = BitConverter.GetBytes(_floatBufferMiniAudio[i]);// _byteBufferMiniAudio![b++] = bytes[0];// _byteBufferMiniAudio[b++] = bytes[1];// _byteBufferMiniAudio[b++] = bytes[2];// _byteBufferMiniAudio[b++] = bytes[3];}DataProvider.AddSamples(_floatBufferMiniAudio);if(MiniAudioPlayer.State==SoundFlow.Enums.PlaybackState.Stopped){MiniAudioPlayer.Play();}// _bufferMiniAudio.AddSamples(_byteBufferMiniAudio, 0, _byteBufferMiniAudio.Length);// UpdateStream(_byteBufferMiniAudio, SampleRate);break;}caseAudioBackend.NAudio:{_bufferNAudio!.AddSamples(_audioNAudio!.ByteBuffer,0,_audioNAudio.ByteBufferCount);break;}}}if(recording){switch(MP2KPlaybackBackend){caseAudioBackend.PortAudio:{_waveWriterPortAudio!.Write(_audioPortAudio!.ByteBuffer,0,_audioPortAudio.ByteBufferCount);break;}caseAudioBackend.NAudio:{_waveWriterNAudio!.Write(_audioNAudio!.ByteBuffer,0,_audioNAudio.ByteBufferCount);break;}}}}}
Open Edit > Preferences and select MiniAudio and click the OK button.
Open any GBA game dump of Pokémon FireRed that you own.
3. Expected Behavior
The audio should playback clearly, in the same way as PortAudio and NAudio do.
4. Current Behavior
Playback buffer is stuck at the very beginning, despite the buffers being changed on every interval, it doesn't progress and sounds like a game crash on a N64 or GameCube.
5. Minimal Reproducible Example (MRE)
usingSystem.Diagnostics;usingSoundFlow.Backends.MiniAudio;usingSoundFlow.Components;usingSoundFlow.Enums;usingSoundFlow.Providers;usingSoundFlow.Structs;namespaceSoundFlow_SineWaveTest;internalstaticclassProgram{publicstaticfloat[]Sine=newfloat[200];privatestaticvoidMain(string[]args){for(inti=0;i<200;i++){Sine[i]=(float)Math.Sin(((double)i/(double)200)*Math.PI*2);}// 1. Initialize the engine context. It no longer starts a device directly.usingvarengine=newMiniAudioEngine();// 2. Define the audio format for playback.varformat=newAudioFormat{SampleRate=48000,Channels=2,Format=SampleFormat.F32};// 3. Initialize a specific playback device. Passing `null` will use the system default too.// The `using` statement ensures the device is properly disposed.vardefaultDevice=engine.PlaybackDevices.FirstOrDefault(x =>x.IsDefault);usingvarplaybackDevice=engine.InitializePlaybackDevice(defaultDevice,format);// 4. Create a SoundPlayer, passing the engine and format context.// Make sure you replace "path/to/your/audiofile.wav" with the actual path.usingvardataProvider=newQueueDataProvider(format);varplayer=newSoundPlayer(engine,format,dataProvider);// 5. Add the player to the device's master mixer.playbackDevice.MasterMixer.AddComponent(player);// 6. Start the device to begin the audio stream.playbackDevice.Start();boolstopped=false;varstopwatch=newStopwatch();stopwatch.Start();varthread=newThread(Playback){Name="SineWave Tick"};thread.Start();// 7. Start the player.player.Play();Console.WriteLine($"Playing audio on '{playbackDevice.Info?.Name}'... Press any key to stop.");Console.Read();voidPlayback(){dataProvider.AddSamples(Sine);if(player.StateisPlaybackState.Stopped&&!stopped){player.Play();}if(stopwatch.Elapsed.Seconds==5){for(inti=0;i<200;i++){Sine[i]=(float)Math.Sin(((double)i/(double)200)*Math.PI*2)/2;}}if(stopwatch.Elapsed.Seconds==10){for(inti=0;i<200;i++){Sine[i]=(float)Math.Sin(((double)i/(double)200)*Math.PI*2)/4;}}if(stopwatch.Elapsed.Seconds==15){playbackDevice.Stop();}}}}
Requirements
1. Describe the Bug
As I decided to use SoundFlow in the project I'm contributing to. But as I tried to use it and follow the documentation, there was nothing about how to implement QueueDataProvider or RawDataProvider.
So I tried asking here and here for help, haven't got a response so far about it.
So I started to wonder if this actually was a bug in SoundFlow or MiniAudio itself? It's really hard to know.
But seeing as it doesn't occur in PortAudio or NAudio, it really seems to be the case.
Okay, to explain, here's what happens: When playing back any interchanging audio buffer, it won't progress past the first few arrays, and sounds like a game crash on a N64 or GameCube. Despite the audio buffers changing within the MiniAudioEngine, it still can't playback the first few buffers. The audio stays like this until the engine is disposed.
I have tried a similar approach with RawDataProvider, but still causes the exact same issue.
I tried to provide a minimal example, however it only produces a small pop sound and nothing else. It's likely that it needs large audio generation mixing to properly show how the issue occurs in realtime.
As I already said, I have no idea if this is a SoundFlow issue or a MiniAudio issue.
Here's what I mean (Volume Warning!):
2025-09-09.17-28-58.mp4
2. Steps to Reproduce
3. Expected Behavior
The audio should playback clearly, in the same way as PortAudio and NAudio do.
4. Current Behavior
Playback buffer is stuck at the very beginning, despite the buffers being changed on every interval, it doesn't progress and sounds like a game crash on a N64 or GameCube.
5. Minimal Reproducible Example (MRE)
6. Error Messages and Stack Trace (if applicable)
SoundFlow Version
1.2.1
.NET Version
.NET 8.0
Operating System
Fedora 42
Architecture
x64
Audio Backend Used (if known)
ALSA (MiniAudio)
Specific Audio Hardware (if relevant)
No response
8. Affected Components/Modules (if known)
SoundPlayer,AudioPlaybackDevice,MiniAudioEngine,AudioFormat,QueueDataProvider,RawDataProvider9. Impact
Incorrect audio output, playback buffers not changing
10. Possible Workaround (if known)
No response
11. Additional Context
This issue does not occur in NAudio (Windows) or PortAudio (Cross-Platform), as they properly output the buffer while playing.