#define _POSIX_C_SOURCE 200809L #include "patch.h" #include #include #include #include #ifdef _WIN32 # include # include # define MAKE_DIR(p) _mkdir(p) #else # include # define MAKE_DIR(p) mkdir(p, 0755) #endif // ------------------------------------------------------------------ HELPERS static const char *findValue(const char *json, const char *key) { char pattern[PATCH_NAME_LEN + 4]; snprintf(pattern, sizeof(pattern), "\"%s\"", key); const char *p = strstr(json, pattern); if (!p) return NULL; p += strlen(pattern); while (*p && (*p == ' ' || *p == ':' || *p == '\t' || *p == '\n' || *p == '\r')) p++; return p; } static int readInt(const char *json, const char *key, int *out) { const char *p = findValue(json, key); if (!p) return 0; *out = (int)strtol(p, NULL, 10); return 1; } static int readFloat(const char *json, const char *key, float *out) { const char *p = findValue(json, key); if (!p) return 0; *out = strtof(p, NULL); return 1; } // ------------------------------------------------------------------ SAVE int patchSave(Synth *s, const char *path) { MAKE_DIR("patches"); FILE *f = fopen(path, "w"); if (!f) return 0; fprintf(f, "{\n"); for (int o = 0; o < OSC_COUNT; o++) { Oscillator *osc = &s->voices[0].oscillators[o]; fprintf(f, " \"osc%d_waveform\": %d,\n", o, (int)osc->waveform); fprintf(f, " \"osc%d_dutyCycle\": %f,\n", o, osc->dutyCycle); fprintf(f, " \"osc%d_detune\": %f,\n", o, osc->detune); fprintf(f, " \"osc%d_gain\": %f,\n", o, osc->gain); fprintf(f, " \"osc%d_active\": %d,\n", o, osc->active); fprintf(f, " \"osc%d_octave\": %d,\n", o, osc->octave); for (int m = 0; m < 3; m++) fprintf(f, " \"osc%d_modRouting%d\": %d,\n", o, m, osc->modRouting[m]); for (int m = 0; m < 3; m++) fprintf(f, " \"osc%d_modDepth%d\": %f,\n", o, m, osc->modDepth[m]); } Envelope *ae = &s->voices[0].ampEnv; fprintf(f, " \"ampEnv_attack\": %f,\n", ae->attackSec); fprintf(f, " \"ampEnv_decay\": %f,\n", ae->decaySec); fprintf(f, " \"ampEnv_sustain\": %f,\n", ae->sustainLevel); fprintf(f, " \"ampEnv_release\": %f,\n", ae->releaseSec); Envelope *me = &s->voices[0].modEnv; fprintf(f, " \"modEnv_attack\": %f,\n", me->attackSec); fprintf(f, " \"modEnv_decay\": %f,\n", me->decaySec); fprintf(f, " \"modEnv_sustain\": %f,\n", me->sustainLevel); fprintf(f, " \"modEnv_release\": %f,\n", me->releaseSec); for (int l = 0; l < LFO_COUNT; l++) { LFO *lfo = &s->lfos[l]; fprintf(f, " \"lfo%d_rate\": %f,\n", l, lfo->rate); fprintf(f, " \"lfo%d_waveform\": %d,\n", l, (int)lfo->waveform); fprintf(f, " \"lfo%d_active\": %d,\n", l, lfo->active); } Filter *flt = &s->voices[0].filter; fprintf(f, " \"filter_cutoff\": %f,\n", flt->cutoff); fprintf(f, " \"filter_resonance\": %f,\n", flt->resonance); fprintf(f, " \"filter_type\": %d,\n", (int)flt->type); fprintf(f, " \"filter_active\": %d,\n", flt->active); fprintf(f, " \"filter_modRouting\": %d,\n", flt->modRouting); fprintf(f, " \"filter_modDepth\": %f,\n", flt->modDepth); fprintf(f, " \"filter_resModRouting\": %d,\n", flt->resModRouting); fprintf(f, " \"filter_resModDepth\": %f,\n", flt->resModDepth); fprintf(f, " \"master_volume\": %f,\n", s->volume); fprintf(f, " \"master_pitchBendRange\": %f\n", s->pitchBendRange); fprintf(f, "}\n"); fclose(f); return 1; } // ------------------------------------------------------------------ LOAD int patchLoad(Synth *s, const char *path) { FILE *f = fopen(path, "r"); if (!f) return 0; fseek(f, 0, SEEK_END); long size = ftell(f); fseek(f, 0, SEEK_SET); char *buf = (char *)malloc((size_t)size + 1); if (!buf) { fclose(f); return 0; } size_t got = fread(buf, 1, (size_t)size, f); buf[got] = '\0'; fclose(f); int iv; float fv; char key[PATCH_NAME_LEN]; for (int o = 0; o < OSC_COUNT; o++) { Oscillator *osc = &s->voices[0].oscillators[o]; snprintf(key, sizeof(key), "osc%d_waveform", o); if (readInt(buf, key, &iv) && iv >= 0 && iv < WAVE_COUNT) osc->waveform = (Waveform)iv; snprintf(key, sizeof(key), "osc%d_dutyCycle", o); if (readFloat(buf, key, &fv)) osc->dutyCycle = fv; snprintf(key, sizeof(key), "osc%d_detune", o); if (readFloat(buf, key, &fv)) osc->detune = fv; snprintf(key, sizeof(key), "osc%d_gain", o); if (readFloat(buf, key, &fv)) osc->gain = fv; snprintf(key, sizeof(key), "osc%d_active", o); if (readInt(buf, key, &iv)) osc->active = iv; snprintf(key, sizeof(key), "osc%d_octave", o); if (readInt(buf, key, &iv) && iv >= -2 && iv <= 2) osc->octave = iv; for (int m = 0; m < 3; m++) { snprintf(key, sizeof(key), "osc%d_modRouting%d", o, m); if (readInt(buf, key, &iv)) osc->modRouting[m] = iv; snprintf(key, sizeof(key), "osc%d_modDepth%d", o, m); if (readFloat(buf, key, &fv)) osc->modDepth[m] = fv; } } Envelope *ae = &s->voices[0].ampEnv; if (readFloat(buf, "ampEnv_attack", &fv)) ae->attackSec = fv; if (readFloat(buf, "ampEnv_decay", &fv)) ae->decaySec = fv; if (readFloat(buf, "ampEnv_sustain", &fv)) ae->sustainLevel = fv; if (readFloat(buf, "ampEnv_release", &fv)) ae->releaseSec = fv; Envelope *me = &s->voices[0].modEnv; if (readFloat(buf, "modEnv_attack", &fv)) me->attackSec = fv; if (readFloat(buf, "modEnv_decay", &fv)) me->decaySec = fv; if (readFloat(buf, "modEnv_sustain", &fv)) me->sustainLevel = fv; if (readFloat(buf, "modEnv_release", &fv)) me->releaseSec = fv; for (int l = 0; l < LFO_COUNT; l++) { LFO *lfo = &s->lfos[l]; snprintf(key, sizeof(key), "lfo%d_rate", l); if (readFloat(buf, key, &fv)) lfo->rate = fv; snprintf(key, sizeof(key), "lfo%d_waveform", l); if (readInt(buf, key, &iv) && iv >= 0 && iv < WAVE_COUNT) lfo->waveform = (Waveform)iv; snprintf(key, sizeof(key), "lfo%d_active", l); if (readInt(buf, key, &iv)) lfo->active = iv; lfo->phase = 0.0f; lfo->noisePhase = 0.0f; } Filter *flt = &s->voices[0].filter; if (readFloat(buf, "filter_cutoff", &fv)) flt->cutoff = fv; if (readFloat(buf, "filter_resonance", &fv)) flt->resonance = fv; if (readInt (buf, "filter_type", &iv) && iv >= 0 && iv < FILTER_COUNT) flt->type = (FilterType)iv; if (readInt (buf, "filter_active", &iv)) flt->active = iv; if (readInt (buf, "filter_modRouting", &iv)) flt->modRouting = iv; if (readFloat(buf, "filter_modDepth", &fv)) flt->modDepth = fv; if (readInt (buf, "filter_resModRouting",&iv)) flt->resModRouting = iv; if (readFloat(buf, "filter_resModDepth", &fv)) flt->resModDepth = fv; flt->low = 0.0f; flt->band = 0.0f; if (readFloat(buf, "master_volume", &fv)) s->volume = fv; if (readFloat(buf, "master_pitchBendRange", &fv)) s->pitchBendRange = fv; free(buf); return 1; } // ------------------------------------------------------------------ FAVORITES int patchLoadFavs(char (*favs)[PATCH_NAME_LEN], int maxFavs) { FILE *f = fopen("patches/favorites.txt", "r"); if (!f) return 0; int count = 0; while (count < maxFavs && fgets(favs[count], PATCH_NAME_LEN, f)) { size_t len = strlen(favs[count]); if (len > 0 && favs[count][len - 1] == '\n') favs[count][len - 1] = '\0'; if (favs[count][0] != '\0') count++; } fclose(f); return count; } int patchSaveFavs(char (*favs)[PATCH_NAME_LEN], int favCount) { MAKE_DIR("patches"); FILE *f = fopen("patches/favorites.txt", "w"); if (!f) return 0; for (int i = 0; i < favCount; i++) fprintf(f, "%s\n", favs[i]); fclose(f); return 1; } // ------------------------------------------------------------------ SCAN static int cmpStrings(const void *a, const void *b) { return strcmp((const char *)a, (const char *)b); } int patchScanFiles(char (*files)[PATCH_NAME_LEN], int maxFiles) { int count = 0; DIR *d = opendir("patches"); if (!d) return 0; struct dirent *ent; while ((ent = readdir(d)) != NULL && count < maxFiles) { const char *name = ent->d_name; size_t len = strlen(name); if (len > 5 && strcmp(name + len - 5, ".json") == 0) { snprintf(files[count], PATCH_NAME_LEN, "%.*s", (int)(len - 5), name); count++; } } closedir(d); qsort(files, (size_t)count, PATCH_NAME_LEN, cmpStrings); return count; } int patchScanDir(const char *relPath, char (*files)[PATCH_NAME_LEN], int maxFiles, char (*dirs)[PATCH_NAME_LEN], int maxDirs, int *outDirCount) { char scanPath[512]; if (relPath && relPath[0]) snprintf(scanPath, sizeof(scanPath), "patches/%s", relPath); else snprintf(scanPath, sizeof(scanPath), "patches"); MAKE_DIR(scanPath); int fc = 0, dc = 0; DIR *d = opendir(scanPath); if (!d) { *outDirCount = 0; return 0; } struct dirent *ent; while ((ent = readdir(d)) != NULL) { const char *name = ent->d_name; if (name[0] == '.') continue; size_t len = strlen(name); char full[1024]; snprintf(full, sizeof(full), "%s/%s", scanPath, name); struct stat st; if (stat(full, &st) != 0) continue; if (S_ISDIR(st.st_mode)) { if (dc < maxDirs) snprintf(dirs[dc++], PATCH_NAME_LEN, "%s", name); } else if (len > 5 && strcmp(name + len - 5, ".json") == 0) { if (fc < maxFiles) snprintf(files[fc++], PATCH_NAME_LEN, "%.*s", (int)(len - 5), name); } } closedir(d); qsort(files, (size_t)fc, PATCH_NAME_LEN, cmpStrings); qsort(dirs, (size_t)dc, PATCH_NAME_LEN, cmpStrings); *outDirCount = dc; return fc; }