From f0a57d10dd93580ef0b553b8781185fdc09b2da0 Mon Sep 17 00:00:00 2001 From: grm Date: Thu, 16 Oct 2025 00:47:21 +0300 Subject: Add spinner for sound picking --- b.c | 8 +- src/os.c | 255 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/os.h | 7 ++ src/osc.h | 2 + src/osc_sound.c | 10 +++ src/synth_gui.c | 206 ++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 484 insertions(+), 4 deletions(-) create mode 100644 src/os.c create mode 100644 src/os.h diff --git a/b.c b/b.c index f8c56a2..d063bb8 100644 --- a/b.c +++ b/b.c @@ -5,7 +5,7 @@ #define BUILD_DIR "build/" #define TEMPLATE_DIR BUILD_DIR"templates/" -int debug_level = 0; +int debug_level = 5; bool force = false; bool tests = false; @@ -73,7 +73,7 @@ build_c(B_Cmd* cmd, if (rebuild_is_needed == 0) dep_rebuild = b_needs_rebuild(B_COMPILE, output_path, dep_paths, dep_paths_len); - + if (rebuild_is_needed < 0 || dep_rebuild < 0) return false; @@ -186,6 +186,7 @@ main(int argc, char *argv[]) "src/types.h", "src/web.h", "src/gui.h", + "src/os.h", BUILD_DIR"templates/index.html.h", }; @@ -211,12 +212,13 @@ main(int argc, char *argv[]) "src/synth_engine_v2.c", "src/synth_gui.c", "src/web.c", + "src/os.c", }; const char *templates[] = { "tmpl/index.html" }; - + B_Cmd cmd = {0}; b_mkdir_if_not_exists(BUILD_DIR); diff --git a/src/os.c b/src/os.c new file mode 100644 index 0000000..c9f4ee8 --- /dev/null +++ b/src/os.c @@ -0,0 +1,255 @@ +#include "os.h" + +#include +#include +#include +#include +#include + +// Function to check if a string ends with a specific suffix (case insensitive) +int ends_with_wav(const char *str) { + size_t len = strlen(str); + if (len < 4) return 0; + + const char *suffix = str + len - 4; + return (strcasecmp(suffix, ".wav") == 0); +} + +// Function to extract the example name from folder format: "example name[2048-44.1khz-32bit]" +char* extract_example_name(const char *folder_name) { + char *result = malloc(strlen(folder_name) + 1); + if (!result) return NULL; + + strcpy(result, folder_name); + + // Find the opening bracket and terminate the string there + char *bracket = strchr(result, '['); + if (bracket) { + *bracket = '\0'; + } + + // Remove trailing whitespace + int len = strlen(result); + while (len > 0 && (result[len-1] == ' ' || result[len-1] == '\t')) { + result[--len] = '\0'; + } + + return result; +} + +char* scan_for_wav(const char *directory_path) { + DIR *dir; + struct dirent *entry; + struct stat statbuf; + char full_path[1024]; + char *result = NULL; + size_t result_size = 0; + size_t result_capacity = 1024; + int first_entry = 1; + + // Initialize result buffer + result = malloc(result_capacity); + if (!result) return NULL; + result[0] = '\0'; + + // Open directory + dir = opendir(directory_path); + if (!dir) { + free(result); + return NULL; + } + + // Iterate through directory entries + while ((entry = readdir(dir)) != NULL) { + // Skip . and .. entries + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + // Create full path + snprintf(full_path, sizeof(full_path), "%s/%s", directory_path, entry->d_name); + + // Check if it's a directory + if (stat(full_path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) { + // This is a subdirectory, scan for WAV files in it + DIR *subdir; + struct dirent *subentry; + + subdir = opendir(full_path); + if (!subdir) continue; + + // Extract example name from folder name + char *example_name = extract_example_name(entry->d_name); + if (!example_name) { + closedir(subdir); + continue; + } + + // Look for WAV files in subdirectory + while ((subentry = readdir(subdir)) != NULL) { + if (ends_with_wav(subentry->d_name)) { + // Calculate needed space for new entry (full path) + size_t needed_space = strlen(full_path) + 1 + strlen(subentry->d_name) + 2; // +2 for ';' and '\0' + if (!first_entry) needed_space += 1; // for semicolon separator + + // Resize buffer if needed + while (result_size + needed_space >= result_capacity) { + result_capacity *= 2; + char *new_result = realloc(result, result_capacity); + if (!new_result) { + free(result); + free(example_name); + closedir(subdir); + closedir(dir); + return NULL; + } + result = new_result; + } + + // Add separator if not first entry + if (!first_entry) { + strcat(result, ";"); + result_size += 1; + } + + // Add the full path + strcat(result, full_path); + strcat(result, "/"); + strcat(result, subentry->d_name); + result_size += strlen(full_path) + 1 + strlen(subentry->d_name); + + first_entry = 0; + } + } + + free(example_name); + closedir(subdir); + } + } + + closedir(dir); + + // If no WAV files found, return empty string + if (result_size == 0) { + strcpy(result, ""); + } + + return result; +} + + +// Main function to scan directory and return formatted string +char* scan_for_wav_pretty(const char *directory_path) { + DIR *dir; + struct dirent *entry; + struct stat statbuf; + char full_path[1024]; + char *result = NULL; + size_t result_size = 0; + size_t result_capacity = 1024; + int first_entry = 1; + + // Initialize result buffer + result = malloc(result_capacity); + if (!result) return NULL; + result[0] = '\0'; + + // Open directory + dir = opendir(directory_path); + if (!dir) { + free(result); + return NULL; + } + + // Iterate through directory entries + while ((entry = readdir(dir)) != NULL) { + // Skip . and .. entries + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + // Create full path + snprintf(full_path, sizeof(full_path), "%s/%s", directory_path, entry->d_name); + + // Check if it's a directory + if (stat(full_path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) { + // This is a subdirectory, scan for WAV files in it + DIR *subdir; + struct dirent *subentry; + + subdir = opendir(full_path); + if (!subdir) continue; + + // Extract example name from folder name + char *example_name = extract_example_name(entry->d_name); + if (!example_name) { + closedir(subdir); + continue; + } + + // Look for WAV files in subdirectory + while ((subentry = readdir(subdir)) != NULL) { + if (ends_with_wav(subentry->d_name)) { + // Calculate needed space for new entry + size_t needed_space = strlen(example_name) + 1 + strlen(subentry->d_name) + 2; // +2 for ';' and '\0' + if (!first_entry) needed_space += 1; // for semicolon separator + + // Resize buffer if needed + while (result_size + needed_space >= result_capacity) { + result_capacity *= 2; + char *new_result = realloc(result, result_capacity); + if (!new_result) { + free(result); + free(example_name); + closedir(subdir); + closedir(dir); + return NULL; + } + result = new_result; + } + + // Add separator if not first entry + if (!first_entry) { + strcat(result, ";"); + result_size += 1; + } + + // Add the formatted entry + strcat(result, example_name); + strcat(result, "/"); + strcat(result, subentry->d_name); + result_size += strlen(example_name) + 1 + strlen(subentry->d_name); + + first_entry = 0; + } + } + + free(example_name); + closedir(subdir); + } + } + + closedir(dir); + + // If no WAV files found, return empty string + if (result_size == 0) { + strcpy(result, ""); + } + + return result; +} + +/* // Example usage */ +/* int main() { */ +/* const char *test_dir = "/path/to/your/directory"; */ +/* char *result = scan_wav_directory(test_dir); */ + +/* if (result) { */ +/* printf("Found WAV files: %s\n", result); */ +/* free(result); */ +/* } else { */ +/* printf("Error scanning directory or no WAV files found.\n"); */ +/* } */ + +/* return 0; */ +/* } */ diff --git a/src/os.h b/src/os.h new file mode 100644 index 0000000..78617bc --- /dev/null +++ b/src/os.h @@ -0,0 +1,7 @@ +#ifndef OS_H +#define OS_H + +char* scan_for_wav_pretty(const char *directory_path); +char* scan_for_wav(const char *directory_path); + +#endif /* OS_H */ diff --git a/src/osc.h b/src/osc.h index 9cd0472..ef85225 100644 --- a/src/osc.h +++ b/src/osc.h @@ -57,6 +57,8 @@ int osc_next_index(osc_t * osc, float offset); int osc_load_wav(osc_t * osc, const char * path); +void osc_sound_change_wavetable(char *path); + osc_t * make_tri(const char * name); /***************/ diff --git a/src/osc_sound.c b/src/osc_sound.c index 10da73b..fc06a26 100644 --- a/src/osc_sound.c +++ b/src/osc_sound.c @@ -34,6 +34,16 @@ osc_sound(float offset) OSC_sound.data[osc_next_index(&OSC_sound, offset)]); } +void +osc_sound_change_wavetable(char *path) +{ + float * old_data = OSC_sound.data; + osc_load_wav(&OSC_sound, path); + OSC_sound.start = wvt_size*0; + OSC_sound.len = OSC_sound.start + wvt_size; + free(old_data); +} + float osc_sound_next(float f, float offset) { diff --git a/src/synth_gui.c b/src/synth_gui.c index 62a6ab7..3fa2536 100644 --- a/src/synth_gui.c +++ b/src/synth_gui.c @@ -3,6 +3,7 @@ #include #define RAYGUI_IMPLEMENTATION #include "raygui.h" +#include "os.h" //#include "raylib.h" void @@ -895,6 +896,205 @@ draw_audiomidisetup(synth_t *synth) } } +char** split_string(const char* str, char delimiter, int* itemCount) { + if (!str || !itemCount) { + if (itemCount) *itemCount = 0; + return NULL; + } + + // First pass: count delimiters to determine array size + int count = 1; + for (int i = 0; str[i]; i++) { + if (str[i] == delimiter) { + count++; + } + } + + // Allocate array of string pointers + char** result = malloc(count * sizeof(char*)); + if (!result) { + *itemCount = 0; + return NULL; + } + + // Create a working copy of the string + char* str_copy = malloc(strlen(str) + 1); + if (!str_copy) { + free(result); + *itemCount = 0; + return NULL; + } + strcpy(str_copy, str); + + // Second pass: split the string + int index = 0; + char* token = str_copy; + char* next_delim; + + while ((next_delim = strchr(token, delimiter)) != NULL) { + *next_delim = '\0'; // Replace delimiter with null terminator + + // Allocate memory for this substring and copy it + result[index] = malloc(strlen(token) + 1); + if (result[index]) { + strcpy(result[index], token); + } + index++; + token = next_delim + 1; // Move to next token + } + + // Handle the last token (after the last delimiter or the whole string if no delimiters) + result[index] = malloc(strlen(token) + 1); + if (result[index]) { + strcpy(result[index], token); + } + + free(str_copy); + *itemCount = count; + return result; +} + +// Function to free the memory allocated by split_string +void free_split_result(char** result, int itemCount) { + if (result) { + for (int i = 0; i < itemCount; i++) { + free(result[i]); + } + free(result); + } +} + +int +gui_string_spinner(Rectangle rect, char * text, int * index) +{ + int itemCount = 0; + char **items = NULL; + int change = 0; + + float m = GetMouseWheelMove(); + int x = 0; + if (m < 0) x = -1; + else if (m > 0) x = 1; + Vector2 p = GetMousePosition(); + + if (text != NULL) items = split_string(text, ';', &itemCount); + + if (CheckCollisionPointRec(p, rect)) { + if (x > 0) { + if (IsKeyDown(KEY_LEFT_SHIFT)) { + (*index)+=10; + } else { + (*index)++; + } + if (*index >= itemCount) { + *index = itemCount - 1; // set to itemCound to loop + } + change = 1; + } else if (x < 0) { + if (IsKeyDown(KEY_LEFT_SHIFT)) { + (*index)-=10; + } else { + (*index)--; + } + if (*index <= -1) { + *index = 0; // set to itemCound to loop + } + change = 1; + } + } + + if (GuiButton((Rectangle){rect.x, rect.y, rect.height, rect.height}, "<")) { + (*index)--; + if (*index <= -1) { + *index = 0; // set to itemCound to loop + } + change = 1; + } + float box_x = rect.x + (rect.height + 1); + float box_width = rect.width - (rect.height + 1) - (rect.height + 1); + DrawRectangleLines(box_x, rect.y, box_width, rect.height, WHITE); + + int text_size = 10; + + char buf[1024] = ""; + + sprintf(buf, "%d: %s", *index, items[*index]); + DrawText(buf, + box_x + box_width/2 - (float)MeasureText(buf, text_size) / 2, + rect.y + rect.height / 2 - (float)text_size / 2, + text_size, + WHITE); + + if (GuiButton((Rectangle){rect.x + (rect.height + 1) + rect.width - (rect.height + 1) - (rect.height + 1), rect.y, rect.height, rect.height}, ">")) { + (*index)++; + if (*index >= itemCount) { + *index = itemCount - 1; // set to itemCound to loop + } + change = 1; + } + + free_split_result(items, itemCount); + + return change; +} + +int prev_active = -1; + +int sound_file_picker_initialized = 0; +char * wavs = NULL; +char * wavs_full = NULL; + +void +draw_sound_file_picker(Rectangle rect) +{ + static int scroll_index = 0; // Current scroll position + static int active_item = 0; // Currently selected item (-1 means none selected) + + if (!sound_file_picker_initialized) { + wavs = scan_for_wav_pretty("/home/grm/code/synth-project-b/waves/Free Wavetables[2048]"); + //wavs[strlen(wavs) - 1] = '\0'; // remove last ; + wavs_full = scan_for_wav("/home/grm/code/synth-project-b/waves/Free Wavetables[2048]"); + //wavs_full[strlen(wavs_full) - 1] = '\0'; + /* printf("strlen wavs: %ld\n", strlen(wavs)); */ + /* printf("strlen wavs_full: %ld\n", strlen(wavs_full)); */ + sound_file_picker_initialized = 1; + } + + /* char aaa[10000] = ""; */ + /* char *t = aaa; */ + /* int i = 0; */ + /* for (int j =0; j < 1000; j++) { */ + /* sprintf(t, "%d;", i++); */ + /* t += strlen(t); */ + /* } */ + /* aaa[strlen(aaa) - 1] = '\0'; */ + + if (gui_string_spinner(rect, wavs, &scroll_index)) { + char path[1024] = ""; + get_nth_entry(wavs_full, scroll_index, path); + printf("changing wavetable to: %s\n", path); + osc_sound_change_wavetable(path); + } + prev_active = active_item; + //active_item = GuiListView(rect, aaa, &scroll_index, active_item); + /* if (prev_active != active_item && active_item != -1) { */ + /* char path[1024] = ""; */ + /* get_nth_entry(wavs_full, active_item, path); */ + /* printf("changing wavetable to: %s\n", path); */ + /* osc_sound_change_wavetable(path); */ + /* // sound changed */ + /* } */ + /* if (GuiDropdownBox((Rectangle){WIDTH - 300 - 50, */ + /* 12 + 24 + 6, 300, */ + /* 24}, */ + /* wavs, &wav_pick, edit_wav)) { */ + /* edit_wav = !edit_wav; */ + /* } */ + //free(wavs); + //free(wavs_full); + +} + void draw_main(synth_t *synth) { @@ -985,6 +1185,10 @@ draw_main(synth_t *synth) // signals draw_signals(synth, 20, 390, WIDTH - 2*20, 200); + + if (synth->geni == 4) + draw_sound_file_picker((Rectangle){ WIDTH / 2.0 - 128, 150 + 122, 256 + 60, 24 }); + //draw_signals(synth, 300, 390, WIDTH - 2*300, 200); //DrawText("THE SYNTH!!!!!!!!!!!!!!!!!!1", WIDTH / 2 - 100, 50, 20, LIGHTGRAY); @@ -1049,7 +1253,7 @@ rayrun(synth_t *synth){ //---------------------------------------------------------------------------------- BeginDrawing(); ClearBackground(BLACK); - + if (synth->gui.screen == SCREEN_MAIN) draw_main(synth); else if (synth->gui.screen == SCREEN_AUDIOMIDISETUP) -- cgit v1.2.3