Skip to content

Instantly share code, notes, and snippets.

@igormorgado
Last active Nov 6, 2020
Embed
What would you like to do?
A hmh sample with bug in audio

To compile:

make

To run

./hmh

#include "game.h"
#include "sdl_game.h"
internal inline u32
safe_truncate_u64(u64 Value)
{
/* TODO: Assert (value <= 0xFFFFFFFF) */
u32 Result = (u32)Value;
return Result;
}
internal i32
GameOutputSound(struct game_sound_output_buffer *SoundBuffer, f32 ToneHz)
{
local_persist f32 tSine;
i16 amplitude = 1000;
f32 wave_period = SoundBuffer->samples_per_second / ToneHz;
i16 *sample_out = SoundBuffer->samples;
for(size_t i = 0; i < SoundBuffer->sample_count; ++i)
{
f32 sine_value = sinf(tSine);
i16 sample_value = (i16)(sine_value * amplitude);
*sample_out++ = sample_value;
*sample_out++ = sample_value;
tSine += TAU / wave_period;
if(tSine > TAU) tSine -= TAU;
}
return RETURN_SUCCESS;
}
internal void
GameGetSoundSamples(struct game_sound_output_buffer *SoundBuffer)
{
GameOutputSound(SoundBuffer, 512);
}
#ifndef __GAME_H__
#define __GAME_H__
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <x86intrin.h>
#define PI 3.14159265358979323846f /* pi */
#define TAU 6.28318530717958623199f /* Tau = 2*pi */
#define Align8(Value) ((Value + 7) & ~7)
#define Align16(Value) ((Value + 15) & ~15)
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef unsigned int uint;
typedef float f32;
typedef double f64;
#define internal static
#define local_persist static
#define global_variable static
enum RETURN_STATUS
{
RETURN_FAILURE = -1,
RETURN_SUCCESS = 0,
RETURN_EXIT = 255
};
struct game_sound_output_buffer
{
int samples_per_second;
size_t sample_count;
i16 *samples;
};
internal void GameGetSoundSamples(struct game_sound_output_buffer *SoundBuffer);
#endif /* __GAME_H__ */
all:
gcc -o hmh sdl_game.c -I. $(shell pkg-config --libs --cflags sdl2) -lm
clean:
rm -f hmh
#include "game.h"
#include "game.c"
#include "sdl_game.h"
global_variable struct sdl_audio_ring_buffer GlobalAudioRingBuffer;
internal void
sdl_audio_callback(void *user_data, u8 *audio_data, int length)
{
struct sdl_audio_ring_buffer *ring_buffer = (struct sdl_audio_ring_buffer *)user_data;
i32 region_1_size = length;
i32 region_2_size = 0;
/* Not enough space on ring buffer region */
if ( (ring_buffer->play_cursor + length) > ring_buffer->size)
{
region_1_size = ring_buffer->size - ring_buffer->play_cursor;
region_2_size = length - region_1_size;
}
memcpy(audio_data, (u8*)(ring_buffer->data) + ring_buffer->play_cursor, region_1_size);
memcpy(audio_data + region_1_size, ring_buffer->data, region_2_size);
ring_buffer->play_cursor = (ring_buffer->play_cursor + length) % ring_buffer->size;
ring_buffer->write_cursor = (ring_buffer->play_cursor + length) % ring_buffer->size;
}
internal void
sdl_init_audio(struct sdl_sound_output *sound_output)
{
SDL_AudioSpec audio_settings = {};
audio_settings.freq = sound_output->samples_per_second;
audio_settings.format = AUDIO_S16LSB;
audio_settings.channels = 2;
audio_settings.samples = 512;
audio_settings.callback = &sdl_audio_callback;
audio_settings.userdata = &GlobalAudioRingBuffer;
GlobalAudioRingBuffer.size = sound_output->secondary_buffer_size;
GlobalAudioRingBuffer.play_cursor = 0;
GlobalAudioRingBuffer.write_cursor = 0;
GlobalAudioRingBuffer.data = mmap(NULL,
sound_output->secondary_buffer_size,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE,
-1,
0);
if(!GlobalAudioRingBuffer.data)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"%s: mmap(GlobalAudioRingBuffer.data)\n", __func__);
exit(EXIT_FAILURE);
}
sound_output->audio_device = SDL_OpenAudioDevice(NULL, 0, &audio_settings, NULL, 0);
SDL_Log("%s: Audio opened ID (%d): freq: %d, channels: %d, samples: %d, bufsz: %d\n",
__func__,
sound_output->audio_device,
audio_settings.freq,
audio_settings.channels,
audio_settings.samples,
audio_settings.size);
if (audio_settings.format != AUDIO_S16LSB)
{
SDL_LogError (SDL_LOG_CATEGORY_APPLICATION,
"%s: SDL_OpenAudio(): Could not get S16LE format\n",
__func__);
SDL_CloseAudioDevice(sound_output->audio_device);
sound_output->audio_device = 0;
}
SDL_PauseAudioDevice(sound_output->audio_device, SDL_FALSE);
}
internal void
sdl_SoundGetCursors(struct sdl_audio_ring_buffer *ring_buffer,
struct sdl_sound_output *sound_output,
int *byte_to_lock, int *bytes_to_write)
{
SDL_LockAudioDevice(sound_output->audio_device);
*byte_to_lock = (sound_output->running_sample_index * sound_output->bytes_per_sample) % sound_output->secondary_buffer_size;
int target_cursor = 0;
target_cursor = (ring_buffer->play_cursor + (sound_output->safety_bytes * sound_output->bytes_per_sample)) % sound_output->secondary_buffer_size;
if(*byte_to_lock > target_cursor)
{
*bytes_to_write = (sound_output->secondary_buffer_size - *byte_to_lock);
*bytes_to_write += target_cursor;
}
else
{
*bytes_to_write = target_cursor - *byte_to_lock;
}
SDL_UnlockAudioDevice(sound_output->audio_device);
}
internal void
sdl_FillSoundBuffer(struct sdl_sound_output *sound_output,
int byte_to_lock,
int bytes_to_write,
struct game_sound_output_buffer *sound_buffer)
{
i16 *samples = sound_buffer->samples;
i16 *sample_out = NULL;
/* Calculate region sizes */
void *region1 = (u8*)GlobalAudioRingBuffer.data + byte_to_lock;
int region1_size = bytes_to_write;
if(region1_size + byte_to_lock > sound_output->secondary_buffer_size)
region1_size = sound_output->secondary_buffer_size - byte_to_lock;
void *region2 = GlobalAudioRingBuffer.data;
int region2_size = bytes_to_write - region1_size;
/* Fill region 1 */
int region1_sample_count = region1_size / sound_output->bytes_per_sample;
sample_out = (i16*)region1;
for (int i = 0; i < region1_sample_count; ++i)
{
*sample_out++ = *samples++; // L
*sample_out++ = *samples++; // R
++sound_output->running_sample_index;
}
/* Fill region 2 */
int region2_sample_count = region2_size / sound_output->bytes_per_sample;
sample_out = (i16*)region2;
for (int i = 0; i < region2_sample_count; ++i)
{
*sample_out++ = *samples; // L
*sample_out++ = *samples; // R
++sound_output->running_sample_index;
}
}
internal int
sdl_GetWindowRefreshRate(SDL_Window *Window)
{
SDL_DisplayMode Mode;
int DisplayIndex = SDL_GetWindowDisplayIndex(Window);
int DefaultRefreshRate = 60;
if (SDL_GetDesktopDisplayMode(DisplayIndex, &Mode) != 0)
return DefaultRefreshRate;
else if(Mode.refresh_rate == 0)
return DefaultRefreshRate;
else
return Mode.refresh_rate;
}
internal void
sdl_perf_init(struct sdl_performance_counters *perf)
{
//perf->game_update_hz = sdl_GetWindowRefreshRate(window);
perf->game_update_hz = 60;
perf->target_seconds_per_frame = 1.0f/(f64)(perf->game_update_hz);
perf->last_counter = SDL_GetPerformanceCounter();
perf->last_cycle_count = _rdtsc();
perf->performance_count_frequency = SDL_GetPerformanceFrequency();
}
internal SDL_Window *
sdl_InitGame(u16 screen_width, u16 screen_height)
{
int retval;
int sdl_flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO;
int wnd_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
retval = SDL_Init(sdl_flags);
if(retval < 0)
{
SDL_LogError (SDL_LOG_CATEGORY_APPLICATION,
"%s: SDL_Init(): %s\n",
__func__, SDL_GetError());
return NULL;
}
SDL_Window *window;
window = SDL_CreateWindow("HMH",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
screen_width,
screen_height,
wnd_flags);
if(!window)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"%s: SDL_CreateWindow(): %s\n",
__func__, SDL_GetError());
return NULL;
}
return window;
}
internal f64
sdl_GetSecondsElapsed(u64 OldCounter, u64 CurrentCounter, u64 PerfCountFrequency)
{
f64 result = ((f64)(CurrentCounter - OldCounter) / (f64)PerfCountFrequency);
return result;
}
internal void
sdl_WaitFrame(struct sdl_performance_counters *perf)
{
if(sdl_GetSecondsElapsed(perf->last_counter, SDL_GetPerformanceCounter(), perf->performance_count_frequency) < perf->target_seconds_per_frame)
{
i32 TimeToSleep = ((perf->target_seconds_per_frame - sdl_GetSecondsElapsed(perf->last_counter, SDL_GetPerformanceCounter(), perf->performance_count_frequency)) * 1000) - 1;
if (TimeToSleep > 0)
{
SDL_Delay(TimeToSleep);
}
while (sdl_GetSecondsElapsed(perf->last_counter, SDL_GetPerformanceCounter(), perf->performance_count_frequency) < perf->target_seconds_per_frame)
{
// Waiting...
}
}
perf->end_counter = SDL_GetPerformanceCounter();
}
internal void
sdl_EvalPerformance(struct sdl_performance_counters *perf)
{
perf->end_cycle_count = _rdtsc();
perf->counter_elapsed = perf->end_counter - perf->last_counter;
perf->cycles_elapsed = perf->end_cycle_count - perf->last_cycle_count;
perf->ms_per_frame = 1000.0 * (f64)(perf->counter_elapsed) / (f64)(perf->performance_count_frequency);
perf->fps = (f64)(perf->performance_count_frequency) / (f64)(perf->counter_elapsed);
perf->last_cycle_count = perf->end_cycle_count;
perf->last_counter = perf->end_counter;
}
int
main (void)
{
int exitval = EXIT_FAILURE;
int retval = RETURN_SUCCESS;
SDL_Window *window = sdl_InitGame(320, 240);
if(!window) goto __EXIT__;
struct sdl_performance_counters perf = {};
sdl_perf_init(&perf);
SDL_Log("Refresh rate is %d Hz\n", perf.game_update_hz);
SDL_Log("Target FPS is %f Hz\n", perf.target_seconds_per_frame);
struct sdl_sound_output sound_output = {};
sound_output.audio_device = 0;
sound_output.samples_per_second = 48000;
sound_output.channels = 2;
sound_output.bytes_per_sample = sizeof(i16) * sound_output.channels;
sound_output.secondary_buffer_size = sound_output.samples_per_second * sound_output.bytes_per_sample;
sound_output.running_sample_index = 0;
sound_output.safety_bytes = Align16((int)((f32)sound_output.secondary_buffer_size / ((f32)perf.game_update_hz / 3.0f)));
sdl_init_audio(&sound_output);
size_t max_possible_overrun_samples = 8;
size_t max_possible_overrun_bytes = max_possible_overrun_samples * sound_output.bytes_per_sample;
i16 *samples = (i16 *)mmap(NULL,
sound_output.secondary_buffer_size + max_possible_overrun_bytes,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE,
-1,
0);
bool running = true;
while(running == true)
{
SDL_Event event;
while(SDL_PollEvent(&event))
{
switch(event.type)
{
case SDL_QUIT: { running = false; break; }
default: break;
}
}
int byte_to_lock = 0;
int bytes_to_write = 0;
sdl_SoundGetCursors(&GlobalAudioRingBuffer,
&sound_output,
&byte_to_lock, &bytes_to_write);
struct game_sound_output_buffer sound_buffer = {0};
sound_buffer.samples_per_second = sound_output.samples_per_second;
sound_buffer.sample_count = Align8(bytes_to_write / sound_output.bytes_per_sample);
sound_buffer.samples = samples;
bytes_to_write = sound_buffer.sample_count * sound_output.bytes_per_sample;
GameGetSoundSamples(&sound_buffer);
sdl_FillSoundBuffer(&sound_output, byte_to_lock, bytes_to_write, &sound_buffer);
sdl_WaitFrame(&perf);
sdl_EvalPerformance(&perf);
}
exitval = EXIT_SUCCESS;
__EXIT__:
if (sound_output.audio_device > 0) {
munmap(GlobalAudioRingBuffer.data, sound_output.secondary_buffer_size);
munmap(samples, sound_output.secondary_buffer_size + max_possible_overrun_bytes);
SDL_CloseAudioDevice(sound_output.audio_device);
}
SDL_DestroyWindow(window);
SDL_Quit();
return exitval;
}
#ifndef __SDL_GAME_H__
#define __SDL_GAME_H__
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
/* TODO: USE SDL2 stdc implementations */
#include <SDL2/SDL.h>
/* TODO: SDL Assert configuration before include */
#include <SDL2/SDL_assert.h>
#include <assert.h>
/*
* Plataform dependant code
*/
struct sdl_audio_ring_buffer
{
size_t size;
size_t write_cursor;
size_t play_cursor;
void *data;
};
struct sdl_sound_output
{
SDL_AudioDeviceID audio_device;
int samples_per_second;
int channels;
int bytes_per_sample;
int secondary_buffer_size;
size_t running_sample_index;
int safety_bytes;
};
struct sdl_performance_counters
{
int game_update_hz;
f64 target_seconds_per_frame;
u64 performance_count_frequency;
u64 last_cycle_count;
u64 end_cycle_count;
u64 counter_elapsed;
u64 cycles_elapsed;
f64 ms_per_frame;
f64 fps; /* Frames per second */
f64 mcpf; /* ... ... per frame */
u64 last_counter;
u64 end_counter;
};
#endif /* __SDL_GAME_H__ */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment