Skip to content

Commit 21bd094

Browse files
Actually fix seeking in audio pipeline
Thanks to claude.ai that knew what the fuck it was doing. Edit: Nevermind. It was my flawed code. I didn't even need to waste server power consumption using claude. I had already figured it out myself, which was directly in the `Mixer.setTime` function. I will now be reverting the claude.ai generated "fixes" that I've tried.
1 parent 38aedfc commit 21bd094

File tree

4 files changed

+113
-40
lines changed

4 files changed

+113
-40
lines changed

src/debug/DeveloperStuff.hx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ class DeveloperStuff {
2020
if (playField == null) return;
2121
switch (code) {
2222
case KeyCode.PERIOD:
23-
playField.setTime(playField.songPosition + 2000);
23+
playField.setTime(playField.songPosition + 200);
2424
case KeyCode.COMMA:
25-
playField.setTime(playField.songPosition - 2000);
25+
playField.setTime(playField.songPosition - 200);
26+
case KeyCode.S:
27+
playField.setTime(2200);
2628
case KeyCode.NUMBER_9:
2729
if (playField.songStarted)
2830
Mixer.speed -= 0.25;

src/miniaudio/extras/stb_vorbis.c

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4879,39 +4879,82 @@ static int peek_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int
48794879

48804880
int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number)
48814881
{
4882+
//printf("move to %i test\n", sample_number);
48824883
uint32 max_frame_samples;
4884+
uint32 remaining;
48834885

48844886
if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing);
48854887

4886-
// fast page-level search
48874888
if (!seek_to_sample_coarse(f, sample_number))
48884889
return 0;
48894890

48904891
assert(f->current_loc_valid);
48914892
assert(f->current_loc <= sample_number);
48924893

4893-
// linear search for the relevant packet
4894+
// OPTIMIZATION 1: Early exit if already at target
4895+
if (f->current_loc == sample_number)
4896+
return 1;
4897+
48944898
max_frame_samples = (f->blocksize_1*3 - f->blocksize_0) >> 2;
4899+
remaining = sample_number - f->current_loc;
4900+
4901+
// OPTIMIZATION 2: Skip full frames quickly
4902+
while (remaining > max_frame_samples * 2) {
4903+
int mode;
4904+
int left_start, left_end, right_start, right_end;
4905+
4906+
// Save bit reader state
4907+
int saved_valid_bits = f->valid_bits;
4908+
uint32 saved_acc = f->acc;
4909+
int saved_packet_bytes = f->packet_bytes;
4910+
4911+
if (!peek_decode_initial(f, &left_start, &left_end, &right_start, &right_end, &mode)) {
4912+
// Restore and continue with normal path
4913+
f->valid_bits = saved_valid_bits;
4914+
f->acc = saved_acc;
4915+
f->packet_bytes = saved_packet_bytes;
4916+
break;
4917+
}
4918+
4919+
int frame_samples = right_start - left_start;
4920+
4921+
// Check if it's a full frame we can skip
4922+
if (frame_samples == max_frame_samples) {
4923+
// Skip this full frame
4924+
f->current_loc += frame_samples;
4925+
f->previous_length = 0;
4926+
maybe_start_packet(f);
4927+
flush_packet(f);
4928+
remaining = sample_number - f->current_loc;
4929+
} else {
4930+
// Not a full frame, restore and use normal path
4931+
f->valid_bits = saved_valid_bits;
4932+
f->acc = saved_acc;
4933+
f->packet_bytes = saved_packet_bytes;
4934+
break;
4935+
}
4936+
}
4937+
4938+
// Original algorithm for remaining frames
48954939
while (f->current_loc < sample_number) {
48964940
int left_start, left_end, right_start, right_end, mode, frame_samples;
48974941
if (!peek_decode_initial(f, &left_start, &left_end, &right_start, &right_end, &mode))
48984942
return error(f, VORBIS_seek_failed);
4899-
// calculate the number of samples returned by the next frame
4943+
49004944
frame_samples = right_start - left_start;
4945+
49014946
if (f->current_loc + frame_samples > sample_number) {
4902-
return 1; // the next frame will contain the sample
4947+
return 1;
49034948
} else if (f->current_loc + frame_samples + max_frame_samples > sample_number) {
4904-
// there's a chance the frame after this could contain the sample
49054949
vorbis_pump_first_frame(f);
49064950
} else {
4907-
// this frame is too early to be relevant
49084951
f->current_loc += frame_samples;
49094952
f->previous_length = 0;
49104953
maybe_start_packet(f);
49114954
flush_packet(f);
49124955
}
49134956
}
4914-
// the next frame should start with the sample
4957+
49154958
if (f->current_loc != sample_number) return error(f, VORBIS_seek_failed);
49164959
return 1;
49174960
}

src/miniaudio/ma_thing.cpp

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,12 @@ struct DecoderStream {
204204
float* loadingBuffer = nullptr;
205205

206206
// Audio thread state (only touched by audio thread)
207-
ma_uint64 filePosition = 0;
207+
std::atomic<ma_uint64> filePosition{0};
208208
ma_uint64 bufferStartPos = 0;
209209
ma_uint64 localReadPos = 0;
210210
ma_uint64 validFrames = 0;
211+
212+
bool justSeeked = false;
211213

212214
// Async loading state (atomic for lock-free access)
213215
struct AsyncState {
@@ -330,7 +332,7 @@ struct DecoderStream {
330332
}
331333

332334
// Reset state
333-
void resetState() {
335+
void resetState(bool seeking = false) {
334336
// Reset atomic flags
335337
asyncState.nextBufferReady.store(false, std::memory_order_release);
336338
asyncState.loadingBufferReady.store(false, std::memory_order_release);
@@ -339,15 +341,17 @@ struct DecoderStream {
339341
asyncState.requestNextBuffer.store(false, std::memory_order_release);
340342
asyncState.requestLoadingBuffer.store(false, std::memory_order_release);
341343

342-
asyncState.nextBufferValidFrames = 0;
343-
asyncState.loadingBufferValidFrames = 0;
344-
asyncState.nextBufferStartPos = 0;
345-
asyncState.loadingBufferStartPos = 0;
346-
347-
filePosition = 0;
348-
bufferStartPos = 0;
349-
localReadPos = 0;
350-
validFrames = 0;
344+
if (!seeking) {
345+
asyncState.nextBufferValidFrames = 0;
346+
asyncState.loadingBufferValidFrames = 0;
347+
asyncState.nextBufferStartPos = 0;
348+
asyncState.loadingBufferStartPos = 0;
349+
350+
filePosition.store(0, std::memory_order_relaxed);
351+
bufferStartPos = 0;
352+
localReadPos = 0;
353+
validFrames = 0;
354+
}
351355

352356
if (pcmBufferA) memset(pcmBufferA, 0, TOTAL_BUFFER_FRAMES * CHANNEL_COUNT * sizeof(float));
353357
if (pcmBufferB) memset(pcmBufferB, 0, TOTAL_BUFFER_FRAMES * CHANNEL_COUNT * sizeof(float));
@@ -462,7 +466,7 @@ struct DecoderStream {
462466
memcpy(&decoder, &other.decoder, sizeof(ma_decoder));
463467

464468
// Move regular members
465-
filePosition = other.filePosition;
469+
filePosition.store(other.filePosition, std::memory_order_relaxed);
466470
bufferStartPos = other.bufferStartPos;
467471
localReadPos = other.localReadPos;
468472
validFrames = other.validFrames;
@@ -965,7 +969,6 @@ class AudioSystem {
965969
void seekToPCMFrame(int64_t pos) {
966970
if(!exists) return;
967971

968-
// Pause async loading during seek
969972
if (asyncLoader) {
970973
asyncLoader->pauseLoading();
971974
}
@@ -978,20 +981,24 @@ class AudioSystem {
978981
for (size_t i = 0; i < streams.size(); i++) {
979982
DecoderStream& s = streams[i];
980983

981-
// Seek to the exact position requested
982-
ma_uint64 target = std::min<ma_uint64>(
983-
(ma_uint64)std::max<int64_t>(pos, (int64_t)0),
984-
s.decoderLength
985-
);
984+
ma_uint64 target = pos;
985+
986+
printf("SEEKING TO TARGET: %lld\n", (long long)target);
987+
printf(" Before seek - filePos: %lld, bufferStart: %lld, localRead: %lld\n",
988+
(long long)s.filePosition, (long long)s.bufferStartPos, (long long)s.localReadPos);
986989

987-
s.resetState();
990+
s.resetState(true);
991+
s.justSeeked = true;
988992
fillInitialBuffer(i, target);
989993

990-
if (s.active && target < s.decoderLength) {
991-
s.filePosition = target;
992-
}
994+
printf(" After seek - filePos: %lld, bufferStart: %lld, localRead: %lld\n",
995+
(long long)s.filePosition, (long long)s.bufferStartPos, (long long)s.localReadPos);
996+
997+
// Verify the math
998+
ma_uint64 calculatedFilePos = s.bufferStartPos + s.localReadPos;
999+
printf(" Calculated filePos should be: %lld (matches: %s)\n",
1000+
(long long)calculatedFilePos, (calculatedFilePos == s.filePosition) ? "YES" : "NO");
9931001

994-
// Aggressively preload both buffers synchronously
9951002
if (s.active) {
9961003
loadNextBufferSync(i);
9971004
if (s.asyncState.nextBufferReady.load(std::memory_order_relaxed)) {
@@ -1003,7 +1010,6 @@ class AudioSystem {
10031010
mixerState = (pos < (int64_t)streams[longestDecoderIndex].decoderLength) ? 2 : 3;
10041011

10051012
if (wasPlaying && mixerState == 2) {
1006-
// Resume async loading before starting playback
10071013
if (asyncLoader) {
10081014
asyncLoader->resumeLoading();
10091015
}
@@ -1059,6 +1065,7 @@ class AudioSystem {
10591065
void fillInitialBuffer(size_t index, ma_uint64 startFrame) {
10601066
DecoderStream& s = streams[index];
10611067

1068+
// Calculate where to start decoding (with padding before the target)
10621069
ma_uint64 decodeStart = 0;
10631070
if(startFrame > PADDING_FRAMES) {
10641071
decodeStart = startFrame - PADDING_FRAMES;
@@ -1070,18 +1077,28 @@ class AudioSystem {
10701077
s.bufferStartPos = decodeStart;
10711078
s.validFrames = framesRead;
10721079

1073-
if(startFrame >= decodeStart) {
1080+
// Calculate the local read position within this buffer
1081+
if(startFrame >= decodeStart && startFrame < decodeStart + framesRead) {
10741082
s.localReadPos = startFrame - decodeStart;
1075-
} else {
1083+
} else if(startFrame < decodeStart) {
10761084
s.localReadPos = 0;
1085+
} else {
1086+
// startFrame is beyond what we loaded
1087+
s.localReadPos = framesRead;
10771088
}
10781089

1090+
// Ensure localReadPos is within bounds
10791091
if(s.localReadPos >= TOTAL_BUFFER_FRAMES) {
10801092
s.localReadPos = TOTAL_BUFFER_FRAMES - 1;
10811093
}
10821094

1083-
s.filePosition = startFrame;
1084-
s.active = (startFrame < s.decoderLength && framesRead > 0);
1095+
// Set filePosition to the ACTUAL position we'll start reading from
1096+
// This is critical - it should be bufferStart + localReadPos
1097+
//printf("before? %lld\n", s.filePosition);
1098+
s.filePosition.store(s.bufferStartPos + s.localReadPos, std::memory_order_relaxed);
1099+
//printf("after? %lld\n", s.filePosition);
1100+
1101+
s.active = (s.filePosition < s.decoderLength && framesRead > 0);
10851102
s.asyncState.needsLoad.store(false, std::memory_order_release);
10861103
}
10871104

@@ -1093,6 +1110,7 @@ class AudioSystem {
10931110
return;
10941111
}
10951112

1113+
// Calculate next buffer start from CURRENT buffer position, not file position
10961114
ma_uint64 nextBufferStart = s.bufferStartPos + HALF_BUFFER_FRAMES;
10971115
if (nextBufferStart > PADDING_FRAMES) {
10981116
nextBufferStart -= PADDING_FRAMES;
@@ -1177,8 +1195,11 @@ class AudioSystem {
11771195
return 0;
11781196
}
11791197

1180-
if (s.shouldSwapBuffers() || s.isBufferLow()) {
1181-
s.trySwapBuffers();
1198+
// Don't swap buffers immediately after seeking
1199+
if (!s.justSeeked) {
1200+
if (s.shouldSwapBuffers() || s.isBufferLow()) {
1201+
s.trySwapBuffers();
1202+
}
11821203
}
11831204

11841205
ma_uint32 framesRead = 0;
@@ -1192,6 +1213,7 @@ class AudioSystem {
11921213
if (available == 0) {
11931214
if (s.filePosition < s.decoderLength) {
11941215
if (s.asyncState.nextBufferReady.load(std::memory_order_acquire)) {
1216+
s.justSeeked = false; // Clear flag before swapping
11951217
s.trySwapBuffers();
11961218
continue;
11971219
} else {
@@ -1220,9 +1242,14 @@ class AudioSystem {
12201242
}
12211243

12221244
s.localReadPos += toRead;
1223-
s.filePosition += toRead;
1245+
s.filePosition.store(s.filePosition.load(std::memory_order_relaxed) + toRead, std::memory_order_relaxed);
12241246
framesRead += toRead;
12251247

1248+
// Clear the justSeeked flag after first successful read
1249+
if (framesRead > 0) {
1250+
s.justSeeked = false;
1251+
}
1252+
12261253
if (s.filePosition >= s.decoderLength) {
12271254
s.active = false;
12281255
break;

src/structures/gameplay/PlayField.hx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ class PlayField implements State {
261261

262262
if (!died) {
263263
Mixer.update(this, deltaTime);
264+
Sys.println(songPosition);
264265

265266
#if !FV_LIME_FORK
266267
// If the song hasn't started yet, update the countdown conductor only.

0 commit comments

Comments
 (0)