diff --git a/assets/audio/testaid.mid b/assets/audio/testaid.mid new file mode 100644 index 0000000..22568f1 Binary files /dev/null and b/assets/audio/testaid.mid differ diff --git a/items/item.c b/items/item.c index b562aa1..1f01d9b 100644 --- a/items/item.c +++ b/items/item.c @@ -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); } diff --git a/main.c b/main.c index a2f0b24..ea8e9a4 100644 --- a/main.c +++ b/main.c @@ -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; diff --git a/player/player.c b/player/player.c index 79a61f4..8bda845 100644 --- a/player/player.c +++ b/player/player.c @@ -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); } \ No newline at end of file diff --git a/tiles/belt.c b/tiles/belt.c index 16b6729..ccd4165 100644 --- a/tiles/belt.c +++ b/tiles/belt.c @@ -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); } diff --git a/tiles/furnace.c b/tiles/furnace.c index 55474ec..7517107 100644 --- a/tiles/furnace.c +++ b/tiles/furnace.c @@ -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) { diff --git a/tiles/tile.c b/tiles/tile.c index 76fd88a..c31a8bf 100644 --- a/tiles/tile.c +++ b/tiles/tile.c @@ -2,15 +2,31 @@ // Created by bruno on 4/24/25. // -#include #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) { diff --git a/tiles/tile.h b/tiles/tile.h index fb5d115..d9f7ce4 100644 --- a/tiles/tile.h +++ b/tiles/tile.h @@ -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; diff --git a/util/atlas.c b/util/atlas.c index 754248a..252a4f2 100644 --- a/util/atlas.c +++ b/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); } \ No newline at end of file diff --git a/util/audio.c b/util/audio.c index 2260e0d..37b0caa 100644 --- a/util/audio.c +++ b/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. -} \ No newline at end of file + free(data); +} diff --git a/util/audio.h b/util/audio.h index e4f9bf7..3da168b 100644 --- a/util/audio.h +++ b/util/audio.h @@ -9,9 +9,18 @@ #include #include #include +#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 diff --git a/util/font.c b/util/font.c index 8b526f8..6bd844c 100644 --- a/util/font.c +++ b/util/font.c @@ -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++; } diff --git a/util/util.c b/util/util.c index b916a60..a08f080 100644 --- a/util/util.c +++ b/util/util.c @@ -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; diff --git a/util/util.h b/util/util.h index 5695a8e..b30f61f 100644 --- a/util/util.h +++ b/util/util.h @@ -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);