More progress on audio and rendering

This commit is contained in:
2025-06-02 18:04:15 +02:00
parent 84805b92cb
commit 0c3e2aa730
14 changed files with 569 additions and 264 deletions

BIN
assets/audio/testaid.mid Normal file

Binary file not shown.

View File

@@ -16,103 +16,103 @@ uint16_t itemRegistryIndex = 0;
double speed = 1.0f / TILE_SIZE; // fraction of tile per tick
const int dirDx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};
const int dirDy[8] = {1, 0, -1, -1, -1, 0, 1, 1};
const float epsilon = 0.999f; // if we can't move, back it off just below 1
void updateItems() {
const int dirDx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};
const int dirDy[8] = {1, 0, -1, -1, -1, 0, 1, 1};
const float epsilon = 0.999f; // if we can't move, back it off just below 1
for (int i = 0; i < neededUpdates.activeCount; i++) {
int x = neededUpdates.tiles[i].x;
int y = neededUpdates.tiles[i].y;
Tile *t = &tileMap[y][x];
if (t->type == TYPE_AIR) continue;
TileTypeReg tt = TileRegistry[t->type];
int dir = t->direction;
for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) {
Tile *t = &tileMap[y][x];
TileTypeReg tt = TileRegistry[t->type];
if (t->type == TYPE_AIR) continue;
int dir = t->direction;
bool horz = (dir == ORIENT_LEFT || dir == ORIENT_RIGHT);
bool vert = (dir == ORIENT_UP || dir == ORIENT_DOWN);
bool horz = (dir == ORIENT_LEFT || dir == ORIENT_RIGHT);
bool vert = (dir == ORIENT_UP || dir == ORIENT_DOWN);
for (uint8_t lane = 0; lane < ItemSlotCount; lane++) {
if (!tt.outputLane[lane]) continue;
ItemOnBelt *itm = &t->items[lane];
if (itm->type == 0) continue;
if (tt.itemMoves) {
itm->offset += speed;
// 1) Advance
}
for (uint8_t lane = 0; lane < ItemSlotCount; lane++) {
if (!tt.outputLane[lane]) continue;
ItemOnBelt *itm = &t->items[lane];
if (itm->type == 0) continue;
// 2) Time to hop?
if (itm->offset >= 0.5f || !tt.itemMoves) {
if (tt.itemMoves) {
itm->offset += speed;
// 1) Advance
itm->offset -= 1.0f;
}
// 2) Time to hop?
if (itm->offset >= 0.5f || !tt.itemMoves) {
// target coords
int nx = x + dirDx[dir];
int ny = y + dirDy[dir];
// bounds & belt?
if (nx < 0 || nx >= MAP_WIDTH || ny < 0 || ny >= MAP_HEIGHT) {
if (tt.itemMoves) {
itm->offset -= 1.0f;
itm->offset += 1.0f - speed;
}
// target coords
int nx = x + dirDx[dir];
int ny = y + dirDy[dir];
// bounds & belt?
if (nx < 0 || nx >= MAP_WIDTH || ny < 0 || ny >= MAP_HEIGHT) {
if (tt.itemMoves) {
itm->offset += 1.0f - speed;
}
continue;
}
Tile *next = &tileMap[ny][nx];
TileTypeReg ntt = TileRegistry[next->type];
int newLane = lane;
switch (next->type) {
case TYPE_BELT:
int newDir = next->direction;
bool nH = (newDir == ORIENT_LEFT || newDir == ORIENT_RIGHT);
bool nV = (newDir == ORIENT_UP || newDir == ORIENT_DOWN);
if ((horz && nH) || (vert && nV)) {
// same axis → keep lane
} else if (horz && nV) {
// came off a horizontal: lane0=top→vertical.left, lane1=bottom→vertical.right
newLane = (dir == ORIENT_RIGHT ^ newDir == ORIENT_UP ? 0 : 1);
itm->offset = 0.0f;
} else if (vert && nH) {
// came off vertical: lane0=left→horizontal.top, lane1=right→horizontal.bottom
newLane = (dir == ORIENT_UP ^ newDir == ORIENT_RIGHT ? 1 : 0);
itm->offset = 0.0f;
}
// (diagonals fall back to same-lane)
// Find a free slot in
break;
default:
itm->offset += 1.0f - speed;
}
if (next->items[newLane].type == 0 && ntt.allowedInItems[newLane][itm->type]) {
// MOVE it
ItemOnBelt moved = *itm;
moved.tileX = nx;
moved.tileY = ny;
if (!ntt.itemMoves) {
moved.offset = 0.5f;
}
next->items[newLane] = moved;
// clear this one
itm->type = 0;
} else {
// both slots full → wait at end
itm->offset = epsilon;
}
} else {
continue;
}
Tile *next = &tileMap[ny][nx];
TileTypeReg ntt = TileRegistry[next->type];
int newLane = lane;
switch (next->type) {
case TYPE_BELT:
int newDir = next->direction;
bool nH = (newDir == ORIENT_LEFT || newDir == ORIENT_RIGHT);
bool nV = (newDir == ORIENT_UP || newDir == ORIENT_DOWN);
if ((horz && nH) || (vert && nV)) {
// same axis → keep lane
} else if (horz && nV) {
// came off a horizontal: lane0=top→vertical.left, lane1=bottom→vertical.right
newLane = (dir == ORIENT_RIGHT ^ newDir == ORIENT_UP ? 0 : 1);
itm->offset = 0.0f;
} else if (vert && nH) {
// came off vertical: lane0=left→horizontal.top, lane1=right→horizontal.bottom
newLane = (dir == ORIENT_UP ^ newDir == ORIENT_RIGHT ? 1 : 0);
itm->offset = 0.0f;
}
// (diagonals fall back to same-lane)
// Find a free slot in
break;
default:
itm->offset += 1.0f - speed;
}
if (next->items[newLane].type == 0 && ntt.allowedInItems[newLane][itm->type]) {
// MOVE it
ItemOnBelt moved = *itm;
moved.tileX = nx;
moved.tileY = ny;
if (!ntt.itemMoves) {
moved.offset = 0.5f;
}
next->items[newLane] = moved;
// clear this one
itm->type = 0;
} else {
// both slots full → wait at end
itm->offset = epsilon;
}
} else {
}
const UpdateTileCallback cb = ItemTileCallbacks[t->type];
if (cb) {
cb(t);
}
}
const UpdateTileCallback cb = ItemTileCallbacks[t->type];
if (cb) {
cb(t);
}
}
}
@@ -129,8 +129,10 @@ void registerItem(char name[20], SDL_Renderer *renderer) {
ItemRegistry[itemRegistryIndex].texture[ORIENT_LEFT],
TILE_SIZE / 2, TILE_SIZE / 2);
SDL_SetTextureBlendMode(ItemRegistry[itemRegistryIndex].textureOnBelt[ORIENT_LEFT], SDL_BLENDMODE_BLEND);
ItemRegistry[itemRegistryIndex].atlasRects[ORIENT_LEFT] = allocate_32x32(ItemRegistry[itemRegistryIndex].texture[ORIENT_LEFT], renderer);
ItemRegistry[itemRegistryIndex].atlasRectsOnBelt[ORIENT_LEFT] = allocate_16x16(ItemRegistry[itemRegistryIndex].textureOnBelt[ORIENT_LEFT], renderer);
ItemRegistry[itemRegistryIndex].atlasRects[ORIENT_LEFT] = allocate_32x32(
ItemRegistry[itemRegistryIndex].texture[ORIENT_LEFT], renderer);
ItemRegistry[itemRegistryIndex].atlasRectsOnBelt[ORIENT_LEFT] = allocate_16x16(
ItemRegistry[itemRegistryIndex].textureOnBelt[ORIENT_LEFT], renderer);
ItemRegistry[itemRegistryIndex].type = itemRegistryIndex;
ItemRegistry[itemRegistryIndex].isTile = false;
ItemRegistry[itemRegistryIndex].miscVal = 60;
@@ -294,7 +296,9 @@ void renderItem(ItemOnBelt item, SDL_Renderer *renderer, int lane, SDL_Rect play
adjustRect(&rectA, playerRect);
SDL_RenderFillRect(renderer, &rectA);
}
SDL_RenderCopy(renderer, ItemRegistry[item.type].textureOnBelt[ORIENT_LEFT], NULL, &rect);
//SDL_RenderCopyx(renderer, ItemRegistry[item.type].textureOnBelt[ORIENT_LEFT], NULL, &rect);
SDL_RenderCopy(renderer, atlasTexture, &ItemRegistry[item.type].atlasRectsOnBelt[ORIENT_LEFT], &rect);
if (debugMode) {
renderText(renderer, fonts[3], tempStr, rectA.x, rectA.y);
}

84
main.c
View File

@@ -14,7 +14,8 @@ typedef struct GameState {
Player player;
Tile tileMap[MAP_HEIGHT][MAP_WIDTH];
BackgroundTile backgroundTileMap[MAP_HEIGHT][MAP_WIDTH];
SynthVoice voices[NUM_SYNTH_VOICES];
AudioData audioData;
TileArray neededUpdates;
} GameState;
GameState gameState;
@@ -34,7 +35,10 @@ int loadGameState(char *filename, Player *plr) {
memcpy(plr, &gameState.player, sizeof(gameState.player));
memcpy(tileMap, gameState.tileMap, sizeof(tileMap));
memcpy(backgroundMap, gameState.backgroundTileMap, sizeof(backgroundMap));
memcpy(audioData.synthVoices, gameState.voices, sizeof(gameState.voices));
SDL_Rect *tmp = audioData.playerRect;
memcpy(&audioData, &gameState.audioData, sizeof(gameState.audioData));
audioData.playerRect = tmp;
memcpy(&neededUpdates, &gameState.neededUpdates, sizeof(gameState.neededUpdates));
plr->cursor.targetTile = NULL;
plr->cursor.prevTargetTile = NULL;
return 0;
@@ -46,7 +50,8 @@ void saveGameState(char *filename, Player *plr) {
memcpy(&gameState.player, plr, sizeof(gameState.player));
memcpy(gameState.tileMap, tileMap, sizeof(gameState.tileMap));
memcpy(gameState.backgroundTileMap, backgroundMap, sizeof(gameState.backgroundTileMap));
memcpy(gameState.voices, audioData.synthVoices, sizeof(gameState.voices));
memcpy(&gameState.audioData, &audioData, sizeof(gameState.audioData));
memcpy(&gameState.neededUpdates, &neededUpdates, sizeof(neededUpdates));
FILE *gameSave = fopen(filename, "wb");
if (!gameSave) {
@@ -98,9 +103,11 @@ int init() {
srand(time(NULL));
memset(tileMap, 0, sizeof(tileMap));
memset(&neededUpdates, 0, sizeof(neededUpdates));
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, NULL);
SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "1");
SDL_SetHint(SDL_HINT_RENDER_BATCHING, "1");
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE, "1");
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
@@ -161,13 +168,16 @@ int init() {
spec.callback = audio_callback;
spec.userdata = &audioData;
// SDL_AudioDeviceID dev = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0);
// if (dev == 0) {
// printf("Failed to open audio: %s\n", SDL_GetError());
// SDL_Quit();
// }
//
// SDL_PauseAudioDevice(dev, 1);
SDL_AudioDeviceID dev = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0);
if (dev == 0) {
printf("Failed to open audio: %s\n", SDL_GetError());
SDL_Quit();
}
load_midi_file("assets/audio/testaid.mid");
SDL_PauseAudioDevice(dev, 0);
SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 255);
SDL_RenderClear(mainRenderer);
@@ -203,19 +213,23 @@ int render() {
SDL_Rect rect2;
rect2.x = 0;
rect2.y = 0;
rect2.w = 0;
rect2.h = 0;
rect2.w = ATLAS_SIZE;
rect2.h = ATLAS_SIZE;
renderAllTiles(mainRenderer, player.rect);
renderPlayer(&player);
// SDL_RenderCopy(mainRenderer, backgroundTexture, &screenRect, &screenRect);
// SDL_RenderCopy(mainRenderer, tilesTexture, &screenRect, &screenRect);
// SDL_RenderCopy(mainRenderer, itemsTexture, &screenRect, &screenRect);
// SDL_RenderCopy(mainRenderer, entityTexture, &screenRect, &screenRect);
// SDL_RenderCopy(mainRenderer, hudTexture, &screenRect, &screenRect);
SDL_QueryTexture(atlasTexture, NULL, NULL, &rect2.w, &rect2.h);
SDL_RenderCopy(mainRenderer, atlasTexture, &rect2, &rect2);
if (!renderAtlas) {
SDL_RenderCopy(mainRenderer, backgroundTexture, &screenRect, &screenRect);
SDL_RenderCopy(mainRenderer, tilesTexture, &screenRect, &screenRect);
SDL_RenderCopy(mainRenderer, itemsTexture, &screenRect, &screenRect);
SDL_RenderCopy(mainRenderer, entityTexture, &screenRect, &screenRect);
SDL_RenderCopy(mainRenderer, hudTexture, &screenRect, &screenRect);
} else {
SDL_RenderCopy(mainRenderer, atlasTexture, &rect2, &rect2);
}
SDL_RenderPresent(mainRenderer);
@@ -246,11 +260,12 @@ int processEvent(SDL_Event e) {
speed = speed == 0 ? 0.004f : 0;
break;
case SDLK_r:
if (player.cursor.canReach && player.cursor.targetTile->type != TYPE_AIR) {
if (player.inventory.activeSlotIndex == 0 && player.cursor.canReach &&
player.cursor.targetTile->type != TYPE_AIR) {
player.cursor.direction = player.cursor.targetTile->direction;
}
player.cursor.direction = (player.cursor.direction + 2) % ORIENT_DIRECTION_COUNT;
if (player.cursor.canReach) {
if (player.inventory.activeSlotIndex == 0 && player.cursor.canReach) {
player.cursor.targetTile->direction = player.cursor.direction;
}
break;
@@ -260,6 +275,9 @@ int processEvent(SDL_Event e) {
case SDLK_F3:
debugMode = !debugMode;
break;
case SDLK_F10:
renderAtlas = !renderAtlas;
break;
case SDLK_F4:
Tile *tile = &tileMap[playerTileY][playerTileX];
break;
@@ -306,6 +324,12 @@ void processMousePosition() {
if (player.inventory.slotCounts[player.inventory.activeSlotIndex] > 0) {
player.inventory.slotCounts[player.inventory.activeSlotIndex]--;
player.cursor.targetTile->type = player.inventory.activeSlotIndex;
player.cursor.targetTile->rect.x = player.cursor.tileX;
player.cursor.targetTile->rect.y = player.cursor.tileY;
if (TileRegistry[player.inventory.activeSlotIndex].needsTicks) {
player.cursor.targetTile->neededUpdateIndex = add_tile(&neededUpdates,
player.cursor.targetTile->rect);
}
player.cursor.targetTile->direction = player.cursor.direction;
}
} else if (player.cursor.targetTile->type == player.inventory.activeSlotIndex) {
@@ -329,6 +353,12 @@ void processMousePosition() {
player.cursor.targetTile->items[lane].type = 0;
}
}
int neededIndex = player.cursor.targetTile->neededUpdateIndex;
if (TileRegistry[player.cursor.targetTile->type].needsTicks &&
neededUpdates.tiles[neededIndex].x == player.cursor.targetTile->rect.x &&
neededUpdates.tiles[neededIndex].y == player.cursor.targetTile->rect.y) {
remove_tile(&neededUpdates, neededIndex);
}
player.cursor.targetTile->type = TYPE_AIR;
player.cursor.breakingProgress = 0;
} else {
@@ -461,8 +491,10 @@ void processKeyboardHeld() {
}
if (keyboardState[SDL_SCANCODE_Q]) {
if (player.cursor.targetTile->type > 0) {
if (player.cursor.targetTile->type > 0 && player.inventory.activeSlotIndex == 0) {
setActivePlayerSlot(&player, player.cursor.targetTile->type);
} else {
setActivePlayerSlot(&player, 0);
}
}
if (keyboardState[SDL_SCANCODE_Y]) {
@@ -519,6 +551,14 @@ int main(__attribute__((unused)) int argc, __attribute__((unused)) char *args[])
genInitMap();
}
// audioData.synthVoices[0].frequency = 1000;
// audioData.synthVoices[0].phase = 0;
// audioData.synthVoices[0].sourceRect.w = TILE_SIZE;
// audioData.synthVoices[0].sourceRect.h = TILE_SIZE;
// audioData.synthVoices[0].sourceRect.x = 100 * TILE_SIZE;
// audioData.synthVoices[0].sourceRect.y = 100 * TILE_SIZE;
// audioData.synthVoices[0].volume = 255;
// audioData.synthVoices[0].waveform = WAVE_SINE;
//Hack to get window to stay up
SDL_Event e;

View File

@@ -6,6 +6,7 @@
#include "player.h"
#include "../tiles/tile.h"
#include "../util/font.h"
#include "../util/atlas.h"
#define HEALTH_MARGIN 4
@@ -18,6 +19,7 @@ SDL_Texture *hudTexture;
SDL_Texture *PlayerTexture;
SDL_Rect PlayerRect;
SDL_Rect playerTextureRect;
SDL_Rect targetItemBGRect;
@@ -28,9 +30,6 @@ SDL_Color breakingBarColor = {128, 128, 0, 255};
void setActivePlayerSlot(Player *plr, ItemType activeSlotIndex) {
activeSlotIndex = activeSlotIndex % itemRegistryIndex;
if (activeSlotIndex <= 0) {
activeSlotIndex = 1;
}
plr->inventory.activeSlotIndex = activeSlotIndex;
}
@@ -46,11 +45,11 @@ void moveActivePlayerSlot(Player *plr, bool up, bool seek) {
// Stop if we found a slot with count > 0
if (!strlen(ItemRegistry[newSlot].name)) continue;
if (newSlot == 0) continue;
if (newSlot == 0) break;
if (seek) {
if (plr->inventory.slotCounts[newSlot] > 0) break;
} else {
break;
break;
}
} while (true);
@@ -104,6 +103,7 @@ void initPlayer(Player *plr) {
SDL_QueryTexture(PlayerTexture, NULL, NULL, &PlayerRect.w, &PlayerRect.h);
PlayerRect.x = (DISPLAY_WIDTH / 2) - (PlayerRect.w / 2);
PlayerRect.y = (DISPLAY_HEIGHT / 2) - (PlayerRect.h / 2);
playerTextureRect = allocate_32x32(PlayerTexture, mainRenderer);
plr->health = 64;
plr->healthIdle = 0;
@@ -112,12 +112,12 @@ void initPlayer(Player *plr) {
plr->rect.w = TILE_SIZE;
plr->rect.h = TILE_SIZE;
for (ItemType ui = 0; ui < tileTypeIndex; ui++) {
plr->inventory.slotCounts[ui] = 64;
for (ItemType ui = 1; ui < tileTypeIndex; ui++) {
plr->inventory.slotCounts[ui] = 65535;
}
for (ItemType ui = ITEMREGISTRY_SIZE / 2; ui < itemRegistryIndex; ui++) {
plr->inventory.slotCounts[ui] = 64;
plr->inventory.slotCounts[ui] = 65535;
}
hudTexture = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, screenRect.w,
@@ -135,12 +135,12 @@ void initPlayer(Player *plr) {
plr->cursor.targetTileRect.h = TILE_SIZE;
targetItemBGRect.w = DISPLAY_WIDTH;
targetItemBGRect.h = TILE_SIZE;
targetItemBGRect.h = TILE_SIZE + fonts[2].size * 2;
targetItemBGRect.x = 0;
targetItemBGRect.y = DISPLAY_HEIGHT - 32;
targetItemBGRect.y = DISPLAY_HEIGHT - TILE_SIZE - fonts[2].size * 2;
targetItemRect.w = TILE_SIZE / 2;
targetItemRect.h = TILE_SIZE / 2;
targetItemRect.w = TILE_SIZE;
targetItemRect.h = TILE_SIZE;
plr->cursor.heldItemRect.w = TILE_SIZE;
plr->cursor.heldItemRect.h = TILE_SIZE;
@@ -164,7 +164,8 @@ void renderPlayer(Player *plr) {
SDL_SetRenderTarget(mainRenderer, entityTexture);
SDL_RenderClear(mainRenderer);
SDL_RenderCopy(mainRenderer, PlayerTexture, NULL, &PlayerRect);
SDL_RenderCopy(mainRenderer, atlasTexture, &playerTextureRect, &PlayerRect);
//SDL_RenderCopy(mainRenderer, PlayerTexture, NULL, &PlayerRect);
SDL_SetRenderTarget(mainRenderer, hudTexture);
SDL_RenderClear(mainRenderer);
@@ -172,29 +173,31 @@ void renderPlayer(Player *plr) {
DrawThickRect(mainRenderer, plr->cursor.targetTileRect, 4);
ItemType itemIndex = plr->inventory.activeSlotIndex;
SDL_Texture *itemTex;
//SDL_Texture *itemTex;
char itemStringCount[6];
if (itemIndex < itemRegistryIndex) {
if (itemIndex < itemRegistryIndex && itemIndex > 0) {
plr->cursor.heldItemRect.x = plr->cursor.windowX;
plr->cursor.heldItemRect.y = plr->cursor.windowY;
itemTex = ItemRegistry[itemIndex].textureOnBelt[plr->cursor.direction];
if (itemTex == NULL) {
itemTex = ItemRegistry[itemIndex].textureOnBelt[ORIENT_LEFT];
//itemTex = ItemRegistry[itemIndex].textureOnBelt[plr->cursor.direction];
SDL_Rect itemAtlasRect = ItemRegistry[itemIndex].atlasRects[plr->cursor.direction];
if (itemAtlasRect.w == 0 || itemAtlasRect.h == 0) {
itemAtlasRect = ItemRegistry[itemIndex].atlasRects[ORIENT_LEFT];
}
if (itemTex != NULL) {
if (itemAtlasRect.w != 0 && itemAtlasRect.h != 0) {
if (plr->inventory.slotCounts[itemIndex] <= 0) {
// Set a red tint (255, 0, 0)
SDL_SetTextureColorMod(itemTex, 128, 128, 255);
SDL_SetTextureAlphaMod(itemTex, 192);
} else {
SDL_SetTextureColorMod(itemTex, 255, 255, 255);
SDL_SetTextureAlphaMod(itemTex, 255);
}
SDL_RenderCopy(mainRenderer, itemTex, NULL,
&plr->cursor.heldItemRect);
// if (plr->inventory.slotCounts[itemIndex] <= 0) {
// // Set a red tint (255, 0, 0)
// SDL_SetTextureColorMod(itemTex, 128, 128, 255);
//
// SDL_SetTextureAlphaMod(itemTex, 192);
// } else {
// SDL_SetTextureColorMod(itemTex, 255, 255, 255);
//
// SDL_SetTextureAlphaMod(itemTex, 255);
// }
// SDL_RenderCopy(mainRenderer, itemTex, NULL,
// &plr->cursor.heldItemRect);
SDL_RenderCopy(mainRenderer, atlasTexture, &itemAtlasRect, &plr->cursor.heldItemRect);
renderText(mainRenderer, fonts[2], ItemRegistry[itemIndex].name, plr->cursor.heldItemRect.x,
plr->cursor.heldItemRect.y - (fonts[2].size * 3 / 2));
}
@@ -215,21 +218,22 @@ void renderPlayer(Player *plr) {
SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 255);
SDL_RenderFillRect(mainRenderer, &targetItemBGRect);
targetItemRect.y = DISPLAY_HEIGHT - 30 + TILE_SIZE / 4 + 3;
targetItemRect.y = DISPLAY_HEIGHT - TILE_SIZE;
targetItemRect.x = TILE_SIZE / 4;
for (ItemType i = 1; i < ITEMREGISTRY_SIZE; i++) {
itemTex = ItemRegistry[i].textureOnBelt[plr->cursor.direction];
if (itemTex == NULL) {
itemTex = ItemRegistry[i].textureOnBelt[ORIENT_LEFT];
SDL_SetTextureBlendMode(atlasTexture, SDL_BLENDMODE_ADD);
for (ItemType i = 0; i < ITEMREGISTRY_SIZE; i++) {
SDL_Rect itemAtlasRectd = ItemRegistry[i].atlasRects[plr->cursor.direction];
if (itemAtlasRectd.w == 0 || itemAtlasRectd.h == 0) {
itemAtlasRectd = ItemRegistry[i].atlasRects[ORIENT_LEFT];
}
if (itemTex != NULL) {
if (plr->inventory.slotCounts[i] <= 0) {
// Set a red tint (255, 0, 0)
SDL_SetTextureColorMod(itemTex, 128, 128, 255);
}
SDL_RenderCopy(mainRenderer, itemTex, NULL, &targetItemRect);
SDL_SetTextureColorMod(itemTex, 255, 255, 255);
if (itemAtlasRectd.w != 0 && itemAtlasRectd.h != 0) {
// if (plr->inventory.slotCounts[i] <= 0) {
// // Set a red tint (255, 0, 0)
// SDL_SetTextureColorMod(itemTex, 128, 128, 255);
// }
SDL_RenderCopy(mainRenderer, atlasTexture, &itemAtlasRectd, &targetItemRect);
//SDL_SetTextureColorMod(itemTex, 255, 255, 255);
if (plr->inventory.activeSlotIndex == i) {
SDL_SetRenderDrawColor(mainRenderer, 16, plr->inventory.slotCounts[i] > 0 ? 128 : 16,
@@ -240,9 +244,10 @@ void renderPlayer(Player *plr) {
renderText(mainRenderer, fonts[2], itemStringCount, targetItemRect.x - 3,
targetItemRect.y - (fonts[2].size * 3 / 2));
//targetItemRect.x += (TILE_SIZE / 2) + (TILE_SIZE / 4);
targetItemRect.x += TILE_SIZE;
targetItemRect.x += TILE_SIZE / 2 * 3;
}
}
SDL_SetTextureBlendMode(atlasTexture, SDL_BLENDMODE_BLEND);
SDL_SetRenderTarget(mainRenderer, originalTarget);
}

View File

@@ -8,6 +8,7 @@
#include "tile.h"
#include "../player/player.h"
#include "../items/item.h"
#include "../util/atlas.h"
void renderBelt(int x, int y, int w, int h, OrientDirection dir, SDL_Rect playerRect, SDL_Renderer *renderer) {
@@ -36,8 +37,10 @@ void renderBelt(int x, int y, int w, int h, OrientDirection dir, SDL_Rect player
adjustRect(&dst1, playerRect);
adjustRect(&dst2, playerRect);
SDL_RenderCopy(renderer, TileRegistry[tileType].textures[dir], &src1, &dst1);
SDL_RenderCopy(renderer, TileRegistry[tileType].textures[dir], &src2, &dst2);
SDL_RenderCopy(renderer, TileRegistry[tileType].textures[dir], &src1, &dst1); //TODO CONVERT TO ATLAS
SDL_RenderCopy(renderer, TileRegistry[tileType].textures[dir], &src2, &dst2); //TODO CONVERT TO ATLAS
// SDL_RenderCopyx(renderer, atlasTexture, &TileRegistry[tileType].atlasRects[dir], NULL);
// SDL_RenderCopyx(renderer, atlasTexture, &TileRegistry[tileType].atlasRects[dir], NULL);
} else {
int offset = scrollFrame % TILE_SIZE;
@@ -56,6 +59,9 @@ void renderBelt(int x, int y, int w, int h, OrientDirection dir, SDL_Rect player
// Rotate to make the belt vertical
// SDL_RenderCopyx(renderer, atlasTexture, &ItemRegistry[item.type].atlasRectsOnBelt[ORIENT_LEFT], NULL);
// SDL_RenderCopyx(renderer, atlasTexture, &ItemRegistry[item.type].atlasRectsOnBelt[ORIENT_LEFT], NULL);
SDL_RenderCopy(renderer, TileRegistry[tileType].textures[dir], &src1, &dst1);
SDL_RenderCopy(renderer, TileRegistry[tileType].textures[dir], &src2, &dst2);
}

View File

@@ -27,16 +27,19 @@ void updateFurnace(Tile *tile) {
if (tile->miscVal == 0) {
tile->audioCh = getAvailableChannel();
if (tile->audioCh < NUM_SYNTH_VOICES) {
audioData.synthVoices[tile->audioCh].volume = 1;
audioData.synthVoices[tile->audioCh].volume = 255;
audioData.synthVoices[tile->audioCh].phase = 0;
audioData.synthVoices[tile->audioCh].sourceRect.x = TILE_SIZE * tile->x;
audioData.synthVoices[tile->audioCh].sourceRect.y = TILE_SIZE * tile->y;
audioData.synthVoices[tile->audioCh].waveform = WAVE_TRIANGLE;
audioData.synthVoices[tile->audioCh].frequency = 99;
audioData.synthVoices[tile->audioCh].sourceRect.x = TILE_SIZE * tile->rect.x;
audioData.synthVoices[tile->audioCh].sourceRect.y = TILE_SIZE * tile->rect.y;
audioData.synthVoices[tile->audioCh].waveform = WAVE_SINE;
audioData.synthVoices[tile->audioCh].frequency = 200;
}
}
if (tile->audioCh < NUM_SYNTH_VOICES) {
audioData.synthVoices[tile->audioCh].frequency++;
printf("frq: %d\n", ++audioData.synthVoices[tile->audioCh].frequency);
}
if (audioData.synthVoices[tile->audioCh].volume < 255) {
audioData.synthVoices[tile->audioCh].volume++;
}
if (outItem->type == 0 && ++tile->miscVal >= targetOutItem.miscVal) {
if (tile->audioCh < NUM_SYNTH_VOICES) {

View File

@@ -2,15 +2,31 @@
// Created by bruno on 4/24/25.
//
#include <dirent.h>
#include "tile.h"
#include "../player/player.h"
#include "furnace.h"
#include "../util/atlas.h"
#include "../util/font.h"
int scrollFrame = 0;
unsigned long beltFrames = 0;
TileArray neededUpdates;
int add_tile(TileArray *arr, MiniRect t) {
if (arr->activeCount >= MAX_TILES) return 0;
arr->tiles[arr->activeCount] = t;
arr->activeCount++;
return arr->activeCount - 1;
}
void remove_tile(TileArray *arr, int index) {
if (index < 0 || index >= arr->activeCount) return;
arr->activeCount--;
arr->tiles[index] = arr->tiles[arr->activeCount]; // swap with last active
}
SDL_Texture *backgroundTexture;
SDL_Texture *tilesTexture;
SDL_Texture *itemsTexture;
@@ -30,8 +46,8 @@ void generateTestMap() {
for (int y = 0; y < DISPLAY_MAP_HEIGHT; y++) {
for (int x = 0; x < DISPLAY_MAP_WIDTH; x++) {
Tile tile = {0};
tile.x = x;
tile.y = y;
tile.rect.x = x;
tile.rect.y = y;
tileMap[y][x] = tile;
}
}
@@ -66,21 +82,19 @@ void registerTile(char name[20], SDL_Renderer *renderer) {
TileRegistry[tileTypeIndex].textures[ORIENT_RIGHT] = createFlippedTexture(renderer, texture, SDL_FLIP_HORIZONTAL);
TileRegistry[tileTypeIndex].textures[ORIENT_UP] = createRotatedTexture(renderer, texture, 90);
TileRegistry[tileTypeIndex].textures[ORIENT_DOWN] = createRotatedTexture(renderer, texture, 270);
if (tileTypeIndex == 0) {
SDL_SetTextureAlphaMod(TileRegistry[0].textures[ORIENT_LEFT], 64);
SDL_SetTextureAlphaMod(TileRegistry[0].textures[ORIENT_RIGHT], 64);
SDL_SetTextureAlphaMod(TileRegistry[0].textures[ORIENT_UP], 64);
SDL_SetTextureAlphaMod(TileRegistry[0].textures[ORIENT_DOWN], 64);
}
SDL_SetTextureBlendMode(TileRegistry[tileTypeIndex].textures[ORIENT_LEFT], SDL_BLENDMODE_BLEND);
SDL_SetTextureBlendMode(TileRegistry[tileTypeIndex].textures[ORIENT_RIGHT], SDL_BLENDMODE_BLEND);
SDL_SetTextureBlendMode(TileRegistry[tileTypeIndex].textures[ORIENT_UP], SDL_BLENDMODE_BLEND);
SDL_SetTextureBlendMode(TileRegistry[tileTypeIndex].textures[ORIENT_DOWN], SDL_BLENDMODE_BLEND);
TileRegistry[tileTypeIndex].atlasRects[ORIENT_LEFT] = allocate_32x32(TileRegistry[tileTypeIndex].textures[ORIENT_LEFT], renderer);
TileRegistry[tileTypeIndex].atlasRects[ORIENT_RIGHT] = allocate_32x32(TileRegistry[tileTypeIndex].textures[ORIENT_RIGHT], renderer);
TileRegistry[tileTypeIndex].atlasRects[ORIENT_UP] = allocate_32x32(TileRegistry[tileTypeIndex].textures[ORIENT_UP], renderer);
TileRegistry[tileTypeIndex].atlasRects[ORIENT_DOWN] = allocate_32x32(TileRegistry[tileTypeIndex].textures[ORIENT_DOWN], renderer);
TileRegistry[tileTypeIndex].atlasRects[ORIENT_LEFT] = allocate_32x32(
TileRegistry[tileTypeIndex].textures[ORIENT_LEFT], renderer);
TileRegistry[tileTypeIndex].atlasRects[ORIENT_RIGHT] = allocate_32x32(
TileRegistry[tileTypeIndex].textures[ORIENT_RIGHT], renderer);
TileRegistry[tileTypeIndex].atlasRects[ORIENT_UP] = allocate_32x32(TileRegistry[tileTypeIndex].textures[ORIENT_UP],
renderer);
TileRegistry[tileTypeIndex].atlasRects[ORIENT_DOWN] = allocate_32x32(
TileRegistry[tileTypeIndex].textures[ORIENT_DOWN], renderer);
TileRegistry[tileTypeIndex].type = tileTypeIndex;
TileRegistry[tileTypeIndex].breakTime = 15;
@@ -96,7 +110,8 @@ void registerBackgroundTile(char name[20], SDL_Renderer *renderer) {
SDL_Texture *texture = IMG_LoadTexture(renderer, texturePath);
BackgroundTileRegistry[backgroundTileTypeIndex].texture = texture;
SDL_SetTextureBlendMode(BackgroundTileRegistry[backgroundTileTypeIndex].texture, SDL_BLENDMODE_NONE);
BackgroundTileRegistry[backgroundTileTypeIndex].atlasRect = allocate_32x32(BackgroundTileRegistry[backgroundTileTypeIndex].texture, renderer);
BackgroundTileRegistry[backgroundTileTypeIndex].atlasRect = allocate_32x32(
BackgroundTileRegistry[backgroundTileTypeIndex].texture, renderer);
BackgroundTileRegistry[backgroundTileTypeIndex].type = backgroundTileTypeIndex;
@@ -128,6 +143,8 @@ void setupTiles() {
}
}
TileRegistry[TYPE_FURNACE].outputLane[FURNACE_OUTPUT_SLOT] = 1;
TileRegistry[TYPE_FURNACE].needsTicks = true;
TileRegistry[TYPE_BELT].needsTicks = true;
}
uint16_t getBreakTime(int type) {
@@ -186,8 +203,10 @@ void renderAllTiles(SDL_Renderer *renderer, SDL_Rect playerRect) {
BackgroundTile bt = backgroundMap[y][x];
SDL_Texture *tex = BackgroundTileRegistry[bt.type].texture;
if (tex != NULL) {
SDL_RenderCopy(renderer, tex, NULL, &dstRect);
SDL_Rect atlRect = BackgroundTileRegistry[bt.type].atlasRect;
if (atlRect.w != 0 && atlRect.h != 0) {
SDL_RenderCopy(renderer, atlasTexture, &atlRect, &dstRect);
//SDL_RenderCopy(renderer, tex, NULL, &dstRect);
}
}
}
@@ -209,14 +228,21 @@ void renderAllTiles(SDL_Renderer *renderer, SDL_Rect playerRect) {
Tile t = tileMap[y][x];
switch (t.type) {
case TYPE_AIR:
break;
case TYPE_BELT:
renderBelt(x, y, tileSize, tileSize, t.direction, playerRect, renderer);
break;
default: {
SDL_Rect atlRect = TileRegistry[t.type].atlasRects[t.direction];
SDL_Texture *tex = TileRegistry[t.type].textures[t.direction];
if (tex == NULL) tex = TileRegistry[t.type].textures[ORIENT_LEFT];
if (tex != NULL) {
SDL_RenderCopy(renderer, tex, NULL, &dstRect);
if (atlRect.w == 0 || atlRect.h == 0) {
tex = TileRegistry[t.type].textures[ORIENT_LEFT];
atlRect = TileRegistry[t.type].atlasRects[ORIENT_LEFT];
}
if (atlRect.w != 0 && atlRect.h != 0) {
//SDL_RenderCopy(renderer, tex, NULL, &dstRect);
SDL_RenderCopy(renderer, atlasTexture, &atlRect, &dstRect);
}
}
}
@@ -231,6 +257,18 @@ void renderAllTiles(SDL_Renderer *renderer, SDL_Rect playerRect) {
if (x < 0 || x >= MAP_WIDTH) continue;
Tile t = tileMap[y][x];
if (debugMode) {
char locChar[20];
sprintf(locChar, "X:%d\nY:%d", x, y);
SDL_Rect dstRect = {
.x = x * TILE_SIZE,
.y = y * TILE_SIZE,
.w = TILE_SIZE,
.h = TILE_SIZE
};
adjustRect(&dstRect, playerRect);
renderText(renderer, fonts[3], locChar, dstRect.x, dstRect.y);
}
if (t.type == TYPE_BELT || itemViewing) {
for (uint8_t lane = 0; lane < ItemSlotCount; lane++) {
if (t.items[lane].type != 0) {

View File

@@ -7,10 +7,9 @@
#include "../util/util.h"
#include "../items/item.h"
//#include "../items/item.h"
#define MAP_WIDTH 100
#define MAP_HEIGHT 100
#define MAP_WIDTH 1000
#define MAP_HEIGHT 1000
#define DISPLAY_MAP_WIDTH 60
#define DISPLAY_MAP_HEIGHT 31
@@ -20,6 +19,25 @@
#define DISPLAY_WIDTH DISPLAY_MAP_WIDTH * TILE_SIZE
#define DISPLAY_HEIGHT DISPLAY_MAP_HEIGHT * TILE_SIZE
typedef struct MiniRect {
int x;
int y;
} MiniRect;
#define MAX_TILES MAP_WIDTH * MAP_HEIGHT
typedef struct {
MiniRect tiles[MAX_TILES];
int activeCount;
} TileArray;
extern TileArray neededUpdates;
void remove_tile(TileArray *arr, int index);
int add_tile(TileArray *arr, MiniRect t);
extern SDL_Texture *tilesTexture;
extern SDL_Texture *itemsTexture;
extern SDL_Texture *backgroundTexture;
@@ -77,6 +95,7 @@ typedef struct TileTypeReg {
bool itemMoves;
bool allowedInItems[ItemSlotCount][ITEMREGISTRY_SIZE];
bool outputLane[ItemSlotCount];
bool needsTicks;
} TileTypeReg;
typedef struct BackgroundTileType {
@@ -106,8 +125,8 @@ typedef struct Tile {
int miscVal;
ItemOnBelt items[ItemSlotCount];
uint16_t audioCh;
int x;
int y;
MiniRect rect;
int neededUpdateIndex;
} Tile;

View File

@@ -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 were 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);
}

View File

@@ -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 sampleframe 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)
// Well 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, well 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 sampleframe:
// (freq * 256) / SAMPLE_RATE tells how many phase steps per mono-sample.
// Because were 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 perframe, which is relatively expensive.
// In practice, you manage the volume per voice so clipping doesnt occur.
free(data);
}

View File

@@ -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

View File

@@ -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++;
}

View File

@@ -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;

View File

@@ -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);