@@ -74,7 +74,14 @@ static void update_rate_ratio(void){
7474 double freq; /* SetFrequency ratio (1.0 = original rate) */
7575 uint64_t plays, stops, wr_bytes, wr_calls, wr_nonzero;
7676 uint64_t over; /* number of callbacks where we passed the writer */
77+ /* STALE DATA GUARD (see audio_note_region): which 256-byte blocks hold data
78+ * the game wrote and we have not played yet. Only on streaming buffers. */
79+ uint8_t *fresh;
80+ uint32_t wr_play; /* writes since this Play started (>= 8: a streaming buffer) */
81+ uint64_t muted; /* output frames muted because the data was stale */
82+ uint32_t lastblk; /* block the cursor was in at the end of the last callback */
7783} SndBuf;
84+ #define FRESH_SH 8u /* block = 256 bytes (~1.5 ms of 44.1 kHz stereo 16-bit) */
7885
7986static SndBuf g_snd[MAXSND ];
8087static int g_sndn = 1 ; /* 0 = invalid id */
@@ -111,6 +118,7 @@ int audio_register(uint32_t data, uint32_t bytes, uint32_t rate,
111118 b->wpos = 0 ; b->wseen = 0 ; b->trim = 1.0 ; b->target = 0.0 ; b->over = 0 ;
112119 b->freq = 1.0 ; b->plays = b->stops = 0 ;
113120 b->wr_bytes = b->wr_calls = b->wr_nonzero = 0 ;
121+ b->fresh = calloc ((bytes >> FRESH_SH ) + 1u , 1 ); b->wr_play = 0 ; b->muted = 0 ; b->lastblk = 0xFFFFFFFFu ;
114122 fprintf (stderr, " [sound] buffer #%d : %u Hz, %u channels, %u bits, %u bytes (%.2f s)\n " ,
115123 id , rate, chans, bits, bytes,
116124 (double )bytes / (double )(rate * chans * (bits/8u )));
@@ -125,7 +133,7 @@ void audio_play(int id, int loop){
125133 /* CAUTION: DON'T RESET the cursor on a buffer that is already playing. The game
126134 * calls Play over and over on its streaming buffers; going back to the
127135 * start on every call made the song stutter "stop-start". */
128- if (!g_snd[id ].playing ){ g_snd[id ].cursor = 0.0 ; g_replay++; }
136+ if (!g_snd[id ].playing ){ g_snd[id ].cursor = 0.0 ; g_snd[ id ]. wr_play = 0 ; g_replay++; }
129137 else g_reissue++;
130138 g_snd[id ].playing = 1 ;
131139 g_snd[id ].loop = loop;
@@ -145,6 +153,37 @@ void audio_note_write(int id, uint32_t end_off){
145153 }
146154 pthread_mutex_unlock (&g_slock);
147155}
156+ /* ---- STALE DATA GUARD --------------------------------------------------------
157+ * The game streams music (and the mixed sound) into a looping 0.5 s buffer,
158+ * writing what we consumed and keeping ~180 ms ahead (measured). If it stops
159+ * writing for a while (a stall: slow disk, a long frame), the play cursor -- which
160+ * has to keep moving, the game picks where to write from it -- goes round the ring
161+ * and plays the SAME old half second again and again: the "broken record, it
162+ * jumps back for two seconds" glitch reported from menus. Windows does the same
163+ * with a looping buffer, the game just never stalls there. Now every written
164+ * region is marked fresh, a block is unmarked once played, and an unmarked block
165+ * of a streaming buffer plays as silence: a stall becomes a short gap instead of
166+ * a repeat. The cursor keeps moving exactly as before (no change for the game).
167+ * Static buffers (written once, then looped: engine sounds) are not streaming
168+ * (fewer than 8 writes since Play) and loop as before. SNDSTALE=0 disables. */
169+ static int stale_guard_on (void ){
170+ static int v = -1 ; if (v < 0 ){ const char *e = getenv (" SNDSTALE" ); v = !(e && *e == ' 0' ); }
171+ return v;
172+ }
173+ void audio_note_region (int id , uint32_t off, uint32_t n){
174+ if (id <= 0 || id >= MAXSND || !n) return ;
175+ pthread_mutex_lock (&g_slock);
176+ SndBuf *b = &g_snd[id ];
177+ if (b->used && b->bytes && b->fresh ){
178+ if (off >= b->bytes ) off %= b->bytes ;
179+ if (n > b->bytes ) n = b->bytes ;
180+ uint32_t end = off + n; /* may run past the end: wrap */
181+ for (uint32_t q = off >> FRESH_SH ; (q << FRESH_SH ) < end; q++)
182+ b->fresh [q % ((b->bytes >> FRESH_SH ) + 1u )] = 1 ;
183+ if (b->playing ) b->wr_play ++;
184+ }
185+ pthread_mutex_unlock (&g_slock);
186+ }
148187void audio_note_write_stat (int id , uint32_t n, int nonzero){
149188 if (id <= 0 || id >= MAXSND ) return ;
150189 pthread_mutex_lock (&g_slock);
@@ -321,13 +360,23 @@ static void mix_into(int16_t *out, unsigned frames){
321360 }
322361 double step = (double )b->rate / (double )OUT_RATE * g_rate_ratio * b->trim * b->freq ;
323362 active++;
363+ /* see STALE DATA GUARD */
364+ int guard = b->fresh && b->loop && b->wr_play >= 8u && stale_guard_on ();
365+ uint32_t nblk = (b->bytes >> FRESH_SH ) + 1u , lastblk = b->lastblk ;
324366 for (unsigned f = 0 ; f < frames; f++){
325367 double sp = b->cursor + step * (double )f;
326368 if (sp >= (double )nsrc){
327369 if (b->loop ) sp = fmod (sp, (double )nsrc);
328370 else { b->playing = 0 ; break ; }
329371 }
330372 uint32_t si = (uint32_t )sp;
373+ if (guard){
374+ uint32_t blk = (si * bpf) >> FRESH_SH ;
375+ if (blk != lastblk){ /* leaving a block: it is played */
376+ if (lastblk != 0xFFFFFFFFu ) b->fresh [lastblk % nblk] = 0 ;
377+ lastblk = blk; }
378+ if (!b->fresh [blk % nblk]){ b->muted ++; continue ; } /* stale: silence */
379+ }
331380 for (int c = 0 ; c < OUT_CH ; c++){
332381 uint32_t sc = (b->chans == 2u ) ? (uint32_t )c : 0u ;
333382 uint32_t off = b->data + si*bpf + sc*bps;
@@ -353,6 +402,7 @@ static void mix_into(int16_t *out, unsigned frames){
353402 fprintf (g_csv, " %.4f \n " , nby ? (double )z/(double )nby : 0.0 );
354403 }
355404 b->cursor += step * (double )frames;
405+ b->lastblk = guard ? lastblk : 0xFFFFFFFFu ;
356406 if (nsrc){
357407 if (b->cursor >= (double )nsrc){
358408 if (b->loop ) b->cursor = fmod (b->cursor , (double )nsrc);
@@ -567,6 +617,9 @@ void audio_report(void){
567617 (unsigned long long )b->wr_calls , (unsigned long long )b->wr_nonzero ,
568618 b->wr_bytes /1024.0 , b->playing ? " PLAYING" : " stopped" );
569619 }
620+ { uint64_t mu = 0 ; for (int i = 1 ; i < g_sndn; i++) mu += g_snd[i].muted ;
621+ fprintf (stderr, " sound stale guard : %s , %.1f ms of not-yet-rewritten data muted instead of replayed\n " ,
622+ stale_guard_on () ? " on" : " off (SNDSTALE=0)" , (double )mu * 1000.0 / (double )OUT_RATE ); }
570623 { uint64_t ov = 0 ; for (int i = 1 ; i < g_sndn; i++) ov += g_snd[i].over ;
571624 fprintf (stderr, " sound cursor overrun: %llu callbacks where playback passed the writer\n " ,
572625 (unsigned long long )ov); }
0 commit comments