title logo
title right side
03.09.2025 19:24:22 Lokal:
Serverstatus: (1.24/1.80/2.0361.53/1002.3 GByte  
Aussentemperatur : 22.1 °C  Luftdruck : 1014.74 hPas/mbar
Impressions: 31 
Visits heute: 163 

  Aktuelle Seite: SDL2-Klavier

Home  

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