diff --git a/include/SDL3/SDL_test_font.h b/include/SDL3/SDL_test_font.h index c5146fcad..fd6194aaa 100644 --- a/include/SDL3/SDL_test_font.h +++ b/include/SDL3/SDL_test_font.h @@ -38,7 +38,8 @@ extern "C" { /* Function prototypes */ -#define FONT_CHARACTER_SIZE 8 +extern int FONT_CHARACTER_SIZE; + #define FONT_LINE_HEIGHT (FONT_CHARACTER_SIZE + 2) /** diff --git a/src/test/SDL_test_font.c b/src/test/SDL_test_font.c index 6d36a4b4c..592055805 100644 --- a/src/test/SDL_test_font.c +++ b/src/test/SDL_test_font.c @@ -3126,11 +3126,12 @@ struct SDLTest_CharTextureCache */ static struct SDLTest_CharTextureCache *SDLTest_CharTextureCacheList; +int FONT_CHARACTER_SIZE = 8; + int SDLTest_DrawCharacter(SDL_Renderer *renderer, float x, float y, Uint32 c) { const Uint32 charWidth = FONT_CHARACTER_SIZE; const Uint32 charHeight = FONT_CHARACTER_SIZE; - const Uint32 charSize = FONT_CHARACTER_SIZE; SDL_FRect srect; SDL_FRect drect; int result; @@ -3149,8 +3150,8 @@ int SDLTest_DrawCharacter(SDL_Renderer *renderer, float x, float y, Uint32 c) */ srect.x = 0.0f; srect.y = 0.0f; - srect.w = (float)charWidth; - srect.h = (float)charHeight; + srect.w = 8.0f; + srect.h = 8.0f; /* * Setup destination rectangle @@ -3190,7 +3191,7 @@ int SDLTest_DrawCharacter(SDL_Renderer *renderer, float x, float y, Uint32 c) return -1; } - charpos = SDLTest_FontData + ci * charSize; + charpos = SDLTest_FontData + ci * 8; linepos = (Uint8 *)character->pixels; pitch = character->pitch; @@ -3221,6 +3222,8 @@ int SDLTest_DrawCharacter(SDL_Renderer *renderer, float x, float y, Uint32 c) if (cache->charTextureCache[ci] == NULL) { return -1; } + + SDL_SetTextureScaleMode(cache->charTextureCache[ci], SDL_SCALEMODE_NEAREST); } /* diff --git a/test/testaudiostreamdynamicresample.c b/test/testaudiostreamdynamicresample.c index a23c24a95..3094f4b11 100644 --- a/test/testaudiostreamdynamicresample.c +++ b/test/testaudiostreamdynamicresample.c @@ -24,24 +24,197 @@ #include #define SLIDER_WIDTH_PERC 500.f / 600.f -#define SLIDER_HEIGHT_PERC 100.f / 480.f +#define SLIDER_HEIGHT_PERC 70.f / 480.f static int done; static SDLTest_CommonState *state; -static int multiplier = 100; -static SDL_FRect slider_area; -static SDL_FRect slider_fill_area; static SDL_AudioSpec spec; static SDL_AudioStream *stream; static Uint8 *audio_buf = NULL; static Uint32 audio_len = 0; +static SDL_bool auto_loop = SDL_TRUE; +static SDL_bool auto_flush = SDL_TRUE; + +static Uint64 last_get_callback = 0; +static int last_get_amount = 0; + +typedef struct Slider +{ + SDL_FRect area; + SDL_bool changed; + char fmtlabel[64]; + float pos; + int type; + float min; + float mid; + float max; + float value; +} Slider; + +#define NUM_SLIDERS 3 +Slider sliders[NUM_SLIDERS]; +static int active_slider = -1; + +static void init_slider(int index, const char* fmtlabel, int type, float value, float min, float max) +{ + Slider* slider = &sliders[index]; + + slider->area.x = state->window_w * (1.f - SLIDER_WIDTH_PERC) / 2; + slider->area.y = state->window_h * (0.2f + (index * SLIDER_HEIGHT_PERC * 1.4f)); + slider->area.w = SLIDER_WIDTH_PERC * state->window_w; + slider->area.h = SLIDER_HEIGHT_PERC * state->window_h; + slider->changed = SDL_TRUE; + SDL_strlcpy(slider->fmtlabel, fmtlabel, SDL_arraysize(slider->fmtlabel)); + slider->type = type; + slider->min = min; + slider->max = max; + slider->value = value; + + if (slider->type == 0) { + slider->pos = (value - slider->min + 0.5f) / (slider->max - slider->min + 1.0f); + } else { + slider->pos = 0.5f; + slider->mid = value; + } +} + +static float lerp(float v0, float v1, float t) +{ + return (1 - t) * v0 + t * v1; +} + +static void draw_text(SDL_Renderer* renderer, int x, int y, const char* text) +{ + SDL_SetRenderDrawColor(renderer, 0xFD, 0xF6, 0xE3, 0xFF); + SDLTest_DrawString(renderer, (float) x, (float) y, text); +} + +static void draw_textf(SDL_Renderer* renderer, int x, int y, const char* fmt, ...) +{ + char text[256]; + va_list ap; + + va_start(ap, fmt); + SDL_vsnprintf(text, SDL_arraysize(text), fmt, ap); + va_end(ap); + + draw_text(renderer, x, y, text); +} + +static void queue_audio() +{ + Uint8* new_data = NULL; + int new_len = 0; + int retval = 0; + SDL_AudioSpec new_spec; + + new_spec.format = spec.format; + new_spec.channels = (int) sliders[2].value; + new_spec.freq = (int) sliders[1].value; + + SDL_Log("Converting audio from %i to %i", spec.freq, new_spec.freq); + + retval = retval ? retval : SDL_ConvertAudioSamples(&spec, audio_buf, audio_len, &new_spec, &new_data, &new_len); + retval = retval ? retval : SDL_SetAudioStreamFormat(stream, &new_spec, NULL); + retval = retval ? retval : SDL_PutAudioStreamData(stream, new_data, new_len); + + if (auto_flush) { + retval = retval ? retval : SDL_FlushAudioStream(stream); + } + + SDL_free(new_data); + + if (retval) { + SDL_Log("Failed to queue audio: %s", SDL_GetError()); + } else { + SDL_Log("Queued audio"); + } +} + +static void skip_audio(float amount) +{ + float speed; + SDL_AudioSpec dst_spec, new_spec; + int num_frames; + int retval = 0; + void* buf = NULL; + + SDL_LockAudioStream(stream); + + speed = SDL_GetAudioStreamSpeed(stream); + SDL_GetAudioStreamFormat(stream, NULL, &dst_spec); + + /* Gimme that crunchy audio */ + new_spec.format = SDL_AUDIO_S8; + new_spec.channels = 1; + new_spec.freq = 4000; + + SDL_SetAudioStreamSpeed(stream, 100.0f); + SDL_SetAudioStreamFormat(stream, NULL, &new_spec); + + num_frames = (int)(new_spec.freq * ((speed * amount) / 100.0f)); + buf = SDL_malloc(num_frames); + + if (buf != NULL) { + retval = SDL_GetAudioStreamData(stream, buf, num_frames); + SDL_free(buf); + } + + SDL_SetAudioStreamFormat(stream, NULL, &dst_spec); + SDL_SetAudioStreamSpeed(stream, speed); + + SDL_UnlockAudioStream(stream); + + if (retval >= 0) { + SDL_Log("Skipped %.2f seconds", amount); + } else { + SDL_Log("Failed to skip: %s", SDL_GetError()); + } +} + +static const char *AudioFmtToString(const SDL_AudioFormat fmt) +{ + switch (fmt) { + #define FMTCASE(x) case SDL_AUDIO_##x: return #x + FMTCASE(U8); + FMTCASE(S8); + FMTCASE(S16LSB); + FMTCASE(S16MSB); + FMTCASE(S32LSB); + FMTCASE(S32MSB); + FMTCASE(F32LSB); + FMTCASE(F32MSB); + #undef FMTCASE + } + return "?"; +} + +static const char *AudioChansToStr(const int channels) +{ + switch (channels) { + case 1: return "Mono"; + case 2: return "Stereo"; + case 3: return "2.1"; + case 4: return "Quad"; + case 5: return "4.1"; + case 6: return "5.1"; + case 7: return "6.1"; + case 8: return "7.1"; + default: break; + } + return "?"; +} + static void loop(void) { - int i; + int i, j; SDL_Event e; - int newmultiplier = multiplier; + SDL_FPoint p; + SDL_AudioSpec src_spec, dst_spec; + int available_bytes = 0; + float available_seconds = 0; while (SDL_PollEvent(&e)) { SDLTest_CommonEvent(state, &e, &done); @@ -50,61 +223,145 @@ static void loop(void) emscripten_cancel_main_loop(); } #endif - if (e.type == SDL_EVENT_MOUSE_MOTION) { - if (e.motion.state & SDL_BUTTON_LMASK) { - const SDL_FPoint p = { e.motion.x, e.motion.y }; - if (SDL_PointInRectFloat(&p, &slider_area)) { - const float w = SDL_roundf(p.x - slider_area.x); - slider_fill_area.w = w; - newmultiplier = ((int) ((w / slider_area.w) * 800.0f)) - 400; + if (e.type == SDL_EVENT_KEY_DOWN) { + SDL_Keycode sym = e.key.keysym.sym; + if (sym == SDLK_q) { + if (SDL_IsAudioDevicePaused(state->audio_id)) { + SDL_ResumeAudioDevice(state->audio_id); + } else { + SDL_PauseAudioDevice(state->audio_id); } + } else if (sym == SDLK_w) { + auto_loop = auto_loop ? SDL_FALSE : SDL_TRUE; + } else if (sym == SDLK_e) { + auto_flush = auto_flush ? SDL_FALSE : SDL_TRUE; + } else if (sym == SDLK_a) { + SDL_ClearAudioStream(stream); + SDL_Log("Cleared audio stream"); + } else if (sym == SDLK_s) { + queue_audio(); + } else if (sym == SDLK_d) { + float amount = 1.0f; + amount *= (e.key.keysym.mod & SDL_KMOD_CTRL) ? 10.0f : 1.0f; + amount *= (e.key.keysym.mod & SDL_KMOD_SHIFT) ? 10.0f : 1.0f; + skip_audio(amount); } } } - if (multiplier != newmultiplier) { - SDL_AudioSpec newspec; - char title[64]; - int newfreq = spec.freq; - float scale = 1.0f; - - multiplier = newmultiplier; - - if (multiplier < 0) { - scale = 100.0f / (100.0f - multiplier); - } else if (multiplier > 0) { - scale = (multiplier + 100.0f) / 100.0f; + if (SDL_GetMouseState(&p.x, &p.y) & SDL_BUTTON_LMASK) { + if (active_slider == -1) { + for (i = 0; i < NUM_SLIDERS; ++i) { + if (SDL_PointInRectFloat(&p, &sliders[i].area)) { + active_slider = i; + break; + } + } } - - SDL_snprintf(title, sizeof (title), "Drag the slider: %.2fx speed", scale); - - for (i = 0; i < state->num_windows; i++) { - SDL_SetWindowTitle(state->windows[i], title); - } - - newfreq = (int) (spec.freq * scale); - /* SDL_Log("newfreq=%d multiplier=%d\n", newfreq, multiplier); */ - SDL_memcpy(&newspec, &spec, sizeof (spec)); - newspec.freq = newfreq; - SDL_SetAudioStreamFormat(stream, &newspec, NULL); + } else { + active_slider = -1; } - /* keep it looping. */ - if (SDL_GetAudioStreamAvailable(stream) < ((int) (audio_len / 2))) { - SDL_PutAudioStreamData(stream, audio_buf, audio_len); + if (active_slider != -1) { + Slider* slider = &sliders[active_slider]; + + float value = (p.x - slider->area.x) / slider->area.w; + value = SDL_clamp(value, 0.0f, 1.0f); + slider->pos = value; + + if (slider->type == 0) { + value = slider->min + (value * (slider->max - slider->min + 1.0f)); + value = SDL_clamp(value, slider->min, slider->max); + } else { + value = (value * 2.0f) - 1.0f; + value = (value >= 0) + ? lerp(slider->mid, slider->max, value) + : lerp(slider->mid, slider->min, -value); + } + + if (value != slider->value) { + slider->value = value; + slider->changed = SDL_TRUE; + } + } + + if (sliders[0].changed) { + sliders[0].changed = SDL_FALSE; + SDL_SetAudioStreamSpeed(stream, sliders[0].value); + } + + if (SDL_GetAudioStreamFormat(stream, &src_spec, &dst_spec) == 0) { + available_bytes = SDL_GetAudioStreamAvailable(stream); + available_seconds = (float)available_bytes / (float)(SDL_AUDIO_BITSIZE(dst_spec.format) / 8 * dst_spec.freq * dst_spec.channels); + + /* keep it looping. */ + if (auto_loop && (available_seconds < 10.0f)) { + queue_audio(); + } } for (i = 0; i < state->num_windows; i++) { - SDL_SetRenderDrawColor(state->renderers[i], 0, 0, 255, 255); - SDL_RenderClear(state->renderers[i]); - SDL_SetRenderDrawColor(state->renderers[i], 0, 0, 0, 255); - SDL_RenderFillRect(state->renderers[i], &slider_area); - SDL_SetRenderDrawColor(state->renderers[i], 255, 0, 0, 255); - SDL_RenderFillRect(state->renderers[i], &slider_fill_area); - SDL_RenderPresent(state->renderers[i]); + int draw_y = 0; + SDL_Renderer* rend = state->renderers[i]; + + SDL_SetRenderDrawColor(rend, 0x00, 0x2B, 0x36, 0xFF); + SDL_RenderClear(rend); + + for (j = 0; j < NUM_SLIDERS; ++j) { + Slider* slider = &sliders[j]; + SDL_FRect area; + + SDL_copyp(&area, &slider->area); + + SDL_SetRenderDrawColor(rend, 0x07, 0x36, 0x42, 0xFF); + SDL_RenderFillRect(rend, &area); + + area.w *= slider->pos; + + SDL_SetRenderDrawColor(rend, 0x58, 0x6E, 0x75, 0xFF); + SDL_RenderFillRect(rend, &area); + + draw_textf(rend, (int)slider->area.x, (int)slider->area.y, slider->fmtlabel, slider->value); + } + + draw_textf(rend, 0, draw_y, "%7s, Loop: %3s, Flush: %3s", + SDL_IsAudioDevicePaused(state->audio_id) ? "Paused" : "Playing", auto_loop ? "On" : "Off", auto_flush ? "On" : "Off"); + draw_y += FONT_LINE_HEIGHT; + + draw_textf(rend, 0, draw_y, "Available: %4.2f (%i bytes)", available_seconds, available_bytes); + draw_y += FONT_LINE_HEIGHT; + + SDL_LockAudioStream(stream); + + draw_textf(rend, 0, draw_y, "Get Callback: %i bytes, %i ms ago", last_get_amount, (int)(SDL_GetTicks() - last_get_callback)); + draw_y += FONT_LINE_HEIGHT; + + SDL_UnlockAudioStream(stream); + + draw_y = state->window_h - FONT_LINE_HEIGHT * 3; + + draw_textf(rend, 0, draw_y, "Wav: %6s/%6s/%i", + AudioFmtToString(spec.format), AudioChansToStr(spec.channels), spec.freq); + draw_y += FONT_LINE_HEIGHT; + + draw_textf(rend, 0, draw_y, "Src: %6s/%6s/%i", + AudioFmtToString(src_spec.format), AudioChansToStr(src_spec.channels), src_spec.freq); + draw_y += FONT_LINE_HEIGHT; + + draw_textf(rend, 0, draw_y, "Dst: %6s/%6s/%i", + AudioFmtToString(dst_spec.format), AudioChansToStr(dst_spec.channels), dst_spec.freq); + draw_y += FONT_LINE_HEIGHT; + + SDL_RenderPresent(rend); } } +static void SDLCALL our_get_callback(void *userdata, SDL_AudioStream *strm, int approx_amount) +{ + last_get_callback = SDL_GetTicks(); + last_get_amount = approx_amount; +} + int main(int argc, char *argv[]) { char *filename = NULL; @@ -140,20 +397,14 @@ int main(int argc, char *argv[]) i += consumed; } - slider_area.x = state->window_w * (1.f - SLIDER_WIDTH_PERC) / 2; - slider_area.y = state->window_h * (1.f - SLIDER_HEIGHT_PERC) / 2; - slider_area.w = SLIDER_WIDTH_PERC * state->window_w; - slider_area.h = SLIDER_HEIGHT_PERC * state->window_h; - - slider_fill_area = slider_area; - slider_fill_area.w /= 2; - /* Load the SDL library */ if (!SDLTest_CommonInit(state)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError()); return 1; } + FONT_CHARACTER_SIZE = 16; + filename = GetResourceFilename(filename, "sample.wav"); rc = SDL_LoadWAV(filename, &spec, &audio_buf, &audio_len); SDL_free(filename); @@ -164,12 +415,17 @@ int main(int argc, char *argv[]) return 1; } + init_slider(0, "Speed: %3.2fx", 1, 1.0f, 0.2f, 5.0f); + init_slider(1, "Freq: %.0f", 1, (float)spec.freq, 4000.0f, 192000.0f); + init_slider(2, "Channels: %.0f", 0, (float)spec.channels, 1.0f, 8.0f); + for (i = 0; i < state->num_windows; i++) { - SDL_SetWindowTitle(state->windows[i], "Drag the slider: Normal speed"); + SDL_SetWindowTitle(state->windows[i], "Resampler Test"); } stream = SDL_CreateAudioStream(&spec, &spec); - SDL_PutAudioStreamData(stream, audio_buf, audio_len); + SDL_SetAudioStreamGetCallback(stream, our_get_callback, NULL); + SDL_BindAudioStream(state->audio_id, stream); #ifdef __EMSCRIPTEN__ @@ -183,7 +439,6 @@ int main(int argc, char *argv[]) SDL_DestroyAudioStream(stream); SDL_free(audio_buf); SDLTest_CommonQuit(state); - SDL_Quit(); return 0; }