diff -urN visualboyadvance-m-2.1.5-orig/src/common/ConfigManager.cpp visualboyadvance-m-2.1.5/src/common/ConfigManager.cpp
--- visualboyadvance-m-2.1.5-orig/src/common/ConfigManager.cpp	2022-12-15 20:58:01 +0900
+++ visualboyadvance-m-2.1.5/src/common/ConfigManager.cpp	2023-02-02 20:09:00 +0900
@@ -611,6 +611,10 @@
 		gb_effects_config.enabled = true;
 	}
 
+	int soundBufferMax = ReadPref("soundBufferMax", 300);
+	int soundBufferMin = ReadPref("soundBufferMin", 300);
+	soundSetBufferSize(soundBufferMax / 1000.0f, soundBufferMin / 1000.0f);
+
 	if (optFlashSize == 0)
 		flashSetSize(0x10000);
 	else
diff -urN visualboyadvance-m-2.1.5-orig/src/common/SoundDriver.h visualboyadvance-m-2.1.5/src/common/SoundDriver.h
--- visualboyadvance-m-2.1.5-orig/src/common/SoundDriver.h	2022-12-15 20:58:01 +0900
+++ visualboyadvance-m-2.1.5/src/common/SoundDriver.h	2023-02-02 20:09:00 +0900
@@ -35,8 +35,10 @@
         /**
          * Initialize the sound driver.
          * @param sampleRate In Hertz
+         * @param bufferMax Output buffer size in seconds
+         * @param bufferMin Output flush threshold in seconds
          */
-        virtual bool init(long sampleRate) = 0;
+        virtual bool init(long sampleRate, float bufferMax, float bufferMin) = 0;
 
         /**
          * Tell the driver that the sound stream has paused
diff -urN visualboyadvance-m-2.1.5-orig/src/common/SoundSDL.cpp visualboyadvance-m-2.1.5/src/common/SoundSDL.cpp
--- visualboyadvance-m-2.1.5-orig/src/common/SoundSDL.cpp	2022-12-15 20:58:01 +0900
+++ visualboyadvance-m-2.1.5/src/common/SoundSDL.cpp	2023-02-02 20:10:18 +0900
@@ -25,9 +25,6 @@
 
 extern int emulating;
 
-// Hold up to 100 ms of data in the ring buffer
-const double SoundSDL::buftime = 0.100;
-
 SoundSDL::SoundSDL():
     samples_buf(0),
     sound_device(0),
@@ -111,10 +108,16 @@
     samples_buf.write(finalWave, samples * 2);
 
     SDL_UnlockMutex(mutex);
+
+    if (samples_buf.used() >= writeThreshold) {
+	SDL_SemPost(data_available);
+	if (should_wait())
+	    SDL_SemWait(data_read);
+    }
 }
 
 
-bool SoundSDL::init(long sampleRate) {
+bool SoundSDL::init(long sampleRate, float bufferMax, float bufferMin) {
     if (initialized) deinit();
 
     SDL_AudioSpec audio;
@@ -138,7 +141,11 @@
         return false;
     }
 
-    samples_buf.reset(static_cast<size_t>(std::ceil(buftime * sampleRate * 2)));
+    this->bufferMax = bufferMax;
+    this->bufferMin = bufferMin;
+    bufferSize = static_cast<int>(std::ceil(bufferMax * sampleRate * 2));
+    writeThreshold = static_cast<int>(std::ceil(bufferMin * sampleRate * 2));
+    samples_buf.reset(bufferSize);
 
     mutex          = SDL_CreateMutex();
     data_available = SDL_CreateSemaphore(0);
@@ -189,7 +196,7 @@
 void SoundSDL::resume() {}
 
 void SoundSDL::reset() {
-    init(soundGetSampleRate());
+    init(soundGetSampleRate(), bufferMin, bufferMax);
 }
 
 void SoundSDL::setThrottle(unsigned short throttle_) {
diff -urN visualboyadvance-m-2.1.5-orig/src/common/SoundSDL.h visualboyadvance-m-2.1.5/src/common/SoundSDL.h
--- visualboyadvance-m-2.1.5-orig/src/common/SoundSDL.h	2022-12-15 20:58:01 +0900
+++ visualboyadvance-m-2.1.5/src/common/SoundSDL.h	2023-02-02 20:10:12 +0900
@@ -28,7 +28,7 @@
         SoundSDL();
         virtual ~SoundSDL();
 
-        virtual bool init(long sampleRate);
+        virtual bool init(long sampleRate, float bufferMax, float bufferMin);
         virtual void pause();
         virtual void reset();
         virtual void resume();
@@ -53,6 +53,10 @@
         SDL_AudioSpec audio_spec;
 
         unsigned short current_rate;
+	float bufferMax;
+	float bufferMin;
+	int bufferSize;
+	int writeThreshold;
 
         bool initialized = false;
 
diff -urN visualboyadvance-m-2.1.5-orig/src/gba/Sound.cpp visualboyadvance-m-2.1.5/src/gba/Sound.cpp
--- visualboyadvance-m-2.1.5-orig/src/gba/Sound.cpp	2022-12-15 20:58:01 +0900
+++ visualboyadvance-m-2.1.5/src/gba/Sound.cpp	2023-02-02 20:09:01 +0900
@@ -42,6 +42,8 @@
 
 static uint16_t soundFinalWave[1600];
 long soundSampleRate = 44100;
+float soundBufferMax = 0.3;
+float soundBufferMin = 0.3;
 bool soundInterpolation = true;
 bool soundPaused = true;
 float soundFiltering = 0.5f;
@@ -507,6 +509,12 @@
     return soundVolume;
 }
 
+void soundSetBufferSize(float bufferMax, float bufferMin)
+{
+    soundBufferMax = bufferMax;
+    soundBufferMin = bufferMin;
+}
+
 void soundSetEnable(int channels)
 {
     soundEnableFlag = channels;
@@ -540,7 +548,7 @@
     if (!soundDriver)
         return false;
 
-    if (!soundDriver->init(soundSampleRate))
+    if (!soundDriver->init(soundSampleRate, soundBufferMax, soundBufferMin))
         return false;
 
     if (!stereo_buffer) {
diff -urN visualboyadvance-m-2.1.5-orig/src/gba/Sound.h visualboyadvance-m-2.1.5/src/gba/Sound.h
--- visualboyadvance-m-2.1.5-orig/src/gba/Sound.h	2022-12-15 20:58:01 +0900
+++ visualboyadvance-m-2.1.5/src/gba/Sound.h	2023-02-02 20:09:01 +0900
@@ -18,6 +18,11 @@
 void soundSetVolume(float);
 float soundGetVolume();
 
+// Manages sound output buffer size
+// Up to bufferMax seconds will be buffered; buffered data will be sent
+// to the output device until less than bufferMin seconds remain
+void soundSetBufferSize(float bufferMax, float bufferMin);
+
 // Manages muting bitmask. The bits control the following channels:
 // 0x001 Pulse 1
 // 0x002 Pulse 2
