More progress on audio and rendering
This commit is contained in:
112
util/atlas.c
112
util/atlas.c
@@ -7,50 +7,97 @@
|
||||
|
||||
SDL_Texture *atlasTexture;
|
||||
|
||||
int atlasX = 0, atlasY = 0;
|
||||
int tileIndex16 = 0, quadrantIndex16 = 0;
|
||||
int tileIndex32 = 0;
|
||||
|
||||
int tileIndex = 0; // Which 32x32 tile we're on
|
||||
int quadrantIndex = 0; // Which 16x16 slot inside that tile
|
||||
#define MAX_RECTS 256
|
||||
int allocatedRectCount = 0;
|
||||
SDL_Rect allocatedRects[MAX_RECTS];
|
||||
|
||||
|
||||
bool isIntersecting(SDL_Rect a) {
|
||||
for (int i = 0; i < allocatedRectCount; i++) {
|
||||
SDL_Rect b = allocatedRects[i];
|
||||
if (SDL_HasIntersection(&a, &b)) {
|
||||
printf("Rect intersection %d - X:%d, Y: %d, W: %d, H: %d with X:%d, Y: %d, W: %d, H: %d\n",
|
||||
allocatedRectCount, a.x, a.y, a.w, a.h, b.x, b.y, b.w, b.h);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void storeRect(SDL_Rect rect) {
|
||||
if (isIntersecting(rect)) {
|
||||
printf("PROBLEM\n");
|
||||
}
|
||||
if (allocatedRectCount < MAX_RECTS) {
|
||||
allocatedRects[allocatedRectCount++] = rect;
|
||||
} else {
|
||||
fprintf(stderr, "Error: atlas rect limit reached!\n");
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Rect allocate_16x16(SDL_Texture *srcTexture, SDL_Renderer *renderer) {
|
||||
SDL_Texture * oldTarget = SDL_GetRenderTarget(renderer);
|
||||
SDL_Texture *oldTarget = SDL_GetRenderTarget(renderer);
|
||||
SDL_SetRenderTarget(renderer, atlasTexture);
|
||||
int tileX = tileIndex % ATLAS_TILES_PER_ROW;
|
||||
int tileY = tileIndex / ATLAS_TILES_PER_ROW;
|
||||
|
||||
int dx = (quadrantIndex % 2) * QUADRANT_SIZE;
|
||||
int dy = (quadrantIndex / 2) * QUADRANT_SIZE;
|
||||
|
||||
SDL_Rect destRect = {
|
||||
tileX * TILE_SIZE + dx,
|
||||
tileY * TILE_SIZE + dy,
|
||||
SDL_Rect sourceRect = {
|
||||
0,
|
||||
0,
|
||||
QUADRANT_SIZE,
|
||||
QUADRANT_SIZE
|
||||
};
|
||||
SDL_RenderCopy(renderer, srcTexture, NULL, &destRect);
|
||||
|
||||
quadrantIndex++;
|
||||
if (quadrantIndex >= 4) {
|
||||
tileIndex++;
|
||||
quadrantIndex = 0;
|
||||
SDL_Rect destRect;
|
||||
while (1) {
|
||||
int tileX = tileIndex16 % ATLAS_TILES_PER_ROW;
|
||||
int tileY = tileIndex16 / ATLAS_TILES_PER_ROW;
|
||||
|
||||
int dx = (quadrantIndex16 % 2) * QUADRANT_SIZE;
|
||||
int dy = (quadrantIndex16 / 2) * QUADRANT_SIZE;
|
||||
destRect.x = tileX * TILE_SIZE + dx;
|
||||
destRect.y = tileY * TILE_SIZE + dy;
|
||||
destRect.w = QUADRANT_SIZE;
|
||||
destRect.h = QUADRANT_SIZE;
|
||||
if (isIntersecting(destRect)) {
|
||||
tileIndex16 = tileIndex32;
|
||||
tileIndex32++;
|
||||
quadrantIndex16 = 0;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SDL_RenderCopy(renderer, srcTexture, &sourceRect, &destRect);
|
||||
|
||||
quadrantIndex16++;
|
||||
if (quadrantIndex16 >= 4) {
|
||||
tileIndex16 = tileIndex32;
|
||||
tileIndex32 += 1;
|
||||
quadrantIndex16 = 0;
|
||||
|
||||
// Ensure 32x32 allocator skips this tile
|
||||
if (tileIndex32 <= tileIndex16) {
|
||||
tileIndex32 = tileIndex16 + 1;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_SetRenderTarget(renderer, oldTarget);
|
||||
storeRect(destRect);
|
||||
printf("Rect X:%d, Y: %d, W: %d, H: %d\n", destRect.x, destRect.y, destRect.w, destRect.h);
|
||||
return destRect;
|
||||
}
|
||||
|
||||
SDL_Rect allocate_32x32(SDL_Texture *srcTexture, SDL_Renderer *renderer) {
|
||||
SDL_Texture * oldTarget = SDL_GetRenderTarget(renderer);
|
||||
SDL_Texture *oldTarget = SDL_GetRenderTarget(renderer);
|
||||
SDL_SetRenderTarget(renderer, atlasTexture);
|
||||
// If we’re not at the start of a tile, skip to the next clean one
|
||||
if (quadrantIndex != 0) {
|
||||
tileIndex++;
|
||||
quadrantIndex = 0;
|
||||
}
|
||||
|
||||
int tileX = tileIndex % ATLAS_TILES_PER_ROW;
|
||||
int tileY = tileIndex / ATLAS_TILES_PER_ROW;
|
||||
int tileX = tileIndex32 % ATLAS_TILES_PER_ROW;
|
||||
int tileY = tileIndex32 / ATLAS_TILES_PER_ROW;
|
||||
|
||||
SDL_Rect destRect = {
|
||||
tileX * TILE_SIZE,
|
||||
@@ -58,23 +105,30 @@ SDL_Rect allocate_32x32(SDL_Texture *srcTexture, SDL_Renderer *renderer) {
|
||||
TILE_SIZE,
|
||||
TILE_SIZE
|
||||
};
|
||||
|
||||
SDL_RenderCopy(renderer, srcTexture, NULL, &destRect);
|
||||
|
||||
tileIndex++; // Move to next tile
|
||||
// quadrantIndex stays 0 — new tile is fresh
|
||||
tileIndex32++;
|
||||
|
||||
SDL_SetRenderTarget(renderer, oldTarget);
|
||||
storeRect(destRect);
|
||||
printf("Rect X:%d, Y: %d, W: %d, H: %d\n", destRect.x, destRect.y, destRect.w, destRect.h);
|
||||
return destRect;
|
||||
}
|
||||
|
||||
void initAtlas(SDL_Renderer *renderer) {
|
||||
|
||||
SDL_Texture *oldTarget = SDL_GetRenderTarget(renderer);
|
||||
|
||||
atlasTexture = SDL_CreateTexture(renderer,
|
||||
SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET,
|
||||
ATLAS_SIZE, ATLAS_SIZE);
|
||||
// Clear atlas with transparent
|
||||
SDL_SetRenderTarget(renderer, atlasTexture);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
SDL_SetRenderTarget(renderer, oldTarget);
|
||||
|
||||
|
||||
atlasTexture = SDL_CreateTexture(renderer,
|
||||
SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET,
|
||||
ATLAS_SIZE, ATLAS_SIZE);
|
||||
}
|
213
util/audio.c
213
util/audio.c
@@ -6,6 +6,11 @@
|
||||
|
||||
AudioData audioData;
|
||||
|
||||
#define MAX_MIDI_EVENTS 1024
|
||||
MidiEvent midiEvents[MAX_MIDI_EVENTS];
|
||||
int midiEventCount = 0;
|
||||
int nextMidiEvent = 0;
|
||||
|
||||
uint16_t getAvailableChannel() {
|
||||
for (uint16_t i = 0; i < NUM_SYNTH_VOICES; i++) {
|
||||
if (audioData.synthVoices[i].volume == 0) {
|
||||
@@ -39,93 +44,209 @@ static void compute_stereo_gains(float pan, float *outL, float *outR) {
|
||||
// e.g. *outL *= 0.7071f; *outR *= 0.7071f;
|
||||
}
|
||||
|
||||
// This callback now writes stereo frames: interleaved L/R floats.
|
||||
// Improved audio callback with anti-clipping and smooth fade-out
|
||||
void audio_callback(void *userdata, Uint8 *stream, int len) {
|
||||
AudioData *audio = (AudioData *) userdata;
|
||||
|
||||
// 'len' is total bytes; each sample‐frame is 2 floats (L+R), i.e. 2 * sizeof(float).
|
||||
int frames = len / (2 * sizeof(float));
|
||||
int frames = len / (2 * sizeof(float)); // Stereo frame count
|
||||
|
||||
float elapsedSec = audio->totalSamples / SAMPLE_RATE;
|
||||
audio->totalSamples += frames;
|
||||
|
||||
while (nextMidiEvent < midiEventCount &&
|
||||
midiEvents[nextMidiEvent].timeSec <= elapsedSec) {
|
||||
|
||||
MidiEvent *ev = &midiEvents[nextMidiEvent];
|
||||
|
||||
if (ev->type == 0 && ev->velocity > 0) {
|
||||
// Note On
|
||||
for (int i = NUM_SYNTH_VOICES - 4; i < NUM_SYNTH_VOICES; ++i) {
|
||||
SynthVoice *v = &audio->synthVoices[i];
|
||||
if (v->volume == 0) {
|
||||
float freq = 440.0f * powf(2.0f, (ev->note - 69) / 12.0f);
|
||||
v->frequency = (uint16_t) freq;
|
||||
v->volume = ev->velocity * 2;
|
||||
v->waveform = WAVE_SQUARE;
|
||||
v->smoothedAmp = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Note Off
|
||||
for (int i = NUM_SYNTH_VOICES - 4; i < NUM_SYNTH_VOICES; ++i) {
|
||||
SynthVoice *v = &audio->synthVoices[i];
|
||||
float freq = 440.0f * powf(2.0f, (ev->note - 69) / 12.0f);
|
||||
if ((uint16_t)freq == v->frequency) {
|
||||
v->volume = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextMidiEvent++;
|
||||
}
|
||||
|
||||
// Zero out the entire output buffer (silence)
|
||||
// We’ll accumulate into it.
|
||||
// Each float is 4 bytes, so total floats = 2 * frames.
|
||||
float *outBuf = (float *) stream;
|
||||
for (int i = 0; i < 2 * frames; ++i) {
|
||||
outBuf[i] = 0.0f;
|
||||
}
|
||||
|
||||
// Precompute the listener center
|
||||
float listenerCx = audio->playerRect->x + audio->playerRect->w * 0.5f;
|
||||
|
||||
// For each synth voice, mix into the stereo buffer
|
||||
int *voiceCounts = calloc(frames, sizeof(int));
|
||||
|
||||
for (int v = 0; v < NUM_SYNTH_VOICES; v++) {
|
||||
SynthVoice *voice = &audio->synthVoices[v];
|
||||
if (voice->volume == 0 || voice->frequency == 0) {
|
||||
continue; // skip silent or inactive voices
|
||||
}
|
||||
|
||||
// Compute source center X
|
||||
float sourceCx = voice->sourceRect.x + voice->sourceRect.w * 0.5f;
|
||||
if ((voice->volume == 0 && voice->smoothedAmp < 0.001f) || voice->frequency == 0)
|
||||
continue;
|
||||
|
||||
float sourceCx = voice->sourceRect.x + TILE_SIZE * 0.5f;
|
||||
float dx = sourceCx - listenerCx;
|
||||
|
||||
// Normalize for pan. If |dx| >= maxPanDistance → full left or full right.
|
||||
float pan = dx / audio->maxPanDistance;
|
||||
if (pan < -1.0f) pan = -1.0f;
|
||||
if (pan > +1.0f) pan = +1.0f;
|
||||
float pan = fmaxf(-1.0f, fminf(+1.0f, dx / audio->maxPanDistance));
|
||||
|
||||
float gainL, gainR;
|
||||
compute_stereo_gains(pan, &gainL, &gainR);
|
||||
gainL *= 0.7071f;
|
||||
gainR *= 0.7071f;
|
||||
|
||||
// Optional: You could also attenuate overall volume with distance
|
||||
// float dist = fabsf(dx);
|
||||
// float distanceAtten = 1.0f - fminf(dist / audio->maxPanDistance, 1.0f);
|
||||
// float finalVolume = (voice->volume / 255.0f) * distanceAtten;
|
||||
// But for now, we’ll just use voice->volume for amplitude.
|
||||
float dist = fabsf(dx);
|
||||
float distanceAtten = 1.0f - fminf(dist / audio->maxPanDistance, 1.0f);
|
||||
float targetAmp = (voice->volume / 255.0f) * distanceAtten;
|
||||
|
||||
float amp = (voice->volume / 255.0f);
|
||||
double phaseInc = ((double) voice->frequency * 256.0) / (double) SAMPLE_RATE;
|
||||
|
||||
// Phase increment per sample‐frame:
|
||||
// (freq * 256) / SAMPLE_RATE tells how many phase steps per mono-sample.
|
||||
// Because we’re writing stereo, we still advance phase once per frame.
|
||||
uint8_t phaseInc = (uint8_t)((voice->frequency * 256) / SAMPLE_RATE);
|
||||
|
||||
// Mix into each frame
|
||||
for (int i = 0; i < frames; i++) {
|
||||
float t = (float) voice->phase / 255.0f * 2.0f - 1.0f;
|
||||
voice->smoothedAmp += (targetAmp - voice->smoothedAmp) * SMOOTHING_FACTOR;
|
||||
float amp = voice->smoothedAmp;
|
||||
|
||||
double norm = voice->phase / 256.0;
|
||||
double t = norm * 2.0 - 1.0;
|
||||
float sample;
|
||||
|
||||
switch (voice->waveform) {
|
||||
default:
|
||||
case WAVE_SINE:
|
||||
sample = sinf(voice->phase * 2.0f * M_PI / 256.0f);
|
||||
break;
|
||||
case WAVE_SQUARE:
|
||||
sample = (t >= 0.0f) ? 1.0f : -1.0f;
|
||||
sample = (t >= 0.0) ? 1.0f : -1.0f;
|
||||
break;
|
||||
case WAVE_SAWTOOTH:
|
||||
sample = t;
|
||||
sample = (float) t;
|
||||
break;
|
||||
case WAVE_TRIANGLE:
|
||||
sample = (t < 0.0f) ? -t : t;
|
||||
sample = (float) ((t < 0.0) ? -t : t);
|
||||
break;
|
||||
case WAVE_NOISE:
|
||||
sample = ((float) rand() / RAND_MAX) * 2.0f - 1.0f;
|
||||
sample = ((float) rand() / (float) RAND_MAX) * 2.0f - 1.0f;
|
||||
break;
|
||||
default:
|
||||
sample = (float) sin(norm * 2.0 * M_PI);
|
||||
break;
|
||||
}
|
||||
|
||||
voice->phase += phaseInc;
|
||||
if (voice->phase >= 256.0) voice->phase -= 256.0;
|
||||
else if (voice->phase < 0.0) voice->phase += 256.0;
|
||||
|
||||
// Interleaved index: left = 2*i, right = 2*i + 1
|
||||
int idxL = 2 * i;
|
||||
int idxR = 2 * i + 1;
|
||||
|
||||
// Accumulate into buffer
|
||||
outBuf[idxL] += sample * amp * gainL;
|
||||
outBuf[idxR] += sample * amp * gainR;
|
||||
voiceCounts[i]++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < frames; ++i) {
|
||||
int count = voiceCounts[i];
|
||||
if (count > 0) {
|
||||
outBuf[2 * i + 0] /= count;
|
||||
outBuf[2 * i + 1] /= count;
|
||||
}
|
||||
}
|
||||
free(voiceCounts);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static uint32_t read_be_uint32(const uint8_t *data) {
|
||||
return (data[0]<<24) | (data[1]<<16) | (data[2]<<8) | data[3];
|
||||
}
|
||||
|
||||
static uint16_t read_be_uint16(const uint8_t *data) {
|
||||
return (data[0]<<8) | data[1];
|
||||
}
|
||||
|
||||
static uint32_t read_vlq(const uint8_t **ptr) {
|
||||
uint32_t value = 0;
|
||||
const uint8_t *p = *ptr;
|
||||
while (*p & 0x80) {
|
||||
value = (value << 7) | (*p++ & 0x7F);
|
||||
}
|
||||
value = (value << 7) | (*p++ & 0x7F);
|
||||
*ptr = p;
|
||||
return value;
|
||||
}
|
||||
|
||||
void load_midi_file(const char *path) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long size = ftell(f);
|
||||
rewind(f);
|
||||
|
||||
uint8_t *data = malloc(size);
|
||||
fread(data, 1, size, f);
|
||||
fclose(f);
|
||||
|
||||
const uint8_t *ptr = data;
|
||||
if (memcmp(ptr, "MThd", 4) != 0) return;
|
||||
ptr += 8; // skip header length
|
||||
uint16_t format = read_be_uint16(ptr); ptr += 2;
|
||||
uint16_t nTracks = read_be_uint16(ptr); ptr += 2;
|
||||
uint16_t ppqn = read_be_uint16(ptr); ptr += 2;
|
||||
|
||||
if (format != 0 || nTracks != 1) {
|
||||
printf("Only Type 0 MIDI supported\n");
|
||||
free(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp(ptr, "MTrk", 4) != 0) return;
|
||||
uint32_t trackLen = read_be_uint32(ptr+4);
|
||||
ptr += 8;
|
||||
const uint8_t *trackEnd = ptr + trackLen;
|
||||
|
||||
float curTime = 0.0f;
|
||||
uint32_t tempo = 500000; // default: 120 BPM
|
||||
uint8_t lastStatus = 0;
|
||||
|
||||
while (ptr < trackEnd && midiEventCount < MAX_MIDI_EVENTS) {
|
||||
uint32_t delta = read_vlq(&ptr);
|
||||
curTime += (delta * (tempo / 1000000.0f)) / ppqn;
|
||||
|
||||
uint8_t status = *ptr;
|
||||
if (status < 0x80) status = lastStatus;
|
||||
else ptr++;
|
||||
|
||||
lastStatus = status;
|
||||
|
||||
if (status == 0xFF) {
|
||||
uint8_t metaType = *ptr++;
|
||||
uint32_t len = read_vlq(&ptr);
|
||||
if (metaType == 0x51 && len == 3) {
|
||||
tempo = (ptr[0]<<16 | ptr[1]<<8 | ptr[2]);
|
||||
}
|
||||
ptr += len;
|
||||
} else if ((status & 0xF0) == 0x90 || (status & 0xF0) == 0x80) {
|
||||
uint8_t note = *ptr++;
|
||||
uint8_t vel = *ptr++;
|
||||
midiEvents[midiEventCount++] = (MidiEvent){
|
||||
.timeSec = curTime,
|
||||
.type = (status & 0xF0) == 0x90 ? 0 : 1,
|
||||
.note = note,
|
||||
.velocity = vel
|
||||
};
|
||||
} else {
|
||||
ptr += 2; // skip unknown
|
||||
}
|
||||
}
|
||||
|
||||
// Note: We did not normalize by active voices here, because each voice already
|
||||
// uses its own volume. If you still want an automatic “divide by N active voices”,
|
||||
// you would need to track active voices per‐frame, which is relatively expensive.
|
||||
// In practice, you manage the volume per voice so clipping doesn’t occur.
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
|
17
util/audio.h
17
util/audio.h
@@ -9,9 +9,18 @@
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "../tiles/tile.h"
|
||||
|
||||
#define SAMPLE_RATE 44100
|
||||
#define NUM_SYNTH_VOICES 256
|
||||
#define SMOOTHING_FACTOR 0.001f
|
||||
|
||||
typedef struct {
|
||||
float timeSec; // When to trigger this event
|
||||
uint8_t type; // 0 = Note On, 1 = Note Off
|
||||
uint8_t note;
|
||||
uint8_t velocity;
|
||||
} MidiEvent;
|
||||
|
||||
typedef enum Waveform {
|
||||
WAVE_SINE,
|
||||
@@ -23,16 +32,18 @@ typedef enum Waveform {
|
||||
|
||||
typedef struct SynthVoice {
|
||||
Waveform waveform;
|
||||
uint8_t phase;
|
||||
double phase;
|
||||
uint16_t frequency;
|
||||
uint8_t volume;
|
||||
SDL_Rect sourceRect;
|
||||
MiniRect sourceRect;
|
||||
float smoothedAmp; // a float that holds the exponentially smoothed amplitude
|
||||
} SynthVoice;
|
||||
|
||||
typedef struct AudioData {
|
||||
SynthVoice synthVoices[NUM_SYNTH_VOICES];
|
||||
SDL_Rect *playerRect;
|
||||
float maxPanDistance;
|
||||
uint64_t totalSamples;
|
||||
} AudioData;
|
||||
|
||||
extern AudioData audioData;
|
||||
@@ -41,4 +52,6 @@ void audio_callback(void *userdata, Uint8 *stream, int len);
|
||||
|
||||
uint16_t getAvailableChannel();
|
||||
|
||||
void load_midi_file(const char *path);
|
||||
|
||||
#endif //RISCB_AUDIO_H
|
||||
|
@@ -46,7 +46,7 @@ void renderText(SDL_Renderer *renderer, BitmapFont font, char *string, uint16_t
|
||||
string++;
|
||||
continue;
|
||||
}
|
||||
SDL_RenderCopy(renderer, font.texture[*string], &charRect, &outRect);
|
||||
SDL_RenderCopy(renderer, font.texture[*string], &charRect, &outRect); //TODO CONSIDER FONTS IN ONE ATLAS
|
||||
outRect.x += charRect.w + 1;
|
||||
string++;
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ volatile bool running = true;
|
||||
|
||||
bool debugMode = false;
|
||||
bool itemViewing = false;
|
||||
bool renderAtlas = false;
|
||||
|
||||
//The surface contained by the window
|
||||
SDL_Renderer *mainRenderer = NULL;
|
||||
|
@@ -30,6 +30,7 @@ typedef enum OrientDirection{
|
||||
|
||||
extern bool debugMode;
|
||||
extern bool itemViewing;
|
||||
extern bool renderAtlas;
|
||||
|
||||
SDL_Texture *createRotatedTexture(SDL_Renderer *renderer, SDL_Texture *src, double angle);
|
||||
|
||||
|
Reference in New Issue
Block a user