From a5b52b6b89d940d48469e78c800a1cf3b01d8e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Ryb=C3=A1rsky?= Date: Mon, 2 Jun 2025 22:49:53 +0200 Subject: [PATCH] experiments --- CMakeLists.txt | 2 + assets/audio/bg.mid | Bin 0 -> 4120 bytes assets/tiles/4miner.png | Bin 0 -> 489 bytes items/item.h | 1 + main.c | 19 +- player/player.c | 4 +- tiles/miner.c | 44 ++++ tiles/miner.h | 18 ++ tiles/tile.c | 3 + tiles/tilecallbacks.c | 4 +- util/atlas.c | 8 +- util/audio.c | 470 +++++++++++++++++++++++++++++++++------- util/audio.h | 41 +++- 13 files changed, 515 insertions(+), 99 deletions(-) create mode 100644 assets/audio/bg.mid create mode 100644 assets/tiles/4miner.png create mode 100644 tiles/miner.c create mode 100644 tiles/miner.h diff --git a/CMakeLists.txt b/CMakeLists.txt index da350bd..12fec65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,8 @@ add_executable(factorygame util/perlin.h util/atlas.c util/atlas.h + tiles/miner.c + tiles/miner.h ) # Define the path to the assets folder diff --git a/assets/audio/bg.mid b/assets/audio/bg.mid new file mode 100644 index 0000000000000000000000000000000000000000..28514dfba13f52c34200fcdef9618f3b881013bc GIT binary patch literal 4120 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 AFaQ7m literal 0 HcmV?d00001 diff --git a/assets/tiles/4miner.png b/assets/tiles/4miner.png new file mode 100644 index 0000000000000000000000000000000000000000..7d28435cf2ccf2ff245c59fec7b38284ad21b401 GIT binary patch literal 489 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}jKx9jP7LeL$-HD>U|j6! z;usRq`gWS3_hAQ-hW#Qh^)m`4@;Q07d#O)vSi4~9=H}Vm!7D9($;aGb>7MM(9$3I1 z7TE4%<-r=fWv)n+Qb|~Qafbd@wany$#}wsd-cPD7fB$rE`Sj+Fj{24t+gA!rKK6BL zuFxtq*_l#ZCVAzr7)q>j@4ac0`CXj!ICNR_h45{zPFI)3u`hH_IQD#RxX6_J_oDK$ zmSPg>OzN}e%dA)TO#P%1a(3b-#zosg6{ena-e0ivT=n0c=~yLBAtMV}%=zR~dhp zvrvj5IfqMUb8BkY96O1um|irzUf`+h!n`B18|rrdvh#ojxt zJVMK(79`i?J-!gz@@?cursor.targetTile = NULL; plr->cursor.prevTargetTile = NULL; @@ -142,11 +143,11 @@ int init() { loadItems(mainRenderer); setupTiles(); - for (ItemType i = 0; i < ITEMREGISTRY_SIZE; i++) { - if (strlen(ItemRegistry[i].name)) { - printf("%d -> %s\n", i, ItemRegistry[i].name); - } - } +// for (ItemType i = 0; i < ITEMREGISTRY_SIZE; i++) { +// if (strlen(ItemRegistry[i].name)) { +// printf("%d -> %s\n", i, ItemRegistry[i].name); +// } +// } // Create OpenGL context glContext = SDL_GL_CreateContext(window); if (!glContext) { @@ -174,7 +175,14 @@ int init() { SDL_Quit(); } + for (int t = 0; t < MIDI_TRACK_MAX; t++) { + midiEventCount[t] = 0; + nextMidiEvent[t] = 0; + } + + load_midi_file("assets/audio/testaid.mid"); + load_midi_file("assets/audio/bg.mid"); SDL_PauseAudioDevice(dev, 0); @@ -353,6 +361,7 @@ void processMousePosition() { player.cursor.targetTile->items[lane].type = 0; } } + audioData.synthVoices[player.cursor.targetTile->audioCh].volume = 0; int neededIndex = player.cursor.targetTile->neededUpdateIndex; if (TileRegistry[player.cursor.targetTile->type].needsTicks && neededUpdates.tiles[neededIndex].x == player.cursor.targetTile->rect.x && diff --git a/player/player.c b/player/player.c index 8bda845..30f4aef 100644 --- a/player/player.c +++ b/player/player.c @@ -204,13 +204,13 @@ void renderPlayer(Player *plr) { } - renderBar(mainRenderer, (DISPLAY_WIDTH / 2) - 128, DISPLAY_HEIGHT - 50, 200, 8, playerMaxHealth, plr->health, + renderBar(mainRenderer, (DISPLAY_WIDTH / 2) - 128, DISPLAY_HEIGHT - 70, 200, 8, playerMaxHealth, plr->health, healthBarColor, 4); if (plr->cursor.targetTile) { uint16_t tempko = getBreakTime(plr->cursor.targetTile->type); uint16_t tempko2 = plr->cursor.breakingProgress; - renderBar(mainRenderer, (DISPLAY_WIDTH / 2) - 128, DISPLAY_HEIGHT - 70, 200, 8, + renderBar(mainRenderer, (DISPLAY_WIDTH / 2) - 128, DISPLAY_HEIGHT - 90, 200, 8, tempko, tempko2, breakingBarColor, 4); } diff --git a/tiles/miner.c b/tiles/miner.c new file mode 100644 index 0000000..61e54e5 --- /dev/null +++ b/tiles/miner.c @@ -0,0 +1,44 @@ +// +// Created by bruno on 2.6.2025. +// + +#include "miner.h" +#include "tile.h" +#include "../util/audio.h" + +const ItemType MinerRecipes[TILEREGISTRY_SIZE] = { + [BGType_IRON_ORE] = IRON_ORE, + [BGType_SILVER_ORE] = SILVER_ORE, + [BGType_GOLD_ORE] = GOLD_ORE, + [BGType_PLATINUM_ORE] = PLATINUM_ORE +}; + + +void updateMiner(Tile *tile) { + ItemOnBelt *outItem = &tile->items[MINER_OUTPUT_SLOT]; + BackgroundType bgt = backgroundMap[tile->rect.y][tile->rect.x].type; + ItemType targetOutItemType = MinerRecipes[bgt]; + Item targetOutItem = ItemRegistry[targetOutItemType]; + + if (targetOutItemType != TYPE_AIR && outItem->type == 0) { + if (tile->miscVal == 0) { + tile->audioCh = getAvailableChannel(); + if (tile->audioCh < NUM_SYNTH_VOICES) { + audioData.synthVoices[tile->audioCh].volume = 64; + audioData.synthVoices[tile->audioCh].phase = 0; + 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_NOISE; + audioData.synthVoices[tile->audioCh].frequency = 400; + } + } + if (outItem->type == 0 && ++tile->miscVal >= targetOutItem.miscVal) { + if (tile->audioCh < NUM_SYNTH_VOICES) { + audioData.synthVoices[tile->audioCh].volume = 0; + } + tile->miscVal = 0; + outItem->type = targetOutItemType; + outItem->offset = -0.5f; + } + } +} \ No newline at end of file diff --git a/tiles/miner.h b/tiles/miner.h new file mode 100644 index 0000000..7d01d44 --- /dev/null +++ b/tiles/miner.h @@ -0,0 +1,18 @@ +// +// Created by bruno on 2.6.2025. +// + +#ifndef FACTORYGAME_MINER_H +#define FACTORYGAME_MINER_H + +#include "../items/item.h" +#include "stdint.h" + +extern const ItemType FurnaceRecipes[]; + +#define MINER_OUTPUT_SLOT 0 + +void updateMiner(Tile * tile); + + +#endif //FACTORYGAME_MINER_H diff --git a/tiles/tile.c b/tiles/tile.c index c31a8bf..c8ba568 100644 --- a/tiles/tile.c +++ b/tiles/tile.c @@ -7,6 +7,7 @@ #include "furnace.h" #include "../util/atlas.h" #include "../util/font.h" +#include "miner.h" int scrollFrame = 0; unsigned long beltFrames = 0; @@ -145,6 +146,8 @@ void setupTiles() { TileRegistry[TYPE_FURNACE].outputLane[FURNACE_OUTPUT_SLOT] = 1; TileRegistry[TYPE_FURNACE].needsTicks = true; TileRegistry[TYPE_BELT].needsTicks = true; + TileRegistry[TYPE_MINER].needsTicks = true; + TileRegistry[TYPE_MINER].outputLane[MINER_OUTPUT_SLOT] = 1; } uint16_t getBreakTime(int type) { diff --git a/tiles/tilecallbacks.c b/tiles/tilecallbacks.c index 4924b72..ecd1ee2 100644 --- a/tiles/tilecallbacks.c +++ b/tiles/tilecallbacks.c @@ -4,10 +4,12 @@ #include "tilecallbacks.h" #include "furnace.h" +#include "miner.h" const UpdateTileCallback ItemTileCallbacks[TILEREGISTRY_SIZE] = { [TYPE_AIR] = NULL, [TYPE_BLOCK] = NULL, [TYPE_BELT] = updateBelt, - [TYPE_FURNACE] = updateFurnace + [TYPE_FURNACE] = updateFurnace, + [TYPE_MINER] = updateMiner }; \ No newline at end of file diff --git a/util/atlas.c b/util/atlas.c index 252a4f2..f839269 100644 --- a/util/atlas.c +++ b/util/atlas.c @@ -19,8 +19,8 @@ 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); +// 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; } } @@ -88,7 +88,7 @@ SDL_Rect allocate_16x16(SDL_Texture *srcTexture, SDL_Renderer *renderer) { 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); +// printf("Rect X:%d, Y: %d, W: %d, H: %d\n", destRect.x, destRect.y, destRect.w, destRect.h); return destRect; } @@ -112,7 +112,7 @@ SDL_Rect allocate_32x32(SDL_Texture *srcTexture, SDL_Renderer *renderer) { 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); +// printf("Rect X:%d, Y: %d, W: %d, H: %d\n", destRect.x, destRect.y, destRect.w, destRect.h); return destRect; } diff --git a/util/audio.c b/util/audio.c index 37b0caa..8657598 100644 --- a/util/audio.c +++ b/util/audio.c @@ -6,13 +6,12 @@ AudioData audioData; -#define MAX_MIDI_EVENTS 1024 -MidiEvent midiEvents[MAX_MIDI_EVENTS]; -int midiEventCount = 0; -int nextMidiEvent = 0; +MidiEvent midiEvents[MIDI_TRACK_MAX][MAX_MIDI_EVENTS]; +int midiEventCount[MIDI_TRACK_MAX]; +int nextMidiEvent[MIDI_TRACK_MAX]; uint16_t getAvailableChannel() { - for (uint16_t i = 0; i < NUM_SYNTH_VOICES; i++) { + for (uint16_t i = 0; i < NUM_SYNTH_VOICES - MIDI_VOICES; i++) { if (audioData.synthVoices[i].volume == 0) { return i; } @@ -44,45 +43,46 @@ static void compute_stereo_gains(float pan, float *outL, float *outR) { // e.g. *outL *= 0.7071f; *outR *= 0.7071f; } -// Improved audio callback with anti-clipping and smooth fade-out +// Improved audio ; with anti-clipping and smooth fade-out void audio_callback(void *userdata, Uint8 *stream, int len) { AudioData *audio = (AudioData *) userdata; int frames = len / (2 * sizeof(float)); // Stereo frame count - float elapsedSec = audio->totalSamples / SAMPLE_RATE; + float elapsedSec = ((float) audio->totalSamples) / SAMPLE_RATE; audio->totalSamples += frames; - while (nextMidiEvent < midiEventCount && - midiEvents[nextMidiEvent].timeSec <= elapsedSec) { + for (uint8_t midiChannel = 0; midiChannel < MIDI_TRACK_MAX; midiChannel++) { + while (nextMidiEvent[midiChannel] < midiEventCount[midiChannel] && + midiEvents[midiChannel][nextMidiEvent[midiChannel]].timeSec <= elapsedSec) { - MidiEvent *ev = &midiEvents[nextMidiEvent]; + MidiEvent *ev = &midiEvents[midiChannel][nextMidiEvent[midiChannel]]; - 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; + printf("Event at %f, %s, note: %d, velocity: %d\n", ev->timeSec, ev->type == MIDI_NOTE_ON ? "ON" : "OFF", + ev->note, ev->velocity); + + uint16_t freq = (uint16_t)(440.0f * powf(2.0f, (ev->note - 69) / 12.0f)); + uint8_t midiVoiceIndex = NUM_SYNTH_VOICES - midiChannel - 1; + SynthVoice *v = &audio->synthVoices[midiVoiceIndex]; + if (ev->type == MIDI_NOTE_ON && ev->velocity > 0) { + // Note On + v->frequency = freq; + v->volume = ev->velocity * 2; + v->smoothedAmp = 0; + printf("Playing voice %d at freq %d hz, volume %d\n", midiVoiceIndex, v->frequency, + v->volume); + } else if (ev->type == MIDI_NOTE_OFF || ev->velocity == 0) { + // Note Off + v->volume = 0; + printf("Stopping voice %d at freq %d hz, volume %d\n", midiVoiceIndex, v->frequency, v->volume); + } else if ((ev->type & 0xF0) == MIDI_PROGRAM_CHANGE) { + if (ev->note == 0) { + v->waveform = resolvePatch(ev->note); } } + + nextMidiEvent[midiChannel]++; } - - nextMidiEvent++; } float *outBuf = (float *) stream; @@ -104,14 +104,17 @@ void audio_callback(void *userdata, Uint8 *stream, int len) { float dx = sourceCx - listenerCx; 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; + float gainL = 1; + float gainR = 1; + float targetAmp = (voice->volume / 255.0f); + if (v < NUM_SYNTH_VOICES - MIDI_VOICES) { + float distanceAtten = 1.0f - fminf(fabsf(dx) / audio->maxPanDistance, 1.0f); + targetAmp *= distanceAtten; + compute_stereo_gains(pan, &gainL, &gainR); + gainL *= 0.7071f; + gainR *= 0.7071f; - float dist = fabsf(dx); - float distanceAtten = 1.0f - fminf(dist / audio->maxPanDistance, 1.0f); - float targetAmp = (voice->volume / 255.0f) * distanceAtten; + } double phaseInc = ((double) voice->frequency * 256.0) / (double) SAMPLE_RATE; @@ -124,6 +127,9 @@ void audio_callback(void *userdata, Uint8 *stream, int len) { float sample; switch (voice->waveform) { + case WAVE_SINE: + sample = (float) sin(norm * 2.0 * M_PI); + break; case WAVE_SQUARE: sample = (t >= 0.0) ? 1.0f : -1.0f; break; @@ -131,13 +137,37 @@ void audio_callback(void *userdata, Uint8 *stream, int len) { sample = (float) t; break; case WAVE_TRIANGLE: - sample = (float) ((t < 0.0) ? -t : t); + sample = (float) (1.0 - 4.0 * fabs(t - floor(t + 0.5))); break; case WAVE_NOISE: sample = ((float) rand() / (float) RAND_MAX) * 2.0f - 1.0f; break; + case WAVE_HALF_SINE: // one cycle of sine clipped to [0, 1] + sample = (float) (0.5 * sin(norm * 2.0 * M_PI) + 0.5); + break; + case WAVE_PULSE25: + sample = (norm < 0.25) ? 1.0f : -1.0f; + break; + case WAVE_PULSE10: + sample = (norm < 0.10) ? 1.0f : -1.0f; + break; + case WAVE_CLIPPED_SINE: + sample = fmaxf(-0.7f, fminf(0.7f, (float) sin(norm * 2.0 * M_PI))); + break; + case WAVE_EXP: + sample = copysignf(powf(fabsf(t), 0.5f), t); // softer exponential curve + break; + case WAVE_RAMP: + sample = 2.0f * fmodf(norm, 1.0f) - 1.0f; // ascending ramp (like saw) + break; + case WAVE_REVERSE_SAW: + sample = 1.0f - 2.0f * fmodf(norm, 1.0f); // descending ramp + break; + case WAVE_STAIRCASE: + sample = floorf(norm * 8.0f) / 4.0f - 1.0f; // stepped waveform + break; default: - sample = (float) sin(norm * 2.0 * M_PI); + sample = (float) sin(norm * 2.0 * M_PI); // fallback to sine break; } @@ -164,13 +194,12 @@ void audio_callback(void *userdata, Uint8 *stream, int len) { } - static uint32_t read_be_uint32(const uint8_t *data) { - return (data[0]<<24) | (data[1]<<16) | (data[2]<<8) | data[3]; + 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]; + return (data[0] << 8) | data[1]; } static uint32_t read_vlq(const uint8_t **ptr) { @@ -198,55 +227,336 @@ void load_midi_file(const char *path) { 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; + 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"); + if (format > 1) { + printf("ERROR: Only MIDI format 0 or 1 supported (found %d)\n", format); + free(data); + return; + } + if (nTracks == 0 || nTracks > MIDI_TRACK_MAX) { + printf("ERROR: Number of tracks %d out of range\n", nTracks); 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; + for (int trackIndex = 0; trackIndex < nTracks; trackIndex++) { + if (memcmp(ptr, "MTrk", 4) != 0) return; + uint32_t trackLen = read_be_uint32(ptr + 4); + ptr += 8; + const uint8_t *trackEnd = ptr + trackLen; - while (ptr < trackEnd && midiEventCount < MAX_MIDI_EVENTS) { - uint32_t delta = read_vlq(&ptr); - curTime += (delta * (tempo / 1000000.0f)) / ppqn; + float curTime = 0.0f; + uint32_t tempo = 500000; // default: 120 BPM + uint8_t lastStatus = 0; - uint8_t status = *ptr; - if (status < 0x80) status = lastStatus; - else ptr++; + while (ptr < trackEnd && midiEventCount[trackIndex] < MAX_MIDI_EVENTS) { + uint32_t delta = read_vlq(&ptr); + curTime += (delta * (tempo / 1000000.0f)) / ppqn; - lastStatus = status; + uint8_t status = *ptr; + if (status < 0x80) status = lastStatus; + else ptr++; - 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]); + lastStatus = status; + + if (status == 0xFF) { + uint8_t metaType = *ptr++; + uint32_t len = read_vlq(&ptr); + if (metaType == 0x03) { + // This is a track name — skip it + ptr += len; + continue; + } + 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[trackIndex][midiEventCount[trackIndex]++] = (MidiEvent) { + .timeSec = curTime, + .type = status, + .note = note, + .velocity = vel + }; + } else { + ptr += 2; // skip unknown } - 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 } } free(data); } + + +Waveform resolvePatch(uint8_t patchNum) { + switch (patchNum) { + case 0: + return WAVE_CLIPPED_SINE; // Acoustic Grand Piano (harmonic, percussive) + case 1: + return WAVE_CLIPPED_SINE; // Bright Acoustic Piano + case 2: + return WAVE_CLIPPED_SINE; // Electric Grand Piano + case 3: + return WAVE_CLIPPED_SINE; // Honky-Tonk Piano + case 4: + return WAVE_SINE; // Electric Piano 1 (Rhodes) - smooth bell-like + case 5: + return WAVE_SINE; // Electric Piano 2 (Wurlitzer) + case 6: + return WAVE_CLIPPED_SINE; // Harpsichord (plucked, bright) + case 7: + return WAVE_SQUARE; // Clavinet (sharp, funky) + case 8: + return WAVE_SINE; // Celesta (bell-like, soft) + case 9: + return WAVE_SINE; // Glockenspiel (bright bell) + case 10: + return WAVE_SINE; // Music Box (delicate bell) + case 11: + return WAVE_SINE; // Vibraphone (vibrato sine-like) + case 12: + return WAVE_SINE; // Marimba (warm sine) + case 13: + return WAVE_SINE; // Xylophone (sharp sine) + case 14: + return WAVE_SINE; // Tubular Bells (bell) + case 15: + return WAVE_SINE; // Dulcimer (plucked sine) + case 16: + return WAVE_SAWTOOTH; // Drawbar Organ (rich harmonic) + case 17: + return WAVE_SAWTOOTH; // Percussive Organ + case 18: + return WAVE_SAWTOOTH; // Rock Organ + case 19: + return WAVE_SAWTOOTH; // Church Organ + case 20: + return WAVE_SAWTOOTH; // Reed Organ + case 21: + return WAVE_SQUARE; // Accordion (reedy square) + case 22: + return WAVE_SQUARE; // Harmonica (square-ish) + case 23: + return WAVE_SQUARE; // Tango Accordion + case 24: + return WAVE_PULSE25; // Nylon Guitar (plucked, soft pulse) + case 25: + return WAVE_PULSE25; // Steel Guitar + case 26: + return WAVE_PULSE25; // Jazz Guitar + case 27: + return WAVE_PULSE25; // Clean Electric Guitar + case 28: + return WAVE_PULSE25; // Muted Electric Guitar + case 29: + return WAVE_PULSE10; // Overdriven Guitar (more distorted) + case 30: + return WAVE_PULSE10; // Distortion Guitar + case 31: + return WAVE_PULSE25; // Guitar Harmonics (plucked) + case 32: + return WAVE_SINE; // Acoustic Bass + case 33: + return WAVE_SINE; // Electric Bass (finger) + case 34: + return WAVE_SINE; // Electric Bass (pick) + case 35: + return WAVE_SINE; // Fretless Bass + case 36: + return WAVE_TRIANGLE; // Slap Bass 1 (percussive triangle) + case 37: + return WAVE_TRIANGLE; // Slap Bass 2 + case 38: + return WAVE_SAWTOOTH; // Synth Bass 1 + case 39: + return WAVE_SAWTOOTH; // Synth Bass 2 + case 40: + return WAVE_SINE; // Violin + case 41: + return WAVE_SINE; // Viola + case 42: + return WAVE_SINE; // Cello + case 43: + return WAVE_SINE; // Contrabass + case 44: + return WAVE_SAWTOOTH; // Tremolo Strings (rich) + case 45: + return WAVE_SINE; // Pizzicato Strings (plucked) + case 46: + return WAVE_SINE; // Orchestral Harp (plucked sine) + case 47: + return WAVE_CLIPPED_SINE; // Timpani (percussive sine) + case 48: + return WAVE_SAWTOOTH; // String Ensemble 1 (Slow Strings) + case 49: + return WAVE_SAWTOOTH; // String Ensemble 2 (Fast Strings) + case 50: + return WAVE_SAWTOOTH; // SynthStrings 1 + case 51: + return WAVE_SAWTOOTH; // SynthStrings 2 + case 52: + return WAVE_SINE; // Choir Aahs + case 53: + return WAVE_SINE; // Voice Oohs + case 54: + return WAVE_SQUARE; // Synth Voice + case 55: + return WAVE_SAWTOOTH; // Orchestra Hit (harsh) + case 56: + return WAVE_SQUARE; // Trumpet + case 57: + return WAVE_SQUARE; // Trombone + case 58: + return WAVE_SQUARE; // Tuba + case 59: + return WAVE_PULSE25; // Muted Trumpet + case 60: + return WAVE_SQUARE; // French Horn + case 61: + return WAVE_SAWTOOTH; // Brass Section + case 62: + return WAVE_SAWTOOTH; // SynthBrass 1 + case 63: + return WAVE_SAWTOOTH; // SynthBrass 2 + case 64: + return WAVE_SQUARE; // Soprano Sax + case 65: + return WAVE_SQUARE; // Alto Sax + case 66: + return WAVE_SQUARE; // Tenor Sax + case 67: + return WAVE_SQUARE; // Baritone Sax + case 68: + return WAVE_SINE; // Oboe + case 69: + return WAVE_SINE; // English Horn + case 70: + return WAVE_SINE; // Bassoon + case 71: + return WAVE_SINE; // Clarinet + case 72: + return WAVE_SINE; // Piccolo + case 73: + return WAVE_SINE; // Flute + case 74: + return WAVE_SINE; // Recorder + case 75: + return WAVE_SINE; // Pan Flute + case 76: + return WAVE_SINE; // Blown Bottle + case 77: + return WAVE_SINE; // Shakuhachi + case 78: + return WAVE_SINE; // Whistle + case 79: + return WAVE_SINE; // Ocarina + case 80: + return WAVE_SQUARE; // Lead 1 (Square) + case 81: + return WAVE_SAWTOOTH; // Lead 2 (Sawtooth) + case 82: + return WAVE_SAWTOOTH; // Lead 3 (Calliope) + case 83: + return WAVE_SINE; // Lead 4 (Chiff) + case 84: + return WAVE_SQUARE; // Lead 5 (Charang) + case 85: + return WAVE_SQUARE; // Lead 6 (Voice) + case 86: + return WAVE_SAWTOOTH; // Lead 7 (Fifths) + case 87: + return WAVE_SQUARE; // Lead 8 (Bass + Lead) + case 88: + return WAVE_SAWTOOTH; // Pad 1 (New Age) + case 89: + return WAVE_SAWTOOTH; // Pad 2 (Warm) + case 90: + return WAVE_SAWTOOTH; // Pad 3 (Polysynth) + case 91: + return WAVE_SINE; // Pad 4 (Choir) + case 92: + return WAVE_SINE; // Pad 5 (Bowed) + case 93: + return WAVE_SAWTOOTH; // Pad 6 (Metallic) + case 94: + return WAVE_SINE; // Pad 7 (Halo) + case 95: + return WAVE_SINE; // Pad 8 (Sweep) + case 96: + return WAVE_NOISE; // FX 1 (Rain) + case 97: + return WAVE_NOISE; // FX 2 (Soundtrack) + case 98: + return WAVE_NOISE; // FX 3 (Crystal) + case 99: + return WAVE_NOISE; // FX 4 (Atmosphere) + case 100: + return WAVE_NOISE; // FX 5 (Brightness) + case 101: + return WAVE_NOISE; // FX 6 (Goblins) + case 102: + return WAVE_NOISE; // FX 7 (Echoes) + case 103: + return WAVE_NOISE; // FX 8 (Sci-Fi) + case 104: + return WAVE_SAWTOOTH; // Sitar + case 105: + return WAVE_SQUARE; // Banjo + case 106: + return WAVE_PULSE25; // Shamisen + case 107: + return WAVE_SINE; // Koto + case 108: + return WAVE_SINE; // Kalimba + case 109: + return WAVE_SINE; // Bagpipe + case 110: + return WAVE_SINE; // Fiddle + case 111: + return WAVE_SINE; // Shanai + case 112: + return WAVE_SAWTOOTH; // Tinkle Bell + case 113: + return WAVE_NOISE; // Agogo + case 114: + return WAVE_NOISE; // Steel Drums + case 115: + return WAVE_NOISE; // Woodblock + case 116: + return WAVE_NOISE; // Taiko Drum + case 117: + return WAVE_NOISE; // Melodic Tom + case 118: + return WAVE_NOISE; // Synth Drum + case 119: + return WAVE_NOISE; // Reverse Cymbal + case 120: + return WAVE_NOISE; // Guitar Fret Noise + case 121: + return WAVE_NOISE; // Breath Noise + case 122: + return WAVE_NOISE; // Seashore + case 123: + return WAVE_NOISE; // Bird Tweet + case 124: + return WAVE_NOISE; // Telephone Ring + case 125: + return WAVE_NOISE; // Helicopter + case 126: + return WAVE_NOISE; // Applause + case 127: + return WAVE_NOISE; // Gunshot + default: + return WAVE_SINE; // Default fallback + } + +} \ No newline at end of file diff --git a/util/audio.h b/util/audio.h index 3da168b..661ba02 100644 --- a/util/audio.h +++ b/util/audio.h @@ -1,6 +1,12 @@ /* // Created by bruno on 16.2.2025. */ +#define SAMPLE_RATE 44100 +#define NUM_SYNTH_VOICES 128 +#define SMOOTHING_FACTOR 0.001f +#define MIDI_TRACK_MAX 16 +#define MAX_MIDI_EVENTS 1024 +#define MIDI_VOICES MIDI_TRACK_MAX #ifndef RISCB_AUDIO_H #define RISCB_AUDIO_H @@ -11,23 +17,42 @@ #include #include "../tiles/tile.h" -#define SAMPLE_RATE 44100 -#define NUM_SYNTH_VOICES 256 -#define SMOOTHING_FACTOR 0.001f + +typedef enum { + MIDI_NOTE_OFF = 0x80, + MIDI_NOTE_ON = 0x90, + MIDI_PROGRAM_CHANGE = 0xC0, + // You could add more here if needed +} MidiEventType; typedef struct { float timeSec; // When to trigger this event - uint8_t type; // 0 = Note On, 1 = Note Off + MidiEventType type; // 0 = Note On, 1 = Note Off uint8_t note; uint8_t velocity; } MidiEvent; + +extern MidiEvent midiEvents[MIDI_TRACK_MAX][MAX_MIDI_EVENTS]; +extern int midiEventCount[MIDI_TRACK_MAX]; +extern int nextMidiEvent[MIDI_TRACK_MAX]; + + typedef enum Waveform { WAVE_SINE, WAVE_SQUARE, WAVE_SAWTOOTH, WAVE_TRIANGLE, - WAVE_NOISE + WAVE_NOISE, + WAVE_HALF_SINE, + WAVE_PULSE25, + WAVE_PULSE10, + WAVE_CLIPPED_SINE, + WAVE_EXP, + WAVE_RAMP, + WAVE_REVERSE_SAW, + WAVE_STAIRCASE, + WAVE_COUNT } Waveform; typedef struct SynthVoice { @@ -50,8 +75,10 @@ extern AudioData audioData; void audio_callback(void *userdata, Uint8 *stream, int len); -uint16_t getAvailableChannel(); - void load_midi_file(const char *path); +uint16_t getAvailableChannel(); + +Waveform resolvePatch(uint8_t patchNum); + #endif //RISCB_AUDIO_H