Visits heute: 163
Aktuelle Seite: SDL2-Klavier
SDL2-Klavier: Ein virtuelles Synthesizer-Klavier
Dieses kleine Projekt ist ein simples virtuelles Klavier, das über die Tastatur gespielt werden kann. Es nutzt die SDL2-Bibliothek, um die grafische Oberfläche und die Audioausgabe zu steuern. Anstelle von vorab aufgenommenen Samples generiert das Programm die Töne in Echtzeit mithilfe verschiedener mathematischer Wellenformen. Zusätzlich kann ein einfacher Halleffekt zugeschaltet werden.
Funktionen
-
Echtzeit-Audio: Die Klangerzeugung erfolgt live, was eine präzise Steuerung der Töne ermöglicht.
-
Wählbare Wellenformen: Wählen Sie zwischen Sinus, Sägezahn, Dreieck und Rechteck, um verschiedene Klangfarben zu erzeugen.
-
Halleffekt (Reverb): Ein einfacher Halleffekt kann hinzugefügt werden, um den Klang zu bereichern.
-
Anpassbare Tastenbelegung: Die Zuordnung der Klaviertasten zu Ihrer Tastatur kann über die Kommandozeile geändert werden.
-
Grafische Darstellung: Eine einfache, visuelle Darstellung der Tasten hilft bei der Orientierung.
Installation und Kompilierung
Um das Programm zu kompilieren und auszuführen, benötigen Sie die SDL2-Bibliotheken sowie die SDL2_ttf-Bibliothek. Die einfachste Methode ist, sie über Ihren Paketmanager zu installieren.
Benötigte Bibliotheken
-
SDL2: Die Hauptbibliothek für Grafik, Audio und Eingabe.
-
SDL2_ttf: Für die Anzeige von Text (Tastenbeschriftungen).
Benötigte Schrift
Truetype Schrift Poetsen One erhältlich hier: >> LINK
Es wird erwartet, dass sich die TTF-Datei sich im selben Verzeichnis wie das ausführbare Programm befindet und den Dateinamen "por.ttf" hat.
Installationsbefehle
Auf Debian/Ubuntu:
bash
sudo apt-get update
sudo apt-get install libsdl2-dev libsdl2-ttf-dev
Auf macOS mit Homebrew:
bash
brew install sdl2 sdl2_ttf
Makefile
Ein Makefile vereinfacht den Kompilierungsprozess erheblich. Speichern Sie den folgenden Inhalt in einer Datei namens Makefile im selben Verzeichnis wie Ihr Quelltext.
CC = gcc
CFLAGS = -Wall -Wextra -std=c99
LDFLAGS = `sdl2-config --cflags --libs` -lSDL2_ttf -lm
TARGET = sdl_klavier
SRC = sdl-klavier.c
all: $(TARGET)
$(TARGET): $(SRC)
$(CC) $(CFLAGS) $(SRC) -o $(TARGET) $(LDFLAGS)
clean:
rm -f $(TARGET)
Mit diesem Makefile können Sie das Programm einfach mit dem Befehl make kompilieren.
Verwendung
Nach der Kompilierung können Sie das Programm über das Terminal ausführen und die Optionen anpassen: bash
./sdl_klavier [OPTIONEN]
Optionen
-
-h oder --help: Zeigt eine Liste aller Optionen an.
-
-k \<Tastenfolge> oder --keys=\<Tastenfolge>: Legt die Tastenbelegung fest. Die Standardbelegung ist ysxdcvgbhnjmq2w3er5t6z7ui9o0p.
-
-w \<Wellenform> oder --waveform=\<Wellenform>: Wählt die Wellenform aus. Mögliche Werte sind sinus, square, saw und triangle. Die Standardeinstellung ist sinus.
-r oder --reverb: Aktiviert einen einfachen Halleffekt.
Beispiele
- Standardausführung (Sinuswelle): bash
./sdl_klavier
- Mit Sägezahnwelle und angepasster Tastenbelegung: Bash
./sdl_klavier --waveform=saw --keys=awsedftgzhujkolp
- Mit Rechteckwelle und Halleffekt: Bash
./sdl_klavier --waveform=square --reverb
Quelltext
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#define SAMPLE_RATE 44100
#define NUM_CHANNELS 1
// Datenstruktur für Audio und Tastenstatus
typedef struct {
float frequencies[64];
float phases[64];
float volumes[64];
int key_map_index[256];
int key_pressed_state[64];
} AudioData;
AudioData audio_data;
// Globale Variablen für Wellenform und Reverb
const char* current_waveform = "sinus";
int enable_reverb = 0;
float* reverb_buffer = NULL;
int reverb_length = 0;
float reverb_decay_factor = 0.5f;
int reverb_read_pos = 0;
int reverb_write_pos = 0;
// Standard-Tastenzuordnung
const char* default_keymap = "ysxdcvgbhnjmq2w3er5t6z7ui9o0p";
const float base_freq = 130.81; // C3
// Funktion zur Wellenformerzeugung
float generate_waveform_sample(float phase) {
if (strcmp(current_waveform, "square") == 0) {
return sinf(phase) > 0.0f ? 1.0f : -1.0f;
} else if (strcmp(current_waveform, "saw") == 0) {
float saw = fmodf(phase / (2.0f * M_PI) + 0.5f, 1.0f) * 2.0f - 1.0f;
return saw;
} else if (strcmp(current_waveform, "triangle") == 0) {
float tri = fmodf(phase / (2.0f * M_PI) + 0.5f, 1.0f) * 2.0f - 1.0f;
tri = fabsf(tri);
return 2.0f * (tri - 0.5f);
} else { // "sinus" oder unbekannt
return sinf(phase);
}
}
// Audio-Callback-Funktion
void myAudioCallback(void* userdata, Uint8* stream, int len) {
float* f_stream = (float*)stream;
int num_samples = len / sizeof(float);
int i, j;
for (i = 0; i < num_samples; i++) {
float current_sample = 0.0f;
for (j = 0; j < 64; j++) {
if (audio_data.volumes[j] > 0.0f) {
float phase_increment = 2.0f * M_PI * audio_data.frequencies[j] / SAMPLE_RATE;
current_sample += generate_waveform_sample(audio_data.phases[j]) * audio_data.volumes[j];
audio_data.phases[j] += phase_increment;
if (audio_data.phases[j] >= 2.0f * M_PI) {
audio_data.phases[j] -= 2.0f * M_PI;
}
if (audio_data.key_pressed_state[j] == 1) {
audio_data.volumes[j] *= 0.99999f;
} else {
audio_data.volumes[j] *= 0.90f;
}
if (audio_data.volumes[j] < 0.001f) {
audio_data.volumes[j] = 0.0f;
}
}
}
// Reverb-Logik hinzufügen
if (enable_reverb) {
float reverb_sample = reverb_buffer[reverb_read_pos];
reverb_buffer[reverb_write_pos] = current_sample + reverb_sample * reverb_decay_factor;
current_sample += reverb_sample;
reverb_write_pos = (reverb_write_pos + 1) % reverb_length;
reverb_read_pos = (reverb_read_pos + 1) % reverb_length;
}
f_stream[i] = current_sample;
}
}
// Funktion zum Zeichnen der Tastatur mit korrekter Zuordnung
void draw_keyboard(SDL_Renderer* renderer, const char* keymap, TTF_Font* font) {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
const int is_black_key[] = {0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0};
int white_key_width = 40;
int black_key_width = white_key_width / 2;
int x_offset_white = 0;
for (int i = 0; i < strlen(keymap); i++) {
if (is_black_key[i] == 0) {
SDL_Rect rect = {x_offset_white * white_key_width, 0, white_key_width, 200};
int key_index = audio_data.key_map_index[(unsigned char)keymap[i]];
if (key_index != -1 && audio_data.key_pressed_state[key_index] == 1) {
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
} else {
SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255);
}
SDL_RenderFillRect(renderer, &rect);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderDrawRect(renderer, &rect);
SDL_Color color = {0, 0, 0, 255};
char key_char[2] = {keymap[i], '\0'};
if (font) {
SDL_Surface* surface = TTF_RenderText_Solid(font, key_char, color);
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_Rect text_rect = {x_offset_white * white_key_width + white_key_width / 2 - 5, 170, 0, 0};
SDL_QueryTexture(texture, NULL, NULL, &text_rect.w, &text_rect.h);
SDL_RenderCopy(renderer, texture, NULL, &text_rect);
SDL_FreeSurface(surface);
SDL_DestroyTexture(texture);
}
x_offset_white++;
}
}
x_offset_white = 0;
for (int i = 0; i < strlen(keymap); i++) {
if (is_black_key[i] == 1) {
SDL_Rect rect = {x_offset_white * white_key_width - (black_key_width / 2), 0, black_key_width, 100};
int key_index = audio_data.key_map_index[(unsigned char)keymap[i]];
if (key_index != -1 && audio_data.key_pressed_state[key_index] == 1) {
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
} else {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
}
SDL_RenderFillRect(renderer, &rect);
SDL_Color color = {255, 255, 255, 255};
char key_char[2] = {keymap[i], '\0'};
if (font) {
SDL_Surface* surface = TTF_RenderText_Solid(font, key_char, color);
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_Rect text_rect = {x_offset_white * white_key_width - (black_key_width / 2) + black_key_width / 2 - 5, 70, 0, 0};
SDL_QueryTexture(texture, NULL, NULL, &text_rect.w, &text_rect.h);
SDL_RenderCopy(renderer, texture, NULL, &text_rect);
SDL_FreeSurface(surface);
SDL_DestroyTexture(texture);
}
} else {
x_offset_white++;
}
}
SDL_RenderPresent(renderer);
}
// Hilfetext ausgeben
void print_help() {
printf("Tastaturklavier unter SDL2 2025 (C) Sascha Waidmann\n");
printf(" >> https://home.nachwuchs.org/index.php?id=90\n\n");
printf("Verwendung: ./sdl_klavier [OPTIONEN]\n\n");
printf("Optionen:\n");
printf(" -h, --help Diesen Hilfetext anzeigen.\n");
printf(" -k, --keys=<Tastenfolge> Legt die Tastenzuordnung fest. Standard: %s\n", default_keymap);
printf(" -w, --waveform=<Wellenform> Wählt die Wellenform aus (sinus, square, saw, triangle). Standard: sinus\n");
printf(" -r, --reverb Aktiviert einen einfachen Halleffekt.\n");
}
int main(int argc, char* argv[]) {
SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
TTF_Font* font = NULL;
SDL_AudioDeviceID device;
const char* keymap_str = default_keymap;
// Kommandozeilen-Argumente verarbeiten
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
print_help();
return 0;
} else if (strcmp(argv[i], "-k") == 0) {
if (i + 1 < argc) {
keymap_str = argv[++i];
}
} else if (strncmp(argv[i], "--keys=", 7) == 0) {
keymap_str = argv[i] + 7;
} else if (strcmp(argv[i], "-w") == 0) {
if (i + 1 < argc) {
current_waveform = argv[++i];
}
} else if (strncmp(argv[i], "--waveform=", 11) == 0) {
current_waveform = argv[i] + 11;
} else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--reverb") == 0) {
enable_reverb = 1;
}
}
// Initialisiere Reverb-Buffer, falls aktiviert
if (enable_reverb) {
float reverb_delay_ms = 100.0f; // 100 ms Verzögerung
reverb_length = (int)(reverb_delay_ms / 1000.0f * SAMPLE_RATE);
reverb_buffer = (float*)calloc(reverb_length, sizeof(float));
if (!reverb_buffer) {
fprintf(stderr, "Fehler: Speicher fuer Reverb-Buffer konnte nicht alloziiert werden.\n");
return 1;
}
}
// SDL-Initialisierung
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) {
printf("SDL konnte nicht initialisiert werden: %s\n", SDL_GetError());
if (reverb_buffer) free(reverb_buffer);
return 1;
}
if (TTF_Init() == -1) {
printf("SDL_ttf konnte nicht initialisiert werden: %s\n", TTF_GetError());
SDL_Quit();
if (reverb_buffer) free(reverb_buffer);
return 1;
}
// Fenster und Renderer erstellen
window = SDL_CreateWindow("SDL-Klavier", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 200, SDL_WINDOW_SHOWN);
if (!window) {
printf("Fenster konnte nicht erstellt werden: %s\n", SDL_GetError());
TTF_Quit();
SDL_Quit();
if (reverb_buffer) free(reverb_buffer);
return 1;
}
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {
printf("Renderer konnte nicht erstellt werden: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
if (reverb_buffer) free(reverb_buffer);
return 1;
}
font = TTF_OpenFont("por.ttf", 16);
if (!font) {
printf("Schriftart 'por.ttf' konnte nicht geladen werden: %s\n", TTF_GetError());
printf("Es wird erwartet, dass sich eine Schriftart namens 'por.ttf' im selben Verzeichnis befindet.\n", TTF_GetError());
}
// Initialisierung der Audio-Logik
SDL_AudioSpec wanted, obtained;
SDL_zero(wanted);
wanted.freq = SAMPLE_RATE;
wanted.format = AUDIO_F32SYS;
wanted.channels = NUM_CHANNELS;
wanted.samples = 1024;
wanted.callback = myAudioCallback;
device = SDL_OpenAudioDevice(NULL, 0, &wanted, &obtained, 0);
if (device == 0) {
printf("SDL konnte kein Audiogerät öffnen: %s\n", SDL_GetError());
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
if (reverb_buffer) free(reverb_buffer);
return 1;
}
SDL_PauseAudioDevice(device, 0);
// Frequenzen für die Tastenbelegung
for (int i = 0; i < strlen(keymap_str); i++) {
unsigned char key_code = (unsigned char)keymap_str[i];
audio_data.frequencies[i] = base_freq * powf(2.0, (float)i / 12.0);
audio_data.key_map_index[key_code] = i;
}
printf("Klavier gestartet. Wellenform: %s%s\n", current_waveform, enable_reverb ? " (Halleffekt aktiviert)" : "");
printf("Tastaturbelegung: %s\n", keymap_str);
SDL_Event event;
int running = 1;
while (running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
running = 0;
} else if (event.type == SDL_KEYDOWN) {
if (event.key.keysym.sym == SDLK_ESCAPE) {
running = 0;
break;
} else if (event.key.repeat == 0) {
unsigned char key_code = (unsigned char)event.key.keysym.sym;
int key_index = audio_data.key_map_index[key_code];
if (key_index != -1) {
audio_data.volumes[key_index] = 1.0f;
audio_data.key_pressed_state[key_index] = 1;
}
}
} else if (event.type == SDL_KEYUP) {
unsigned char key_code = (unsigned char)event.key.keysym.sym;
int key_index = audio_data.key_map_index[key_code];
if (key_index != -1) {
audio_data.key_pressed_state[key_index] = 0;
}
}
}
draw_keyboard(renderer, keymap_str, font);
SDL_Delay(10);
}
// Aufräumen
if (reverb_buffer) free(reverb_buffer);
if (font) TTF_CloseFont(font);
SDL_CloseAudioDevice(device);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
return 0;
}
Lizenz
GPL V3 oder neuer
Download
Format tar.xz | LINK |
Format zip | LINK |