experiments
This commit is contained in:
@@ -32,6 +32,8 @@ add_executable(factorygame
|
|||||||
util/perlin.h
|
util/perlin.h
|
||||||
util/atlas.c
|
util/atlas.c
|
||||||
util/atlas.h
|
util/atlas.h
|
||||||
|
tiles/miner.c
|
||||||
|
tiles/miner.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define the path to the assets folder
|
# Define the path to the assets folder
|
||||||
|
BIN
assets/audio/bg.mid
Normal file
BIN
assets/audio/bg.mid
Normal file
Binary file not shown.
BIN
assets/tiles/4miner.png
Normal file
BIN
assets/tiles/4miner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 489 B |
@@ -18,6 +18,7 @@ typedef enum ItemType {
|
|||||||
TYPE_BLOCK,
|
TYPE_BLOCK,
|
||||||
TYPE_BELT,
|
TYPE_BELT,
|
||||||
TYPE_FURNACE,
|
TYPE_FURNACE,
|
||||||
|
TYPE_MINER,
|
||||||
IRON_ORE = ITEMREGISTRY_SIZE / 2,
|
IRON_ORE = ITEMREGISTRY_SIZE / 2,
|
||||||
SILVER_ORE,
|
SILVER_ORE,
|
||||||
GOLD_ORE,
|
GOLD_ORE,
|
||||||
|
19
main.c
19
main.c
@@ -38,6 +38,7 @@ int loadGameState(char *filename, Player *plr) {
|
|||||||
SDL_Rect *tmp = audioData.playerRect;
|
SDL_Rect *tmp = audioData.playerRect;
|
||||||
memcpy(&audioData, &gameState.audioData, sizeof(gameState.audioData));
|
memcpy(&audioData, &gameState.audioData, sizeof(gameState.audioData));
|
||||||
audioData.playerRect = tmp;
|
audioData.playerRect = tmp;
|
||||||
|
audioData.totalSamples = 0;
|
||||||
memcpy(&neededUpdates, &gameState.neededUpdates, sizeof(gameState.neededUpdates));
|
memcpy(&neededUpdates, &gameState.neededUpdates, sizeof(gameState.neededUpdates));
|
||||||
plr->cursor.targetTile = NULL;
|
plr->cursor.targetTile = NULL;
|
||||||
plr->cursor.prevTargetTile = NULL;
|
plr->cursor.prevTargetTile = NULL;
|
||||||
@@ -142,11 +143,11 @@ int init() {
|
|||||||
loadItems(mainRenderer);
|
loadItems(mainRenderer);
|
||||||
setupTiles();
|
setupTiles();
|
||||||
|
|
||||||
for (ItemType i = 0; i < ITEMREGISTRY_SIZE; i++) {
|
// for (ItemType i = 0; i < ITEMREGISTRY_SIZE; i++) {
|
||||||
if (strlen(ItemRegistry[i].name)) {
|
// if (strlen(ItemRegistry[i].name)) {
|
||||||
printf("%d -> %s\n", i, ItemRegistry[i].name);
|
// printf("%d -> %s\n", i, ItemRegistry[i].name);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
// Create OpenGL context
|
// Create OpenGL context
|
||||||
glContext = SDL_GL_CreateContext(window);
|
glContext = SDL_GL_CreateContext(window);
|
||||||
if (!glContext) {
|
if (!glContext) {
|
||||||
@@ -174,7 +175,14 @@ int init() {
|
|||||||
SDL_Quit();
|
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/testaid.mid");
|
||||||
|
load_midi_file("assets/audio/bg.mid");
|
||||||
|
|
||||||
SDL_PauseAudioDevice(dev, 0);
|
SDL_PauseAudioDevice(dev, 0);
|
||||||
|
|
||||||
@@ -353,6 +361,7 @@ void processMousePosition() {
|
|||||||
player.cursor.targetTile->items[lane].type = 0;
|
player.cursor.targetTile->items[lane].type = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
audioData.synthVoices[player.cursor.targetTile->audioCh].volume = 0;
|
||||||
int neededIndex = player.cursor.targetTile->neededUpdateIndex;
|
int neededIndex = player.cursor.targetTile->neededUpdateIndex;
|
||||||
if (TileRegistry[player.cursor.targetTile->type].needsTicks &&
|
if (TileRegistry[player.cursor.targetTile->type].needsTicks &&
|
||||||
neededUpdates.tiles[neededIndex].x == player.cursor.targetTile->rect.x &&
|
neededUpdates.tiles[neededIndex].x == player.cursor.targetTile->rect.x &&
|
||||||
|
@@ -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);
|
healthBarColor, 4);
|
||||||
|
|
||||||
if (plr->cursor.targetTile) {
|
if (plr->cursor.targetTile) {
|
||||||
uint16_t tempko = getBreakTime(plr->cursor.targetTile->type);
|
uint16_t tempko = getBreakTime(plr->cursor.targetTile->type);
|
||||||
uint16_t tempko2 = plr->cursor.breakingProgress;
|
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);
|
tempko, tempko2, breakingBarColor, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
44
tiles/miner.c
Normal file
44
tiles/miner.c
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
tiles/miner.h
Normal file
18
tiles/miner.h
Normal file
@@ -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
|
@@ -7,6 +7,7 @@
|
|||||||
#include "furnace.h"
|
#include "furnace.h"
|
||||||
#include "../util/atlas.h"
|
#include "../util/atlas.h"
|
||||||
#include "../util/font.h"
|
#include "../util/font.h"
|
||||||
|
#include "miner.h"
|
||||||
|
|
||||||
int scrollFrame = 0;
|
int scrollFrame = 0;
|
||||||
unsigned long beltFrames = 0;
|
unsigned long beltFrames = 0;
|
||||||
@@ -145,6 +146,8 @@ void setupTiles() {
|
|||||||
TileRegistry[TYPE_FURNACE].outputLane[FURNACE_OUTPUT_SLOT] = 1;
|
TileRegistry[TYPE_FURNACE].outputLane[FURNACE_OUTPUT_SLOT] = 1;
|
||||||
TileRegistry[TYPE_FURNACE].needsTicks = true;
|
TileRegistry[TYPE_FURNACE].needsTicks = true;
|
||||||
TileRegistry[TYPE_BELT].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) {
|
uint16_t getBreakTime(int type) {
|
||||||
|
@@ -4,10 +4,12 @@
|
|||||||
|
|
||||||
#include "tilecallbacks.h"
|
#include "tilecallbacks.h"
|
||||||
#include "furnace.h"
|
#include "furnace.h"
|
||||||
|
#include "miner.h"
|
||||||
|
|
||||||
const UpdateTileCallback ItemTileCallbacks[TILEREGISTRY_SIZE] = {
|
const UpdateTileCallback ItemTileCallbacks[TILEREGISTRY_SIZE] = {
|
||||||
[TYPE_AIR] = NULL,
|
[TYPE_AIR] = NULL,
|
||||||
[TYPE_BLOCK] = NULL,
|
[TYPE_BLOCK] = NULL,
|
||||||
[TYPE_BELT] = updateBelt,
|
[TYPE_BELT] = updateBelt,
|
||||||
[TYPE_FURNACE] = updateFurnace
|
[TYPE_FURNACE] = updateFurnace,
|
||||||
|
[TYPE_MINER] = updateMiner
|
||||||
};
|
};
|
@@ -19,8 +19,8 @@ bool isIntersecting(SDL_Rect a) {
|
|||||||
for (int i = 0; i < allocatedRectCount; i++) {
|
for (int i = 0; i < allocatedRectCount; i++) {
|
||||||
SDL_Rect b = allocatedRects[i];
|
SDL_Rect b = allocatedRects[i];
|
||||||
if (SDL_HasIntersection(&a, &b)) {
|
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",
|
// 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);
|
// allocatedRectCount, a.x, a.y, a.w, a.h, b.x, b.y, b.w, b.h);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ SDL_Rect allocate_16x16(SDL_Texture *srcTexture, SDL_Renderer *renderer) {
|
|||||||
|
|
||||||
SDL_SetRenderTarget(renderer, oldTarget);
|
SDL_SetRenderTarget(renderer, oldTarget);
|
||||||
storeRect(destRect);
|
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;
|
return destRect;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ SDL_Rect allocate_32x32(SDL_Texture *srcTexture, SDL_Renderer *renderer) {
|
|||||||
|
|
||||||
SDL_SetRenderTarget(renderer, oldTarget);
|
SDL_SetRenderTarget(renderer, oldTarget);
|
||||||
storeRect(destRect);
|
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;
|
return destRect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
394
util/audio.c
394
util/audio.c
@@ -6,13 +6,12 @@
|
|||||||
|
|
||||||
AudioData audioData;
|
AudioData audioData;
|
||||||
|
|
||||||
#define MAX_MIDI_EVENTS 1024
|
MidiEvent midiEvents[MIDI_TRACK_MAX][MAX_MIDI_EVENTS];
|
||||||
MidiEvent midiEvents[MAX_MIDI_EVENTS];
|
int midiEventCount[MIDI_TRACK_MAX];
|
||||||
int midiEventCount = 0;
|
int nextMidiEvent[MIDI_TRACK_MAX];
|
||||||
int nextMidiEvent = 0;
|
|
||||||
|
|
||||||
uint16_t getAvailableChannel() {
|
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) {
|
if (audioData.synthVoices[i].volume == 0) {
|
||||||
return i;
|
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;
|
// 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) {
|
void audio_callback(void *userdata, Uint8 *stream, int len) {
|
||||||
AudioData *audio = (AudioData *) userdata;
|
AudioData *audio = (AudioData *) userdata;
|
||||||
|
|
||||||
int frames = len / (2 * sizeof(float)); // Stereo frame count
|
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;
|
audio->totalSamples += frames;
|
||||||
|
|
||||||
while (nextMidiEvent < midiEventCount &&
|
for (uint8_t midiChannel = 0; midiChannel < MIDI_TRACK_MAX; midiChannel++) {
|
||||||
midiEvents[nextMidiEvent].timeSec <= elapsedSec) {
|
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) {
|
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
|
// Note On
|
||||||
for (int i = NUM_SYNTH_VOICES - 4; i < NUM_SYNTH_VOICES; ++i) {
|
v->frequency = freq;
|
||||||
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->volume = ev->velocity * 2;
|
||||||
v->waveform = WAVE_SQUARE;
|
|
||||||
v->smoothedAmp = 0;
|
v->smoothedAmp = 0;
|
||||||
break;
|
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) {
|
||||||
} else {
|
|
||||||
// Note Off
|
// 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;
|
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++;
|
nextMidiEvent[midiChannel]++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float *outBuf = (float *) stream;
|
float *outBuf = (float *) stream;
|
||||||
@@ -104,14 +104,17 @@ void audio_callback(void *userdata, Uint8 *stream, int len) {
|
|||||||
float dx = sourceCx - listenerCx;
|
float dx = sourceCx - listenerCx;
|
||||||
float pan = fmaxf(-1.0f, fminf(+1.0f, dx / audio->maxPanDistance));
|
float pan = fmaxf(-1.0f, fminf(+1.0f, dx / audio->maxPanDistance));
|
||||||
|
|
||||||
float gainL, gainR;
|
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);
|
compute_stereo_gains(pan, &gainL, &gainR);
|
||||||
gainL *= 0.7071f;
|
gainL *= 0.7071f;
|
||||||
gainR *= 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;
|
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;
|
float sample;
|
||||||
|
|
||||||
switch (voice->waveform) {
|
switch (voice->waveform) {
|
||||||
|
case WAVE_SINE:
|
||||||
|
sample = (float) sin(norm * 2.0 * M_PI);
|
||||||
|
break;
|
||||||
case WAVE_SQUARE:
|
case WAVE_SQUARE:
|
||||||
sample = (t >= 0.0) ? 1.0f : -1.0f;
|
sample = (t >= 0.0) ? 1.0f : -1.0f;
|
||||||
break;
|
break;
|
||||||
@@ -131,13 +137,37 @@ void audio_callback(void *userdata, Uint8 *stream, int len) {
|
|||||||
sample = (float) t;
|
sample = (float) t;
|
||||||
break;
|
break;
|
||||||
case WAVE_TRIANGLE:
|
case WAVE_TRIANGLE:
|
||||||
sample = (float) ((t < 0.0) ? -t : t);
|
sample = (float) (1.0 - 4.0 * fabs(t - floor(t + 0.5)));
|
||||||
break;
|
break;
|
||||||
case WAVE_NOISE:
|
case WAVE_NOISE:
|
||||||
sample = ((float) rand() / (float) RAND_MAX) * 2.0f - 1.0f;
|
sample = ((float) rand() / (float) RAND_MAX) * 2.0f - 1.0f;
|
||||||
break;
|
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:
|
default:
|
||||||
sample = (float) sin(norm * 2.0 * M_PI);
|
sample = (float) sin(norm * 2.0 * M_PI); // fallback to sine
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +194,6 @@ void audio_callback(void *userdata, Uint8 *stream, int len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static uint32_t read_be_uint32(const uint8_t *data) {
|
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];
|
||||||
}
|
}
|
||||||
@@ -198,16 +227,26 @@ void load_midi_file(const char *path) {
|
|||||||
const uint8_t *ptr = data;
|
const uint8_t *ptr = data;
|
||||||
if (memcmp(ptr, "MThd", 4) != 0) return;
|
if (memcmp(ptr, "MThd", 4) != 0) return;
|
||||||
ptr += 8; // skip header length
|
ptr += 8; // skip header length
|
||||||
uint16_t format = read_be_uint16(ptr); ptr += 2;
|
uint16_t format = read_be_uint16(ptr);
|
||||||
uint16_t nTracks = read_be_uint16(ptr); ptr += 2;
|
ptr += 2;
|
||||||
uint16_t ppqn = 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) {
|
if (format > 1) {
|
||||||
printf("Only Type 0 MIDI supported\n");
|
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);
|
free(data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (int trackIndex = 0; trackIndex < nTracks; trackIndex++) {
|
||||||
if (memcmp(ptr, "MTrk", 4) != 0) return;
|
if (memcmp(ptr, "MTrk", 4) != 0) return;
|
||||||
uint32_t trackLen = read_be_uint32(ptr + 4);
|
uint32_t trackLen = read_be_uint32(ptr + 4);
|
||||||
ptr += 8;
|
ptr += 8;
|
||||||
@@ -217,7 +256,7 @@ void load_midi_file(const char *path) {
|
|||||||
uint32_t tempo = 500000; // default: 120 BPM
|
uint32_t tempo = 500000; // default: 120 BPM
|
||||||
uint8_t lastStatus = 0;
|
uint8_t lastStatus = 0;
|
||||||
|
|
||||||
while (ptr < trackEnd && midiEventCount < MAX_MIDI_EVENTS) {
|
while (ptr < trackEnd && midiEventCount[trackIndex] < MAX_MIDI_EVENTS) {
|
||||||
uint32_t delta = read_vlq(&ptr);
|
uint32_t delta = read_vlq(&ptr);
|
||||||
curTime += (delta * (tempo / 1000000.0f)) / ppqn;
|
curTime += (delta * (tempo / 1000000.0f)) / ppqn;
|
||||||
|
|
||||||
@@ -230,6 +269,11 @@ void load_midi_file(const char *path) {
|
|||||||
if (status == 0xFF) {
|
if (status == 0xFF) {
|
||||||
uint8_t metaType = *ptr++;
|
uint8_t metaType = *ptr++;
|
||||||
uint32_t len = read_vlq(&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) {
|
if (metaType == 0x51 && len == 3) {
|
||||||
tempo = (ptr[0] << 16 | ptr[1] << 8 | ptr[2]);
|
tempo = (ptr[0] << 16 | ptr[1] << 8 | ptr[2]);
|
||||||
}
|
}
|
||||||
@@ -237,9 +281,9 @@ void load_midi_file(const char *path) {
|
|||||||
} else if ((status & 0xF0) == 0x90 || (status & 0xF0) == 0x80) {
|
} else if ((status & 0xF0) == 0x90 || (status & 0xF0) == 0x80) {
|
||||||
uint8_t note = *ptr++;
|
uint8_t note = *ptr++;
|
||||||
uint8_t vel = *ptr++;
|
uint8_t vel = *ptr++;
|
||||||
midiEvents[midiEventCount++] = (MidiEvent){
|
midiEvents[trackIndex][midiEventCount[trackIndex]++] = (MidiEvent) {
|
||||||
.timeSec = curTime,
|
.timeSec = curTime,
|
||||||
.type = (status & 0xF0) == 0x90 ? 0 : 1,
|
.type = status,
|
||||||
.note = note,
|
.note = note,
|
||||||
.velocity = vel
|
.velocity = vel
|
||||||
};
|
};
|
||||||
@@ -247,6 +291,272 @@ void load_midi_file(const char *path) {
|
|||||||
ptr += 2; // skip unknown
|
ptr += 2; // skip unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
free(data);
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
41
util/audio.h
41
util/audio.h
@@ -1,6 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
// Created by bruno on 16.2.2025.
|
// 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
|
#ifndef RISCB_AUDIO_H
|
||||||
#define RISCB_AUDIO_H
|
#define RISCB_AUDIO_H
|
||||||
@@ -11,23 +17,42 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include "../tiles/tile.h"
|
#include "../tiles/tile.h"
|
||||||
|
|
||||||
#define SAMPLE_RATE 44100
|
|
||||||
#define NUM_SYNTH_VOICES 256
|
typedef enum {
|
||||||
#define SMOOTHING_FACTOR 0.001f
|
MIDI_NOTE_OFF = 0x80,
|
||||||
|
MIDI_NOTE_ON = 0x90,
|
||||||
|
MIDI_PROGRAM_CHANGE = 0xC0,
|
||||||
|
// You could add more here if needed
|
||||||
|
} MidiEventType;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
float timeSec; // When to trigger this event
|
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 note;
|
||||||
uint8_t velocity;
|
uint8_t velocity;
|
||||||
} MidiEvent;
|
} 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 {
|
typedef enum Waveform {
|
||||||
WAVE_SINE,
|
WAVE_SINE,
|
||||||
WAVE_SQUARE,
|
WAVE_SQUARE,
|
||||||
WAVE_SAWTOOTH,
|
WAVE_SAWTOOTH,
|
||||||
WAVE_TRIANGLE,
|
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;
|
} Waveform;
|
||||||
|
|
||||||
typedef struct SynthVoice {
|
typedef struct SynthVoice {
|
||||||
@@ -50,8 +75,10 @@ extern AudioData audioData;
|
|||||||
|
|
||||||
void audio_callback(void *userdata, Uint8 *stream, int len);
|
void audio_callback(void *userdata, Uint8 *stream, int len);
|
||||||
|
|
||||||
uint16_t getAvailableChannel();
|
|
||||||
|
|
||||||
void load_midi_file(const char *path);
|
void load_midi_file(const char *path);
|
||||||
|
|
||||||
|
uint16_t getAvailableChannel();
|
||||||
|
|
||||||
|
Waveform resolvePatch(uint8_t patchNum);
|
||||||
|
|
||||||
#endif //RISCB_AUDIO_H
|
#endif //RISCB_AUDIO_H
|
||||||
|
Reference in New Issue
Block a user