v0.9.5 beta — full feature build ready for distribution

Core additions since initial commit:
- Platform-split MIDI (midi_linux.c / midi_windows.c)
- Patch system: save/load/scan, subdirectory navigation, favourites
- Config system: last MIDI device and CC mappings persisted to disk
- Custom embedded pixel font and sprite sheet (no loose asset files at runtime)
- Window icon embedded at runtime (SetWindowIcon) and in Windows .exe (windres)
- chdirToExeDir() in platform.c so double-click launch finds patches/ correctly
- About screen accessible from Master panel

UI polish:
- TITLE_BAR_H constant (20 px) — sprite buttons now fit inside title bars
- All title bar text, button overlay text, and close/active labels vertically centred
- Full-screen dim overlay computed from camera transform (no more partial coverage)
- Patch browser: folder navigation, breadcrumb title, 256-slot limit lifted

Build:
- Makefile auto-discovers sources; embeds sprites/font/icon via xxd rules
- Windows cross-compile: make PLATFORM=windows (mingw-w64 + windres)
- windows.h isolated in platform.c to avoid Rectangle/CloseWindow conflicts with raylib.h
- WIN_RES uses lazy = assignment so OBJ_DIR expands correctly for windres output path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Anachronaut
2026-07-07 19:18:34 -04:00
parent 801ea22d3e
commit 75e0894ff8
70 changed files with 5910 additions and 237 deletions

91
source/config.c Normal file
View File

@@ -0,0 +1,91 @@
#define _POSIX_C_SOURCE 200809L
#include "config.h"
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
# include <direct.h>
# define MAKE_DIR(p) _mkdir(p)
#else
# include <sys/stat.h>
# define MAKE_DIR(p) mkdir(p, 0755)
#endif
void configSanitizeName(const char *in, char *out, int maxLen)
{
int i = 0;
for (; in[i] && i < maxLen - 1; i++) {
char c = in[i];
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '_' || c == '-')
out[i] = c;
else
out[i] = '_';
}
out[i] = '\0';
}
int configSaveLastDevice(const char *deviceName)
{
MAKE_DIR(CONFIG_DIR);
FILE *f = fopen(CONFIG_DIR "/last_device.txt", "w");
if (!f) return 0;
fprintf(f, "%s\n", deviceName);
fclose(f);
return 1;
}
int configLoadLastDevice(char *out, int maxLen)
{
FILE *f = fopen(CONFIG_DIR "/last_device.txt", "r");
if (!f) return 0;
int ok = (fgets(out, maxLen, f) != NULL);
fclose(f);
if (!ok) return 0;
int len = (int)strlen(out);
if (len > 0 && out[len - 1] == '\n') out[len - 1] = '\0';
return out[0] != '\0';
}
int configSaveCcMappings(const char *deviceName, ConfigCcEntry *entries, int count)
{
MAKE_DIR(CONFIG_DIR);
char safe[CONFIG_NAME_LEN];
configSanitizeName(deviceName, safe, sizeof(safe));
char path[CONFIG_NAME_LEN + 32];
snprintf(path, sizeof(path), CONFIG_DIR "/cc_%s.cfg", safe);
FILE *f = fopen(path, "w");
if (!f) return 0;
for (int i = 0; i < count; i++)
fprintf(f, "%d %d %f %f\n",
entries[i].cc, entries[i].controlId,
entries[i].min, entries[i].max);
fclose(f);
return 1;
}
int configLoadCcMappings(const char *deviceName, ConfigCcEntry *entries, int maxCount)
{
char safe[CONFIG_NAME_LEN];
configSanitizeName(deviceName, safe, sizeof(safe));
char path[CONFIG_NAME_LEN + 32];
snprintf(path, sizeof(path), CONFIG_DIR "/cc_%s.cfg", safe);
FILE *f = fopen(path, "r");
if (!f) return 0;
int count = 0;
while (count < maxCount) {
int cc, id;
float mn, mx;
if (fscanf(f, "%d %d %f %f\n", &cc, &id, &mn, &mx) != 4) break;
if (cc >= 0 && cc < 128) {
entries[count].cc = cc;
entries[count].controlId = id;
entries[count].min = mn;
entries[count].max = mx;
count++;
}
}
fclose(f);
return count;
}

165
source/font_data.c Normal file
View File

@@ -0,0 +1,165 @@
unsigned char font_png[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x70,
0x08, 0x06, 0x00, 0x00, 0x00, 0xd6, 0xac, 0x78, 0xd2, 0x00, 0x00, 0x00,
0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
0x07, 0x51, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0xed, 0x9c, 0xdd, 0x76,
0xe3, 0x30, 0x08, 0x84, 0x93, 0x9e, 0xbe, 0xff, 0x2b, 0x7b, 0x6f, 0x56,
0x3d, 0x94, 0x0c, 0x30, 0x48, 0x72, 0x12, 0x37, 0xf3, 0x5d, 0x35, 0x96,
0xf8, 0x91, 0x04, 0x08, 0xa7, 0xdd, 0xbd, 0xdd, 0xc4, 0x47, 0x73, 0x67,
0x27, 0x1e, 0xc7, 0x71, 0xdc, 0x6e, 0xb7, 0xdb, 0xfd, 0x7e, 0x87, 0x32,
0x76, 0x1c, 0xcd, 0x3d, 0x8e, 0xe3, 0x18, 0x9f, 0xed, 0xcf, 0x8c, 0x8d,
0x4a, 0x9f, 0x9d, 0xc3, 0xfa, 0x98, 0x8d, 0x7b, 0x3d, 0xd1, 0xf3, 0x2b,
0x33, 0xd6, 0xf4, 0xf5, 0x4c, 0x63, 0xc7, 0x7f, 0xec, 0x33, 0x3f, 0xc7,
0xcf, 0xcb, 0xf4, 0xd9, 0xcf, 0x77, 0x43, 0x26, 0xcb, 0xe8, 0x1c, 0x7a,
0xa2, 0xcf, 0x91, 0xef, 0xd9, 0xfa, 0x98, 0x75, 0x3d, 0x0b, 0xeb, 0xc3,
0xb6, 0x00, 0xf0, 0x1b, 0x86, 0xc6, 0xec, 0x46, 0x66, 0xd9, 0x9c, 0x1d,
0x00, 0x93, 0xc5, 0xd1, 0x18, 0x93, 0xf9, 0x59, 0x76, 0x67, 0x15, 0xc7,
0x07, 0xf0, 0xbb, 0x56, 0x09, 0xef, 0xf7, 0xf7, 0x4e, 0xe5, 0x59, 0x10,
0x54, 0x72, 0xcc, 0x06, 0x46, 0x87, 0x1f, 0x5d, 0x3b, 0x95, 0x8f, 0xbb,
0x88, 0xb2, 0x3d, 0xba, 0x42, 0x58, 0x5f, 0x33, 0xfd, 0x83, 0xce, 0x7a,
0x50, 0xa0, 0xbf, 0xcd, 0x15, 0xe0, 0x83, 0xa7, 0xb3, 0x69, 0xf6, 0x0a,
0xc8, 0x4a, 0xf0, 0x59, 0x20, 0x9f, 0xba, 0x81, 0xb6, 0x7a, 0x3d, 0x74,
0xae, 0xcd, 0x97, 0x54, 0x27, 0x9f, 0xe1, 0x7e, 0xcc, 0x83, 0xc6, 0xb3,
0xcf, 0x91, 0xad, 0x8e, 0x7f, 0xcc, 0x26, 0x46, 0xfe, 0x79, 0xbf, 0xa2,
0xaa, 0x10, 0xe9, 0x3f, 0x33, 0x00, 0xb2, 0xb5, 0x6d, 0x7b, 0x0b, 0xd8,
0x01, 0x6b, 0x03, 0xcd, 0xf3, 0x11, 0x3e, 0xaa, 0xc2, 0x8c, 0x7d, 0xab,
0x27, 0x7b, 0xde, 0xa5, 0x5a, 0xdf, 0x8c, 0xcf, 0x3b, 0xec, 0x8a, 0x0f,
0x26, 0x7c, 0xb5, 0xe9, 0x34, 0x61, 0x8c, 0xfc, 0xca, 0x78, 0x54, 0xda,
0xb2, 0x39, 0x3b, 0xe5, 0x23, 0x1d, 0x67, 0x37, 0x94, 0x33, 0x4d, 0x22,
0xf3, 0xa6, 0x63, 0xe7, 0x7d, 0xd9, 0x41, 0xf4, 0xba, 0x15, 0x29, 0x40,
0x63, 0x91, 0x7c, 0x67, 0x3c, 0x5b, 0xe8, 0xdd, 0xc1, 0xea, 0xf7, 0xf2,
0x3b, 0xfc, 0x47, 0x3a, 0x98, 0xfe, 0x21, 0x5b, 0x5f, 0x76, 0x90, 0x07,
0x00, 0xcd, 0x45, 0x36, 0xb2, 0xf5, 0x7d, 0x7b, 0x25, 0x59, 0xb6, 0x64,
0x0b, 0xc8, 0x0e, 0x6f, 0x57, 0xa6, 0xb0, 0xd9, 0x71, 0xc5, 0xbb, 0x6e,
0x26, 0xf3, 0x2d, 0xf6, 0x7c, 0x7c, 0x2f, 0x91, 0xee, 0x95, 0x15, 0xb0,
0x91, 0x82, 0x1a, 0x12, 0x26, 0x53, 0x99, 0x46, 0xc7, 0x8f, 0x33, 0x25,
0x1a, 0xcd, 0xad, 0xb2, 0x3d, 0xb3, 0x5f, 0xf9, 0xf0, 0xcc, 0x2b, 0x60,
0xf5, 0xf0, 0x59, 0x5d, 0x68, 0xec, 0xcb, 0x0f, 0xae, 0x38, 0xc0, 0xe8,
0xa8, 0x2a, 0x4d, 0xa4, 0x97, 0xd5, 0x9d, 0xe9, 0xaf, 0x0e, 0x3f, 0x93,
0x47, 0x57, 0x4f, 0xe4, 0x63, 0x77, 0x9c, 0x49, 0x86, 0xec, 0x0a, 0xa8,
0xd6, 0x97, 0xd9, 0xfb, 0x42, 0x03, 0x9d, 0xc3, 0xf1, 0xc6, 0xa3, 0xac,
0xad, 0xae, 0x0f, 0xbf, 0xb9, 0x4c, 0x06, 0xcf, 0xf8, 0x97, 0xe9, 0x7e,
0x25, 0x4c, 0x10, 0x64, 0x30, 0xeb, 0x43, 0x01, 0xbc, 0xa5, 0x8b, 0x9e,
0x89, 0xec, 0xce, 0x15, 0xc1, 0xc8, 0xb3, 0xe3, 0x6c, 0x60, 0xad, 0x5c,
0x73, 0x99, 0x5c, 0x25, 0x33, 0x13, 0xa8, 0xac, 0xcc, 0x8e, 0x2a, 0x2f,
0xfe, 0x18, 0x5b, 0x9a, 0x20, 0xa6, 0x41, 0x5a, 0x91, 0x3f, 0xa3, 0x02,
0x21, 0xf9, 0xa8, 0x2f, 0x98, 0xb5, 0xef, 0xe5, 0xd1, 0x7c, 0x34, 0xee,
0x61, 0xfd, 0x8b, 0xc6, 0x91, 0x8f, 0xe3, 0x39, 0xfd, 0x3d, 0x80, 0x17,
0x8c, 0xc6, 0x90, 0xbc, 0x5f, 0x70, 0xe6, 0xd4, 0x4a, 0x1f, 0x12, 0xc1,
0xda, 0x8f, 0x40, 0xfd, 0x09, 0xda, 0x07, 0x66, 0xce, 0x4a, 0x2f, 0x32,
0x64, 0xb3, 0x46, 0x31, 0x7b, 0x8e, 0xd6, 0x4e, 0x7d, 0x0f, 0xe0, 0xa3,
0x69, 0x44, 0x1d, 0x73, 0xa7, 0x59, 0xfc, 0x06, 0x65, 0xf3, 0x90, 0xfe,
0xca, 0x56, 0x67, 0x7c, 0xe7, 0x3d, 0xc8, 0xea, 0xda, 0xd1, 0x88, 0x56,
0x41, 0xdb, 0x3d, 0x93, 0xad, 0x7f, 0x0f, 0x70, 0x76, 0xa7, 0xdd, 0x69,
0xe4, 0x32, 0xfb, 0xd1, 0xbc, 0x9d, 0x55, 0x27, 0xe3, 0x8c, 0xbd, 0x99,
0xe5, 0x21, 0x00, 0xd8, 0x8d, 0x43, 0xd8, 0x2a, 0xd2, 0x8d, 0x44, 0x86,
0x5d, 0xfa, 0xec, 0xbd, 0x19, 0xe9, 0xef, 0x04, 0x43, 0xb7, 0xbb, 0x9e,
0xed, 0xc6, 0x87, 0xdf, 0x99, 0xff, 0x63, 0xef, 0x59, 0x9d, 0x3f, 0x01,
0xc0, 0x1c, 0xd8, 0x6a, 0xb6, 0x58, 0x1b, 0xd9, 0x26, 0x44, 0x63, 0x51,
0x23, 0x13, 0x7d, 0xce, 0xec, 0xbf, 0x82, 0xaa, 0xf7, 0xa8, 0xd6, 0x17,
0x3d, 0xf3, 0xe3, 0xed, 0x00, 0x40, 0x91, 0x33, 0xb3, 0x51, 0x91, 0xbc,
0x75, 0x2a, 0xb3, 0x71, 0xd6, 0x15, 0xc2, 0xda, 0x8f, 0xf0, 0x1b, 0x9a,
0xf5, 0x44, 0x5d, 0xbd, 0xcf, 0x08, 0xc8, 0x5d, 0xe7, 0x2b, 0xfe, 0x20,
0x5b, 0xdf, 0xa3, 0x77, 0xcb, 0x8f, 0x39, 0x28, 0xb3, 0xec, 0xb3, 0xdd,
0xe3, 0xd5, 0xe7, 0x48, 0x3e, 0xdb, 0x03, 0x66, 0xfd, 0x4c, 0x73, 0xeb,
0x65, 0xd1, 0xdc, 0x4e, 0x76, 0x3f, 0xfc, 0x2e, 0x00, 0x2d, 0x92, 0xed,
0x9a, 0x77, 0xc8, 0x47, 0xba, 0xde, 0x01, 0xb4, 0x9e, 0x2e, 0x77, 0x43,
0xa4, 0xbf, 0x23, 0xb3, 0xea, 0x1f, 0xfc, 0xab, 0xe0, 0xc8, 0x90, 0xbd,
0xf3, 0x32, 0xa5, 0xcc, 0xb8, 0x5d, 0xd0, 0xab, 0x0f, 0xdb, 0x66, 0x22,
0xca, 0xc6, 0x28, 0x68, 0xbd, 0xff, 0x7e, 0x0d, 0xec, 0x9a, 0xaa, 0x4a,
0xc9, 0x10, 0xf5, 0x29, 0x15, 0x30, 0x00, 0x56, 0x22, 0xdc, 0xca, 0xa3,
0x80, 0x39, 0x0c, 0x2b, 0x36, 0x32, 0xbb, 0x99, 0x6e, 0xe6, 0xba, 0x42,
0x07, 0xc7, 0x1c, 0xee, 0x4a, 0x93, 0xb5, 0x12, 0x04, 0x6c, 0x85, 0x45,
0xfc, 0x0a, 0x80, 0xaa, 0x53, 0xac, 0x36, 0x38, 0xeb, 0x84, 0x51, 0x96,
0xec, 0x0a, 0x82, 0x2a, 0x43, 0x19, 0xac, 0x1c, 0xf2, 0xab, 0xd2, 0xbb,
0x72, 0xf8, 0x48, 0x6e, 0x66, 0x6f, 0xd8, 0x0a, 0x6d, 0xf9, 0xf5, 0x45,
0x50, 0x56, 0xfa, 0xab, 0xfb, 0x1c, 0xcd, 0xb3, 0xa5, 0xb5, 0xb2, 0xb1,
0x8a, 0xb5, 0x1d, 0xd9, 0x89, 0x02, 0x18, 0x35, 0x7d, 0x1d, 0xff, 0xb3,
0xf2, 0x3b, 0x53, 0x09, 0xd0, 0x1e, 0x56, 0x32, 0xd9, 0xb3, 0x2c, 0x98,
0xe8, 0x7f, 0x19, 0xc4, 0x66, 0x30, 0x1a, 0x5f, 0xcd, 0x50, 0x7f, 0x30,
0x59, 0x95, 0xc9, 0x9e, 0x67, 0x3f, 0x77, 0xfc, 0x58, 0x81, 0xb9, 0x02,
0x51, 0xe5, 0x3d, 0xeb, 0xda, 0x14, 0x1f, 0x4e, 0x9a, 0xad, 0x2c, 0x2b,
0xb2, 0x3b, 0xf5, 0xf9, 0x0c, 0x61, 0xe5, 0x59, 0x7b, 0xb3, 0xf3, 0x18,
0xb9, 0x67, 0x5c, 0x93, 0x88, 0x87, 0x5f, 0x06, 0x45, 0xf7, 0xce, 0xaa,
0x83, 0xb3, 0x87, 0xc3, 0xca, 0x57, 0x9b, 0xbc, 0x6a, 0xbf, 0x23, 0xb3,
0x2b, 0x90, 0xb2, 0xb9, 0xe3, 0x9c, 0xfc, 0xb9, 0x64, 0xf3, 0x91, 0x8f,
0xad, 0x4d, 0x8c, 0xe6, 0x75, 0x7b, 0x81, 0xaa, 0x37, 0xf0, 0x3a, 0x2a,
0x79, 0xf6, 0xf0, 0x2b, 0xf9, 0xc8, 0xff, 0x48, 0x4f, 0x44, 0x66, 0x2f,
0xd3, 0x51, 0x1d, 0x1e, 0xe3, 0x3f, 0xb2, 0x91, 0x7d, 0xa6, 0xde, 0x02,
0xbc, 0x91, 0x08, 0xdf, 0xac, 0x45, 0xfa, 0xd8, 0x05, 0x66, 0x3e, 0x9c,
0x51, 0xa5, 0x58, 0xff, 0x67, 0x59, 0xbd, 0x2a, 0xab, 0x33, 0x98, 0xd1,
0x4b, 0xfd, 0x41, 0x08, 0x7b, 0x40, 0x67, 0x53, 0x2d, 0x30, 0x0b, 0xb8,
0x33, 0xbb, 0xe8, 0xee, 0xc1, 0x9e, 0x11, 0x5c, 0xb3, 0x3c, 0xe5, 0x3f,
0x88, 0xf0, 0xec, 0x38, 0x8c, 0xae, 0xfc, 0xdd, 0xb0, 0x62, 0x77, 0x85,
0x55, 0xfb, 0x67, 0xf8, 0x4f, 0x55, 0x00, 0x9f, 0x41, 0xf6, 0x73, 0x74,
0x8f, 0xdb, 0xe7, 0x28, 0x03, 0xab, 0xf1, 0xcc, 0xbe, 0x97, 0xaf, 0x60,
0xef, 0xf8, 0x59, 0x66, 0xfc, 0x7b, 0x66, 0x15, 0x58, 0xdd, 0x3f, 0xf1,
0x87, 0x79, 0xab, 0x28, 0x58, 0x6d, 0x92, 0xae, 0x40, 0x96, 0x85, 0xd5,
0x5b, 0x00, 0x1a, 0x63, 0x6d, 0x46, 0x72, 0xdf, 0xde, 0x40, 0xe5, 0x1c,
0x1a, 0x67, 0x0e, 0x2e, 0x93, 0xaf, 0x60, 0x5e, 0x6b, 0x90, 0xbf, 0x9d,
0xf1, 0x4a, 0x3f, 0xf3, 0xd9, 0xc3, 0x5c, 0x8f, 0x0c, 0xfe, 0xed, 0x84,
0xd1, 0xc1, 0xec, 0xd1, 0xed, 0x96, 0xfc, 0x41, 0x88, 0x75, 0xd8, 0x2a,
0xcb, 0x16, 0x7d, 0x18, 0x22, 0x67, 0x22, 0xfd, 0xd9, 0x67, 0x06, 0xe4,
0x6f, 0x67, 0x3c, 0x7b, 0xce, 0xc2, 0x34, 0x69, 0xab, 0x4d, 0x60, 0x34,
0x16, 0xed, 0xbd, 0x9f, 0xe3, 0x9f, 0x51, 0x6f, 0x01, 0x6c, 0x04, 0x46,
0x0e, 0xa2, 0xcc, 0xf1, 0x73, 0x7d, 0x53, 0xf8, 0xcc, 0x6b, 0xc0, 0x67,
0x6a, 0x64, 0x3f, 0xcb, 0xf4, 0xa8, 0x3a, 0x78, 0x1b, 0x2b, 0x41, 0x56,
0x5d, 0x11, 0x95, 0x0f, 0xc8, 0x7e, 0xf8, 0xf7, 0x00, 0x68, 0x01, 0x6c,
0x84, 0x47, 0xc1, 0x71, 0xbf, 0xe3, 0x6e, 0x7f, 0xd7, 0x61, 0x57, 0x9b,
0x9b, 0x65, 0x48, 0x54, 0xae, 0xab, 0x31, 0x64, 0x3f, 0xdb, 0xf8, 0x1d,
0x15, 0x20, 0xf3, 0x81, 0x39, 0x27, 0x2b, 0x0f, 0xaf, 0x80, 0x48, 0x78,
0x6c, 0x60, 0x37, 0x9a, 0x51, 0x60, 0xcd, 0x64, 0x42, 0x74, 0x80, 0xe8,
0x80, 0x3a, 0x1b, 0xed, 0xd7, 0x85, 0xfc, 0x63, 0xf6, 0xc6, 0xda, 0x3d,
0xe3, 0x9a, 0x61, 0x2a, 0x50, 0x35, 0xee, 0xe7, 0xd2, 0xff, 0x34, 0x0c,
0xdd, 0xa1, 0xec, 0x26, 0xa3, 0xcc, 0xef, 0x1c, 0xd0, 0x90, 0x47, 0xcf,
0xec, 0xc2, 0xb2, 0xea, 0xe5, 0x03, 0x37, 0xb2, 0x8f, 0x6c, 0xb1, 0x41,
0x17, 0xcd, 0xb5, 0xf3, 0xab, 0x6b, 0x14, 0xb1, 0xab, 0x62, 0xc2, 0xca,
0x3c, 0xa3, 0xe8, 0x2c, 0xd0, 0xc6, 0x54, 0x19, 0xc3, 0x66, 0xe4, 0xea,
0x9c, 0xb3, 0xe8, 0x54, 0xaf, 0x33, 0xc6, 0x5f, 0xf2, 0x55, 0xf0, 0x3b,
0xc0, 0x64, 0xed, 0x27, 0xb0, 0xf5, 0x5f, 0x07, 0xaf, 0xc0, 0x94, 0x4e,
0xf4, 0xbc, 0xa3, 0xcb, 0x8f, 0xfb, 0xb2, 0x5c, 0xdd, 0xa3, 0x99, 0xce,
0xca, 0x46, 0x57, 0xd6, 0xcb, 0xed, 0x0c, 0x52, 0xab, 0x1f, 0xfe, 0x41,
0x48, 0x26, 0x7c, 0xa6, 0x63, 0x95, 0xdd, 0x99, 0x2b, 0xc2, 0x83, 0x74,
0xd8, 0x1e, 0x65, 0xf6, 0x7e, 0xdd, 0xcd, 0xd9, 0xfb, 0x3a, 0xf4, 0x3f,
0xbc, 0x06, 0xb2, 0x82, 0x57, 0x24, 0xcb, 0xca, 0x95, 0xb7, 0x93, 0x5d,
0x30, 0xc1, 0xb7, 0x3b, 0x40, 0xc3, 0x2b, 0x80, 0xcd, 0xf4, 0x5d, 0x9d,
0xec, 0xec, 0xc6, 0x23, 0xfb, 0x48, 0x17, 0x53, 0x92, 0xdf, 0xa1, 0x12,
0x3c, 0xcb, 0xee, 0xb0, 0x43, 0xff, 0xd3, 0xb0, 0x55, 0xc7, 0x7c, 0xc9,
0xdd, 0xad, 0x3f, 0xd3, 0xd5, 0xb9, 0x8f, 0xdf, 0xa1, 0x12, 0x9c, 0xcd,
0x47, 0xbd, 0x05, 0xcc, 0x34, 0x63, 0x9f, 0x10, 0x04, 0x83, 0x32, 0x53,
0x3a, 0x1b, 0xb8, 0x2a, 0x8f, 0x74, 0xfd, 0x38, 0xfa, 0x22, 0xf9, 0x77,
0x69, 0x0a, 0xcf, 0xe2, 0xf4, 0x0a, 0x30, 0x93, 0x4d, 0x57, 0xdc, 0x7c,
0xfb, 0x0d, 0x9f, 0xfd, 0x19, 0xcd, 0xab, 0xf4, 0x54, 0x36, 0x32, 0xdd,
0xd5, 0x98, 0x9f, 0xf3, 0xf0, 0x9f, 0x45, 0x47, 0x4e, 0xac, 0x94, 0xc3,
0x4e, 0x10, 0x5c, 0xf5, 0xf0, 0xab, 0x9f, 0xed, 0xe7, 0x2a, 0x38, 0x66,
0xf6, 0x7a, 0x34, 0xae, 0xf6, 0x75, 0xd9, 0xeb, 0x1d, 0xe3, 0xf6, 0xd9,
0x77, 0x27, 0x52, 0xd1, 0x33, 0xf6, 0xa0, 0x98, 0x0e, 0xfb, 0x8a, 0x87,
0xcf, 0x82, 0x0e, 0x25, 0xfb, 0x3e, 0xc3, 0x8e, 0x67, 0x67, 0xd1, 0xd9,
0x7f, 0xfb, 0xf3, 0x90, 0x7f, 0x6a, 0x13, 0xb8, 0x73, 0x41, 0x57, 0x62,
0x36, 0xe3, 0xd9, 0x4a, 0xe0, 0x83, 0x09, 0x55, 0x81, 0x88, 0xef, 0x6a,
0xc3, 0xad, 0xa2, 0x6c, 0xae, 0x2f, 0x7d, 0xd1, 0x5c, 0x54, 0x09, 0xae,
0x7e, 0xf8, 0xd1, 0x86, 0x8f, 0xf5, 0xac, 0x8e, 0xef, 0x20, 0xaa, 0x28,
0x53, 0x9d, 0x3d, 0x1a, 0x8f, 0x60, 0xe5, 0x66, 0xf5, 0xb3, 0xac, 0xbe,
0x45, 0x30, 0x7a, 0x99, 0x64, 0x61, 0xae, 0xc0, 0xae, 0x7c, 0xe4, 0x6b,
0x76, 0xc5, 0x8c, 0xb1, 0x97, 0xfd, 0x32, 0xc8, 0x46, 0xfd, 0x55, 0x33,
0xdf, 0x33, 0xd6, 0x84, 0xd6, 0x83, 0x9a, 0x43, 0x3f, 0x6f, 0x56, 0x9e,
0xd9, 0xbf, 0xb7, 0xd9, 0xe3, 0x2c, 0xb3, 0xc6, 0xd5, 0x30, 0x23, 0xcb,
0xc8, 0x8b, 0x47, 0x7e, 0xfe, 0xbb, 0x78, 0xc4, 0x98, 0x94, 0x35, 0x6d,
0x11, 0x2b, 0x07, 0x39, 0x23, 0x53, 0xf9, 0x2b, 0x30, 0x3f, 0x6f, 0x01,
0x77, 0x87, 0x9d, 0xe4, 0x9b, 0x94, 0xec, 0x1e, 0x63, 0xc6, 0xc7, 0x1c,
0xd6, 0xc9, 0x99, 0x60, 0x53, 0x10, 0x70, 0xd0, 0x3d, 0x40, 0x76, 0x67,
0xb3, 0x8d, 0x0d, 0x1a, 0xeb, 0x06, 0x4a, 0x37, 0x70, 0xde, 0xe6, 0xee,
0x7b, 0x53, 0xb6, 0x34, 0x81, 0xb6, 0x79, 0xe9, 0x6e, 0xfa, 0x6a, 0xf6,
0xa2, 0x4e, 0x57, 0x87, 0xce, 0xd3, 0xfa, 0xab, 0x60, 0xfb, 0x1e, 0x39,
0xf3, 0x0d, 0xd4, 0x90, 0xf5, 0x63, 0xd5, 0x6b, 0x0c, 0xf3, 0x7a, 0x24,
0xe6, 0xf8, 0xf5, 0xbb, 0x00, 0xd4, 0x00, 0x8e, 0xb1, 0xea, 0x9b, 0xa6,
0x6e, 0xf6, 0x7b, 0x7d, 0xdd, 0x71, 0x71, 0x51, 0xf4, 0x1a, 0x28, 0x84,
0x10, 0x42, 0x08, 0x21, 0x84, 0x10, 0x42, 0x08, 0x21, 0x84, 0x10, 0x42,
0x08, 0x21, 0x84, 0x10, 0x42, 0x08, 0x21, 0x84, 0x10, 0x42, 0x08, 0x21,
0x84, 0x10, 0x42, 0x08, 0x21, 0x84, 0x10, 0x42, 0x08, 0x21, 0x84, 0x10,
0x42, 0x08, 0x21, 0x84, 0x10, 0x42, 0x08, 0x21, 0x84, 0x10, 0x42, 0x08,
0x81, 0xf9, 0x07, 0x4e, 0x1f, 0xd4, 0x6d, 0xe4, 0x18, 0xb7, 0xb8, 0x00,
0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
unsigned int font_png_len = 1943;

312
source/icon_data.c Normal file
View File

@@ -0,0 +1,312 @@
unsigned char icon_png[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
0x08, 0x06, 0x00, 0x00, 0x00, 0xc3, 0x3e, 0x61, 0xcb, 0x00, 0x00, 0x00,
0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
0x0e, 0x2d, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0xed, 0x5d, 0x3b, 0x6f,
0x1b, 0xc7, 0x16, 0xfe, 0x78, 0x13, 0x58, 0x8a, 0x40, 0x3d, 0x6c, 0xfd,
0x98, 0x54, 0x41, 0x0a, 0x23, 0x4d, 0xe2, 0x2e, 0x69, 0x0c, 0xd8, 0x95,
0x00, 0x03, 0x6e, 0x2c, 0x20, 0x80, 0x85, 0x14, 0x4a, 0x65, 0x15, 0x81,
0x02, 0x04, 0xb0, 0x1a, 0x03, 0x04, 0x5c, 0x39, 0x40, 0x1a, 0xdd, 0x2e,
0x57, 0xcd, 0x45, 0x8a, 0x8b, 0x5b, 0xdd, 0x1f, 0x43, 0x89, 0x22, 0x09,
0xd9, 0x32, 0x10, 0xf0, 0x16, 0xf4, 0x8c, 0x67, 0x67, 0xce, 0xcc, 0x9c,
0x79, 0xec, 0xce, 0x2c, 0xcd, 0x0f, 0xa0, 0x4d, 0x2d, 0x97, 0xcb, 0xd9,
0x39, 0xdf, 0x9e, 0xe7, 0x3c, 0x06, 0x58, 0x03, 0x00, 0x16, 0xca, 0xfb,
0x41, 0xb1, 0x56, 0x14, 0xc0, 0x2a, 0xde, 0xec, 0x42, 0xfb, 0xdb, 0x77,
0x8f, 0xfa, 0xf9, 0x9c, 0xef, 0xac, 0x0c, 0xfe, 0x51, 0xba, 0x01, 0x99,
0x41, 0x09, 0x93, 0x3a, 0xd6, 0xf8, 0xec, 0x74, 0xf4, 0x06, 0xa3, 0xf3,
0x8b, 0x90, 0xdf, 0x50, 0x5f, 0xbd, 0xc6, 0xe7, 0xa5, 0x1b, 0xd0, 0x06,
0x4e, 0x47, 0x6f, 0xe4, 0xfb, 0xa3, 0x27, 0x8f, 0x81, 0xa5, 0xa0, 0x9c,
0x4f, 0xf5, 0xe4, 0x72, 0x8c, 0xd1, 0xf9, 0x05, 0x9e, 0xfc, 0xf0, 0xad,
0xeb, 0x7c, 0x1b, 0xc1, 0x7a, 0xab, 0x31, 0xfa, 0x42, 0x80, 0x50, 0xb5,
0xce, 0xbe, 0xa6, 0x4a, 0x96, 0xc9, 0xe5, 0x98, 0xd5, 0x86, 0xdf, 0xff,
0xfc, 0x0b, 0x00, 0xf0, 0xe8, 0xc1, 0xfd, 0x0c, 0xcd, 0x28, 0x8b, 0x3e,
0x98, 0x80, 0x50, 0xb5, 0xde, 0x80, 0x22, 0xe0, 0x2c, 0xea, 0x5a, 0x08,
0x7f, 0x55, 0xd0, 0x07, 0x02, 0x00, 0x00, 0x8e, 0x4f, 0xcf, 0x1a, 0x4f,
0x2b, 0xba, 0xb5, 0xbf, 0x0b, 0x00, 0x18, 0x9d, 0x5f, 0xe0, 0xe6, 0xdd,
0x6d, 0xe8, 0xf7, 0xaa, 0xf6, 0x17, 0x7a, 0x43, 0x00, 0x00, 0x98, 0x4e,
0xae, 0x74, 0x12, 0x84, 0x62, 0xa1, 0xfd, 0xcf, 0x39, 0xd7, 0xc0, 0xcd,
0xbb, 0x5b, 0x0e, 0x11, 0x92, 0x34, 0x57, 0x57, 0xe8, 0x15, 0x01, 0x00,
0x83, 0x04, 0xac, 0x0e, 0x4d, 0xd0, 0x1c, 0xb1, 0x4f, 0xee, 0x02, 0x00,
0xfe, 0xf9, 0xef, 0xff, 0xca, 0x57, 0xad, 0x28, 0x4d, 0x80, 0x28, 0xf5,
0x38, 0x9d, 0x5c, 0xd9, 0xae, 0x65, 0x05, 0xa5, 0x39, 0xb8, 0xc7, 0x5c,
0xc7, 0x43, 0xdb, 0x51, 0x1b, 0x4a, 0x46, 0x01, 0x7a, 0x47, 0x05, 0x85,
0x53, 0xc7, 0xa7, 0x67, 0x38, 0x39, 0x7a, 0x46, 0x5d, 0xc7, 0x0a, 0xae,
0x10, 0x6d, 0xe7, 0x4d, 0x2e, 0xc7, 0xd8, 0xbb, 0xb7, 0x4f, 0x7d, 0x24,
0xda, 0xde, 0x88, 0x14, 0xde, 0xde, 0xbe, 0x07, 0x00, 0x7c, 0xb1, 0x71,
0x87, 0xdb, 0xc4, 0xce, 0x51, 0x4a, 0x03, 0xc8, 0x8e, 0x3a, 0x3e, 0x3d,
0x23, 0x8f, 0xeb, 0xf8, 0x20, 0x6c, 0x2f, 0x12, 0x7d, 0x04, 0x2f, 0x26,
0x97, 0x63, 0x5b, 0xb8, 0x28, 0xdb, 0xae, 0x27, 0x95, 0x04, 0x11, 0x6a,
0x44, 0xa9, 0x04, 0xc6, 0x02, 0x68, 0x0a, 0x5f, 0x13, 0xf0, 0x40, 0x3f,
0x57, 0x3f, 0x5f, 0xc5, 0xce, 0xde, 0xdd, 0xac, 0x8d, 0xe3, 0x62, 0xef,
0xde, 0xbe, 0x48, 0x1c, 0x49, 0xa8, 0xc2, 0xdf, 0xda, 0xdc, 0x90, 0xef,
0x95, 0x9c, 0x81, 0xde, 0xe7, 0x45, 0x53, 0xd1, 0xd5, 0x24, 0x82, 0x14,
0x95, 0x0e, 0x04, 0x9a, 0x83, 0xe9, 0xe4, 0xaa, 0x18, 0x09, 0x5c, 0x29,
0x64, 0x11, 0x29, 0x28, 0x24, 0xe1, 0x08, 0x5f, 0x1c, 0xef, 0x84, 0x04,
0xa5, 0x4c, 0xc0, 0x00, 0x30, 0xd5, 0x3a, 0x61, 0x0e, 0xd8, 0xf6, 0xdd,
0xe2, 0x18, 0xb6, 0x0a, 0x87, 0x39, 0xe0, 0x40, 0xde, 0x5b, 0xc9, 0x68,
0xa1, 0x74, 0x14, 0x60, 0xc0, 0xa6, 0xe6, 0x6d, 0xc7, 0x55, 0x94, 0x20,
0x01, 0xe0, 0x4d, 0x21, 0xab, 0x30, 0xf2, 0x10, 0xa5, 0x43, 0xc4, 0xd2,
0x45, 0x0c, 0xc3, 0x17, 0x48, 0x85, 0xcd, 0x59, 0x0c, 0x71, 0x0e, 0x3f,
0x14, 0x90, 0xa2, 0xae, 0x41, 0x45, 0x09, 0xba, 0x9f, 0xa0, 0x42, 0x27,
0xc0, 0xf7, 0xdf, 0x7c, 0x25, 0xde, 0x76, 0x22, 0x9b, 0xb6, 0x7e, 0x84,
0xeb, 0xd8, 0x24, 0x13, 0x80, 0x1b, 0x1d, 0x00, 0x3c, 0x01, 0xda, 0x84,
0x1f, 0x72, 0x3d, 0x2e, 0x09, 0x7e, 0xff, 0xf3, 0x2f, 0x23, 0x44, 0xec,
0x9a, 0x00, 0x6d, 0x38, 0x81, 0x9d, 0x38, 0x36, 0x0e, 0xc1, 0x73, 0xcb,
0xb8, 0x3e, 0x78, 0xaf, 0x23, 0xc8, 0xa2, 0x13, 0x81, 0xca, 0x17, 0x50,
0xce, 0xa2, 0x48, 0x29, 0xef, 0xef, 0x6e, 0x03, 0xe8, 0x5e, 0xf8, 0x40,
0x8b, 0x51, 0xc0, 0xc1, 0xe1, 0x73, 0x00, 0xc0, 0x70, 0x7b, 0x07, 0x2f,
0x4f, 0x7e, 0x16, 0x87, 0x45, 0x07, 0x36, 0x6e, 0x70, 0x36, 0xbd, 0xc6,
0xf6, 0xce, 0xae, 0xf7, 0x9a, 0x81, 0x42, 0x6f, 0x03, 0xa4, 0x17, 0xaf,
0x6a, 0x0d, 0x41, 0x06, 0x47, 0xd2, 0xc8, 0xc0, 0xf8, 0x7a, 0xd6, 0x08,
0x19, 0xbb, 0x44, 0xeb, 0x61, 0xe0, 0x7c, 0x36, 0xc5, 0xe1, 0xf1, 0x0b,
0x00, 0xa0, 0x88, 0x20, 0x31, 0x9b, 0x5e, 0x03, 0x00, 0x49, 0x04, 0x8b,
0xe0, 0x4b, 0xfb, 0x2f, 0xc0, 0xc7, 0x36, 0xc8, 0xfb, 0x51, 0xb5, 0x82,
0x70, 0x0e, 0x39, 0x44, 0xf0, 0x8c, 0x2d, 0x68, 0x2d, 0x57, 0xd0, 0x49,
0x14, 0x30, 0x9f, 0x4d, 0x1b, 0x44, 0x50, 0xa1, 0x1f, 0x13, 0x44, 0x00,
0x96, 0x82, 0x27, 0x84, 0x3f, 0x40, 0x1d, 0xc2, 0x57, 0x31, 0x80, 0xd6,
0xae, 0xa3, 0x27, 0x8f, 0x25, 0x19, 0x02, 0xc3, 0xc5, 0x90, 0x5c, 0x41,
0x32, 0xda, 0xe8, 0x48, 0xd9, 0x30, 0x61, 0x06, 0x54, 0x0c, 0xb7, 0x77,
0x58, 0x17, 0x51, 0xb4, 0x85, 0x40, 0x4a, 0x5b, 0xa5, 0xb3, 0xe9, 0x4b,
0x18, 0x29, 0xea, 0x3c, 0xb5, 0x6f, 0x64, 0x3f, 0xa8, 0x3e, 0x82, 0x4d,
0x1b, 0x10, 0xc9, 0xa2, 0x86, 0x80, 0xd5, 0x68, 0x21, 0xa7, 0xaf, 0xd0,
0x86, 0x09, 0x68, 0x14, 0x45, 0x74, 0xcc, 0x67, 0x53, 0x27, 0x09, 0x32,
0x0b, 0xde, 0x40, 0x87, 0x59, 0x43, 0xd9, 0x0f, 0x81, 0x66, 0xc1, 0x2a,
0xf8, 0x36, 0xd0, 0xaa, 0x09, 0x78, 0xfd, 0xf2, 0x57, 0xf2, 0xb8, 0x30,
0x09, 0x3a, 0x34, 0xe1, 0xb7, 0xa6, 0xea, 0x3b, 0x4c, 0x18, 0x19, 0x66,
0x41, 0x40, 0x37, 0x0b, 0xb6, 0x94, 0xf2, 0xf8, 0x7a, 0xd6, 0x5e, 0xeb,
0xd0, 0xae, 0x2d, 0x75, 0x9a, 0x02, 0x01, 0xa1, 0x0d, 0x08, 0xe1, 0x67,
0x6f, 0x8b, 0x9e, 0x6f, 0xa0, 0x34, 0x41, 0x46, 0x13, 0x40, 0xb6, 0x01,
0x30, 0xc3, 0x46, 0x8e, 0x93, 0x48, 0x84, 0x8a, 0x40, 0x41, 0x13, 0xc0,
0xf1, 0x4a, 0xa5, 0x0a, 0xbc, 0x99, 0xcf, 0xb1, 0x35, 0x1c, 0x92, 0x17,
0x6a, 0x5b, 0xe5, 0xbb, 0x20, 0x34, 0x41, 0x87, 0x26, 0x01, 0x00, 0x16,
0x7a, 0xfe, 0x80, 0x13, 0x32, 0x8e, 0xaf, 0x67, 0x7a, 0x32, 0xa9, 0x58,
0x14, 0xe0, 0xf2, 0x4a, 0x17, 0xb6, 0xf3, 0x6e, 0xe6, 0x73, 0xdc, 0xcc,
0xe7, 0x8d, 0x2f, 0x68, 0x26, 0xa2, 0x98, 0x77, 0x3f, 0x9d, 0x5c, 0x75,
0x6d, 0x16, 0x00, 0x98, 0x26, 0xc1, 0x85, 0x36, 0x84, 0x0f, 0x24, 0x38,
0x81, 0x0f, 0x0f, 0x9e, 0xca, 0xf7, 0x7f, 0xbc, 0x7e, 0xa5, 0x7e, 0xe4,
0x0c, 0x4f, 0x04, 0x09, 0xb4, 0xef, 0x54, 0x11, 0xd6, 0x75, 0x4c, 0x02,
0xe9, 0x20, 0x06, 0x0e, 0x62, 0xc9, 0xda, 0x57, 0x59, 0x9c, 0xc0, 0x87,
0x07, 0x4f, 0xe5, 0x8b, 0xfa, 0x4c, 0x47, 0x8d, 0xc2, 0x2f, 0x00, 0x43,
0x13, 0x24, 0x94, 0x97, 0xa3, 0x87, 0x9f, 0x47, 0x6b, 0x80, 0x3f, 0x5e,
0xbf, 0x62, 0x0b, 0x5c, 0xff, 0x9e, 0x82, 0x4f, 0x55, 0xf8, 0x02, 0xa4,
0x26, 0x60, 0xa6, 0x91, 0x7d, 0x73, 0x1e, 0x59, 0x7d, 0x1b, 0xa3, 0x01,
0xa2, 0x85, 0xb6, 0x16, 0x3e, 0x89, 0x50, 0x9f, 0xc0, 0x78, 0xca, 0x47,
0xe7, 0x17, 0x18, 0x9d, 0x5f, 0xe8, 0x39, 0x03, 0x96, 0x26, 0x88, 0x35,
0x01, 0x03, 0xc0, 0x10, 0xa8, 0x13, 0x6b, 0xe1, 0x3b, 0xe1, 0x25, 0x01,
0x95, 0x27, 0x10, 0x82, 0x17, 0x18, 0x5f, 0xcf, 0x82, 0xa7, 0xae, 0x25,
0x67, 0x02, 0x6d, 0xa6, 0xc0, 0x81, 0xb5, 0xf0, 0x69, 0x90, 0x19, 0x54,
0xd5, 0x1c, 0x70, 0xa6, 0xb0, 0x87, 0x4e, 0x58, 0x4d, 0x71, 0x02, 0x65,
0xd8, 0xe6, 0xd3, 0x04, 0xca, 0xe7, 0x6b, 0xe1, 0xbb, 0x31, 0x00, 0xcc,
0x41, 0x29, 0x5c, 0xc7, 0x30, 0x26, 0x54, 0x6c, 0xbd, 0x1a, 0x18, 0x62,
0x26, 0xd6, 0x00, 0xe0, 0x20, 0x41, 0x42, 0x45, 0xd1, 0x0a, 0x1f, 0x01,
0xf4, 0xf0, 0xc2, 0x99, 0xec, 0xd1, 0xb1, 0xb6, 0xfb, 0x69, 0xa0, 0x86,
0xa7, 0x51, 0x24, 0x78, 0xf2, 0xc3, 0xb7, 0xae, 0xa1, 0xe7, 0x4e, 0xb8,
0x08, 0xe0, 0x0b, 0x33, 0x42, 0xe2, 0xcd, 0x2a, 0x84, 0x3f, 0x9b, 0x5e,
0x37, 0xc6, 0x1b, 0x54, 0x0c, 0xef, 0x6a, 0x26, 0x82, 0x08, 0xa9, 0x19,
0x42, 0xaf, 0x13, 0xa8, 0x17, 0x72, 0x6e, 0xe6, 0x73, 0x43, 0xad, 0x7b,
0x92, 0x3d, 0x55, 0x08, 0x5f, 0x85, 0x6b, 0xf4, 0x51, 0x45, 0x18, 0xe0,
0x43, 0xdd, 0xc0, 0x35, 0x57, 0x51, 0x3b, 0x3f, 0x18, 0xc1, 0x51, 0xc0,
0xd6, 0x70, 0x88, 0x83, 0xc3, 0xe7, 0x46, 0x5e, 0x5f, 0x45, 0x5f, 0xec,
0x3e, 0x77, 0x2c, 0x62, 0x69, 0x44, 0xa4, 0x8b, 0xd9, 0x88, 0x0e, 0x03,
0x45, 0x75, 0xcf, 0x45, 0x04, 0x54, 0xf8, 0xf4, 0xeb, 0xa8, 0x5c, 0x1b,
0x38, 0x07, 0xd7, 0x58, 0xe0, 0x3a, 0xdf, 0x90, 0x87, 0xcb, 0x07, 0x18,
0x00, 0xcb, 0x8a, 0x9d, 0x6d, 0x60, 0x07, 0xb0, 0x24, 0x82, 0x5a, 0xea,
0xed, 0xcb, 0xd3, 0xaf, 0xa3, 0x62, 0xff, 0x80, 0x8c, 0x0a, 0x08, 0x70,
0xfc, 0x32, 0xe3, 0x73, 0x9f, 0x06, 0x90, 0x0c, 0x14, 0x24, 0xb0, 0x0d,
0xee, 0xd8, 0x1a, 0x0e, 0x75, 0x6d, 0x50, 0xfd, 0xd3, 0xbf, 0x0a, 0x38,
0x1d, 0xbd, 0x31, 0xc8, 0x41, 0x25, 0x8c, 0x6c, 0xb3, 0x93, 0x38, 0x26,
0xa0, 0xa1, 0x86, 0x5e, 0xbf, 0xfc, 0xd5, 0x4a, 0x82, 0xbe, 0x3e, 0xfd,
0x3d, 0x80, 0xd3, 0x21, 0xe4, 0x4c, 0x4c, 0xb1, 0x81, 0x9b, 0x08, 0x0a,
0x1d, 0xac, 0xb1, 0x7e, 0xfa, 0x0b, 0x43, 0x8d, 0x10, 0x5c, 0x73, 0x13,
0xab, 0x9b, 0x1d, 0xbc, 0x46, 0x3e, 0x4c, 0x2e, 0xc7, 0xde, 0x3c, 0x41,
0x48, 0x14, 0xe0, 0x74, 0x30, 0x14, 0x47, 0xb1, 0xda, 0xa7, 0x5f, 0x1d,
0x89, 0xcc, 0x9d, 0x9f, 0x50, 0x11, 0xbc, 0x79, 0x01, 0x15, 0x84, 0xd3,
0x48, 0xca, 0x45, 0x27, 0x40, 0xaf, 0x56, 0xb8, 0x4a, 0x81, 0x20, 0x43,
0x0f, 0x89, 0xe0, 0x04, 0x57, 0xf0, 0x02, 0xaa, 0x09, 0x60, 0x09, 0xff,
0xf0, 0xf8, 0x85, 0xd1, 0x69, 0xae, 0x30, 0xb1, 0x26, 0x50, 0x39, 0x0b,
0x6a, 0x7e, 0x42, 0xc5, 0xe0, 0x86, 0x84, 0xe2, 0x5c, 0xaf, 0x36, 0x36,
0x4c, 0x00, 0x35, 0x7f, 0x8f, 0x82, 0x20, 0x81, 0xd6, 0x81, 0xd5, 0xaa,
0x7f, 0x01, 0x41, 0x02, 0x35, 0x77, 0xe1, 0x9b, 0xad, 0xb4, 0xca, 0x48,
0x76, 0x02, 0xfb, 0xda, 0x71, 0xba, 0x36, 0xe8, 0x99, 0x26, 0xe0, 0x20,
0x6e, 0x48, 0xd8, 0xcb, 0x93, 0x9f, 0x6b, 0x4d, 0x8b, 0x66, 0xc7, 0x27,
0x42, 0x02, 0x67, 0x49, 0x5f, 0x25, 0x80, 0x54, 0xdf, 0x27, 0x47, 0xcf,
0xd8, 0x24, 0x50, 0x66, 0xf6, 0x54, 0xaf, 0xfe, 0x29, 0x50, 0x13, 0x56,
0x2a, 0x87, 0xd3, 0x0f, 0x08, 0x5d, 0xd2, 0x56, 0xf7, 0x01, 0x64, 0xd6,
0x4f, 0x9d, 0x97, 0xcf, 0xf5, 0x0b, 0xfa, 0x0c, 0xd7, 0xf4, 0xb5, 0xbe,
0xc1, 0x47, 0x02, 0x95, 0x3c, 0x94, 0x0f, 0x60, 0x78, 0x8f, 0xc4, 0xfc,
0xbd, 0x95, 0x44, 0xcf, 0x34, 0x41, 0x16, 0xf8, 0xaa, 0x81, 0xbd, 0x54,
0xeb, 0x6b, 0xd8, 0xa1, 0x9b, 0x0e, 0x76, 0x14, 0xf0, 0xa9, 0x38, 0x86,
0xab, 0x0c, 0x4d, 0xf8, 0x03, 0xc0, 0x9f, 0x0a, 0x6e, 0x84, 0x12, 0xdb,
0x3b, 0xbb, 0xae, 0x9a, 0xf9, 0xaa, 0x65, 0x11, 0xab, 0xbf, 0x1f, 0xce,
0x6a, 0x27, 0x16, 0x67, 0x51, 0x6a, 0x76, 0x5d, 0x03, 0x78, 0x27, 0x19,
0xea, 0x9a, 0xa0, 0x2f, 0x0e, 0x22, 0x77, 0xf2, 0x4a, 0xe0, 0x24, 0x97,
0x62, 0x10, 0x8b, 0x5d, 0xb8, 0x66, 0x34, 0x5b, 0xd2, 0xc2, 0x03, 0xfd,
0x00, 0xe0, 0x61, 0xbb, 0x6d, 0x25, 0xcf, 0xd8, 0x11, 0x34, 0xa9, 0xf1,
0x76, 0xd7, 0xce, 0x9a, 0x2b, 0x3a, 0xc8, 0x99, 0x08, 0x4b, 0x31, 0xb3,
0xba, 0x26, 0xa0, 0xd4, 0x3d, 0x85, 0xcf, 0xc1, 0x58, 0x8f, 0xdf, 0x06,
0xd1, 0xe0, 0x50, 0x22, 0x0c, 0xb7, 0x77, 0x92, 0x48, 0xc0, 0x1c, 0x8f,
0x98, 0x0d, 0xae, 0x10, 0x31, 0x67, 0x1a, 0x39, 0x65, 0x7c, 0xa2, 0x63,
0xb5, 0x13, 0x5e, 0x31, 0x28, 0x65, 0xbd, 0xde, 0x98, 0x06, 0xe7, 0xe8,
0xb4, 0x2e, 0xe3, 0x76, 0x17, 0xd9, 0x72, 0x67, 0x10, 0x53, 0xc6, 0x26,
0x12, 0x26, 0xc1, 0xa9, 0xdd, 0xb3, 0x0d, 0x08, 0xd9, 0xde, 0xd9, 0x0d,
0x26, 0x42, 0x2e, 0x12, 0x74, 0x45, 0x84, 0x9e, 0x93, 0x80, 0xf4, 0xeb,
0x0c, 0x1f, 0x20, 0xd7, 0xd2, 0xed, 0xa1, 0x37, 0x90, 0xa3, 0x03, 0xbb,
0xf4, 0x0d, 0x7c, 0xa4, 0xcb, 0x65, 0x16, 0x52, 0xfc, 0x02, 0xcf, 0x4a,
0xea, 0x03, 0xf9, 0xcf, 0x07, 0x90, 0xaa, 0x22, 0x85, 0x10, 0x31, 0x2c,
0xee, 0x93, 0x83, 0xd8, 0x07, 0x12, 0x00, 0x4d, 0xbf, 0x40, 0x77, 0x0e,
0xd9, 0x4b, 0xab, 0xaf, 0x89, 0x40, 0xa3, 0x0f, 0x11, 0x02, 0x60, 0x27,
0x81, 0xab, 0x16, 0x90, 0x2d, 0x0d, 0x5c, 0xca, 0x3f, 0xe8, 0x02, 0xae,
0x6a, 0xa2, 0x58, 0x11, 0x35, 0x87, 0x79, 0x4b, 0x9d, 0xb4, 0xa2, 0xfa,
0x05, 0x6a, 0xb1, 0xc8, 0xe7, 0x04, 0x66, 0xad, 0x05, 0xc4, 0x90, 0x40,
0xbc, 0x42, 0xd1, 0x75, 0x65, 0xcf, 0xa7, 0x71, 0x72, 0x10, 0x21, 0x07,
0x09, 0xa6, 0x93, 0xab, 0x86, 0x06, 0x08, 0x4a, 0x05, 0xdb, 0x10, 0xb2,
0x6d, 0x4b, 0x0e, 0xb8, 0x96, 0x9e, 0x55, 0x41, 0xcc, 0x56, 0x02, 0xd0,
0x9c, 0xc0, 0x92, 0x9a, 0xf9, 0x2b, 0x35, 0x19, 0x86, 0x6b, 0x92, 0x7d,
0x8e, 0xa0, 0x4d, 0x03, 0xb0, 0xe7, 0xff, 0x77, 0x2d, 0x7c, 0x20, 0x6c,
0x10, 0xaa, 0x6f, 0xee, 0x62, 0x8a, 0x00, 0x4b, 0xce, 0x84, 0xe2, 0xf4,
0xbb, 0x67, 0x87, 0x95, 0x01, 0x60, 0xaf, 0x05, 0x34, 0xc0, 0x64, 0xdb,
0xa0, 0x83, 0x57, 0x34, 0xb6, 0x86, 0x43, 0xeb, 0x8a, 0x25, 0x99, 0x04,
0xd9, 0xc5, 0xfd, 0x37, 0xfa, 0xc1, 0x65, 0x12, 0x88, 0x9d, 0x58, 0xc9,
0x7e, 0x54, 0x4d, 0x80, 0xb1, 0x9a, 0x75, 0xa9, 0x7d, 0xf8, 0x2c, 0x90,
0xed, 0x3b, 0x3c, 0x7e, 0x11, 0x6c, 0x4f, 0x89, 0x75, 0x89, 0xc5, 0xff,
0x0b, 0x20, 0x6a, 0xb5, 0x33, 0x3c, 0x3c, 0x78, 0xaa, 0x92, 0xa7, 0xb3,
0xdd, 0x3e, 0x55, 0x30, 0xd6, 0x38, 0x60, 0xa5, 0x82, 0x17, 0xc0, 0x52,
0xf0, 0xaa, 0x87, 0x58, 0x6a, 0x3b, 0x56, 0x02, 0x0d, 0xe1, 0x03, 0x08,
0x72, 0x0e, 0x2d, 0xc2, 0x37, 0xfe, 0x8e, 0xd1, 0x04, 0x1a, 0x69, 0x8a,
0x94, 0x90, 0x53, 0x9c, 0x43, 0x6f, 0x2a, 0x78, 0x67, 0xef, 0x6e, 0x69,
0x22, 0x18, 0xc2, 0x57, 0xe1, 0x23, 0x82, 0x47, 0xf8, 0xc6, 0xf1, 0xbe,
0x92, 0xc0, 0x01, 0xa7, 0x3f, 0xc7, 0xae, 0x05, 0x14, 0x22, 0x81, 0x53,
0xf8, 0x2a, 0x28, 0x12, 0x30, 0x85, 0x6f, 0x7c, 0xde, 0x47, 0x12, 0xe8,
0x5a, 0xe0, 0xf8, 0xf4, 0x8c, 0xb5, 0x17, 0x33, 0x6b, 0x3c, 0x80, 0xc0,
0xe9, 0xe8, 0x0d, 0xe9, 0x17, 0x28, 0x0e, 0x47, 0x4e, 0x1b, 0x48, 0xd6,
0x27, 0x5c, 0xea, 0x4e, 0xf5, 0x0b, 0x02, 0x85, 0x4f, 0xfe, 0x6e, 0x4c,
0x88, 0xd8, 0xc1, 0xd2, 0x78, 0xce, 0x87, 0xc2, 0xe6, 0x0f, 0x10, 0x4e,
0x21, 0x80, 0x8f, 0x1a, 0x80, 0xd5, 0xd0, 0xa3, 0x27, 0x8f, 0xbb, 0xd2,
0x04, 0xd6, 0xe2, 0x94, 0x2b, 0xab, 0x28, 0xcc, 0x41, 0x82, 0xf0, 0x1b,
0xe7, 0xd7, 0xac, 0x09, 0x6c, 0x1a, 0xd1, 0xf6, 0x80, 0x10, 0xda, 0x00,
0x80, 0x39, 0x31, 0x84, 0x15, 0x7e, 0xb5, 0xec, 0x17, 0xb0, 0x2a, 0x93,
0x36, 0x12, 0x64, 0xda, 0x7b, 0xa8, 0x17, 0x24, 0xb0, 0x81, 0x49, 0x02,
0x00, 0x61, 0x2b, 0x84, 0x18, 0x68, 0x81, 0x08, 0x41, 0x65, 0x69, 0x5d,
0x1b, 0x64, 0xde, 0x78, 0x6a, 0x25, 0x49, 0xa0, 0x23, 0xd8, 0x36, 0xda,
0x66, 0x9d, 0x64, 0xd8, 0x6d, 0x2b, 0x69, 0x4c, 0x82, 0xcd, 0xc6, 0x65,
0x40, 0xdb, 0x3e, 0x41, 0x48, 0xfe, 0x60, 0x01, 0xb8, 0x1d, 0xe2, 0x80,
0x49, 0x3c, 0x64, 0x26, 0xd0, 0xf6, 0xa3, 0x6d, 0x33, 0xb8, 0x56, 0xe1,
0x37, 0xae, 0xd7, 0x82, 0x26, 0x58, 0x58, 0x8e, 0x47, 0x21, 0x54, 0xf8,
0x80, 0x7f, 0xad, 0xe0, 0x46, 0xc3, 0x5c, 0x73, 0xce, 0x94, 0xcf, 0xa2,
0x17, 0x36, 0xac, 0x50, 0xf8, 0xc6, 0x75, 0x33, 0x92, 0x20, 0xe7, 0x02,
0x90, 0x14, 0x58, 0x3e, 0x9d, 0xab, 0x18, 0x04, 0xe0, 0xe3, 0xae, 0x14,
0x9c, 0xcd, 0x0a, 0x22, 0xd0, 0x07, 0xe1, 0x1b, 0xd7, 0x2f, 0xe9, 0x13,
0x30, 0x2b, 0xa1, 0xec, 0xbe, 0xa0, 0x08, 0xd0, 0x10, 0xbe, 0x8a, 0xbd,
0x7b, 0xfb, 0xec, 0xf5, 0xe7, 0x18, 0xe8, 0x93, 0xf0, 0x8d, 0xdf, 0x89,
0x21, 0xc1, 0xd3, 0x1f, 0x7f, 0x72, 0xfe, 0xcd, 0x45, 0xce, 0x01, 0xa8,
0xd6, 0x21, 0x61, 0xbe, 0x27, 0xde, 0xb6, 0x79, 0x01, 0xd3, 0x19, 0xec,
0xa3, 0xf0, 0x55, 0x44, 0x3b, 0x86, 0x7b, 0xbb, 0xcd, 0xf0, 0xf5, 0xd5,
0x6f, 0xbf, 0x88, 0xb7, 0xde, 0xfe, 0x52, 0x9f, 0x7e, 0x2a, 0xf3, 0x19,
0xb3, 0x0b, 0x6b, 0xd0, 0x62, 0xd1, 0xfa, 0x82, 0x83, 0xa3, 0xf3, 0x8b,
0x98, 0x7d, 0xee, 0xfa, 0x2e, 0x7c, 0xf1, 0xbb, 0x51, 0x55, 0xc4, 0xc9,
0xf5, 0x32, 0x3c, 0xd3, 0x89, 0x80, 0xc0, 0x6a, 0x22, 0x35, 0x21, 0x45,
0x44, 0x07, 0x0a, 0x11, 0xbc, 0xd7, 0x64, 0x0f, 0x0a, 0xb5, 0x81, 0x22,
0x81, 0x43, 0x0b, 0xac, 0x82, 0xf0, 0x55, 0x24, 0x85, 0x88, 0x82, 0x04,
0x1e, 0x2d, 0x60, 0x3c, 0xfd, 0x3a, 0x18, 0xda, 0xc0, 0xda, 0x57, 0xb6,
0x41, 0xa1, 0x2e, 0x18, 0x9f, 0x3b, 0xfc, 0x02, 0x72, 0x7b, 0x99, 0xd3,
0xd1, 0x9b, 0xe0, 0x24, 0x52, 0x85, 0xc2, 0x07, 0xf2, 0x0f, 0x2a, 0xd1,
0x21, 0xfb, 0x2c, 0x74, 0x52, 0x8a, 0x96, 0x2b, 0x08, 0xae, 0x06, 0xfa,
0x42, 0x08, 0x27, 0x09, 0x88, 0x70, 0xd1, 0x18, 0x6c, 0x22, 0xc0, 0x21,
0x42, 0xa5, 0xc2, 0x17, 0x68, 0x9b, 0x04, 0x52, 0xbb, 0x24, 0x92, 0x80,
0x44, 0x4a, 0x67, 0x5a, 0x9d, 0x45, 0x61, 0x12, 0xf4, 0xe9, 0xc9, 0x9c,
0x05, 0x8c, 0xf4, 0x6a, 0x63, 0xe5, 0xc2, 0x57, 0x21, 0x49, 0xfe, 0xf4,
0xc7, 0x9f, 0xa4, 0xad, 0xf7, 0xc1, 0x61, 0x06, 0xac, 0xe6, 0x45, 0x8c,
0x71, 0xa4, 0xc6, 0x46, 0x1e, 0x1c, 0x3e, 0x6f, 0x98, 0x04, 0xdf, 0x22,
0x5e, 0xb1, 0x3b, 0x86, 0x38, 0xfd, 0x04, 0xca, 0x24, 0x70, 0x57, 0xaf,
0x12, 0xda, 0x60, 0x3a, 0xb9, 0xea, 0x93, 0xf0, 0x01, 0xc5, 0x31, 0x7c,
0xf5, 0xdb, 0x2f, 0x41, 0x24, 0x20, 0xe0, 0xf4, 0x2d, 0xa8, 0x7d, 0x9b,
0x04, 0x42, 0x57, 0x6d, 0x8d, 0x21, 0x80, 0x35, 0x4f, 0xa0, 0x23, 0x32,
0x4a, 0x00, 0xd0, 0xab, 0x27, 0x5f, 0x85, 0x24, 0x41, 0x02, 0x62, 0x1c,
0x4b, 0xdf, 0xb8, 0x8e, 0x20, 0x27, 0xd0, 0x05, 0xb6, 0xf0, 0x05, 0x62,
0x92, 0x47, 0xdc, 0xc5, 0x0d, 0x2a, 0x84, 0xec, 0x9f, 0xf7, 0x7f, 0xff,
0x8d, 0xbd, 0xdd, 0x5d, 0x2a, 0xdc, 0x6b, 0x40, 0x68, 0x09, 0x3d, 0x29,
0x14, 0x21, 0x7c, 0xf1, 0xde, 0x5b, 0xce, 0xb7, 0x7d, 0xd9, 0x87, 0x60,
0xe1, 0x53, 0xf0, 0x69, 0x84, 0x55, 0x10, 0xbe, 0x1a, 0xb2, 0xdd, 0xf9,
0xec, 0xb3, 0xc6, 0x49, 0x09, 0x66, 0xa1, 0x81, 0x5c, 0xdb, 0xf2, 0x05,
0x95, 0x21, 0x01, 0xc8, 0xdd, 0xa9, 0x6f, 0xde, 0xdd, 0x46, 0xff, 0x28,
0x23, 0x8b, 0x08, 0xf4, 0x4b, 0xf8, 0x80, 0x43, 0xf5, 0xdb, 0x52, 0xbe,
0xa9, 0x64, 0xc8, 0x31, 0xfc, 0x2c, 0x88, 0x00, 0xea, 0xfe, 0xf4, 0x6f,
0x6f, 0xdf, 0x03, 0x58, 0x12, 0x81, 0xca, 0x10, 0xfa, 0xe0, 0x48, 0x1e,
0x85, 0xb4, 0xab, 0x46, 0x38, 0x7d, 0x00, 0x9d, 0x0c, 0x29, 0x24, 0xe8,
0x9c, 0x00, 0x00, 0xf0, 0xaf, 0xff, 0xfc, 0x0f, 0xef, 0xde, 0x2f, 0x85,
0xff, 0xfd, 0x37, 0x5f, 0x59, 0xbf, 0x10, 0x52, 0x4b, 0x58, 0x21, 0xe1,
0x53, 0x70, 0x6a, 0x86, 0x1c, 0x26, 0x21, 0xc5, 0x1c, 0x44, 0xf9, 0x00,
0x9e, 0x6b, 0xb1, 0x8a, 0x49, 0xc0, 0x92, 0x04, 0x2b, 0x2e, 0x7c, 0x0a,
0x8d, 0x7e, 0xcc, 0xb1, 0x2c, 0x5d, 0x8a, 0x26, 0x08, 0x09, 0x03, 0xa9,
0x10, 0x27, 0x49, 0x60, 0x9f, 0xa0, 0xf0, 0x81, 0xc0, 0xa1, 0xf8, 0x80,
0x7f, 0x36, 0xb3, 0x36, 0x45, 0x2d, 0x08, 0xa1, 0x79, 0x80, 0x4f, 0x45,
0x48, 0x6d, 0x63, 0x01, 0xf0, 0x9e, 0x7e, 0xd7, 0x6c, 0xe6, 0x1c, 0xda,
0x23, 0xb7, 0x40, 0x25, 0xab, 0x85, 0xc3, 0x38, 0xbe, 0x9e, 0x39, 0xbf,
0x90, 0xba, 0xfd, 0x79, 0x0f, 0xc1, 0x4e, 0xf4, 0x10, 0xaa, 0x9d, 0xa3,
0x35, 0x82, 0xfa, 0x30, 0xe7, 0xbe, 0x81, 0x86, 0xf0, 0x01, 0x60, 0x7f,
0x77, 0x1b, 0x5b, 0x9b, 0x1b, 0xd6, 0x2f, 0x69, 0xc3, 0xcd, 0xba, 0x18,
0x80, 0x5a, 0x0a, 0x8d, 0x7b, 0x3b, 0x38, 0x7c, 0x1e, 0xb2, 0x8a, 0x89,
0x5a, 0x84, 0x73, 0x25, 0x77, 0x82, 0x1f, 0xa0, 0xe8, 0xdd, 0xc3, 0x35,
0x90, 0xc2, 0x57, 0xb1, 0xb5, 0xb9, 0xe1, 0xcc, 0x1d, 0x8c, 0xce, 0x2f,
0x54, 0x6d, 0x50, 0x64, 0xaa, 0x75, 0x8b, 0x20, 0x93, 0x44, 0x81, 0xdf,
0xb7, 0xce, 0x6a, 0x4e, 0x41, 0xae, 0x4e, 0x5e, 0x00, 0xcb, 0x10, 0x11,
0x80, 0x0c, 0x13, 0x05, 0xd4, 0x70, 0xd1, 0x17, 0x1d, 0x10, 0xdb, 0x9c,
0xf6, 0x99, 0x08, 0x0d, 0x6d, 0xe6, 0x12, 0xbe, 0xad, 0xd4, 0x9b, 0x2b,
0xe3, 0x67, 0x43, 0x2b, 0x04, 0x00, 0xe0, 0xcc, 0x15, 0x70, 0x42, 0xc4,
0x15, 0xf0, 0x0d, 0xd8, 0xc2, 0x17, 0x28, 0x41, 0x82, 0xd6, 0x08, 0xf0,
0xdd, 0xd7, 0x5f, 0x52, 0xbf, 0x15, 0x54, 0x4f, 0xe8, 0xa9, 0x36, 0x68,
0x08, 0x5e, 0x0c, 0xca, 0x08, 0x19, 0xc9, 0xab, 0x13, 0xa1, 0xcd, 0x19,
0xc7, 0x59, 0x09, 0xc0, 0xfc, 0x8d, 0x05, 0xb0, 0xac, 0x29, 0x70, 0xeb,
0x09, 0x96, 0xdd, 0xaf, 0x6b, 0x22, 0x83, 0x71, 0xff, 0xd4, 0x68, 0x1c,
0x8a, 0x04, 0x7a, 0xfd, 0x5e, 0x68, 0x0a, 0x07, 0x09, 0xaa, 0x24, 0x00,
0x40, 0x93, 0xc0, 0x3a, 0x28, 0x34, 0xa6, 0xa8, 0x54, 0x21, 0x11, 0x9c,
0x0b, 0x6a, 0xd9, 0x26, 0x68, 0x0a, 0x22, 0xd8, 0x06, 0x6f, 0x50, 0x24,
0x68, 0x8b, 0x00, 0xb9, 0xa2, 0x00, 0x20, 0xa0, 0x61, 0x42, 0xf8, 0x80,
0x3f, 0x3a, 0x50, 0x21, 0xcc, 0x86, 0x96, 0x42, 0xce, 0x9a, 0x9d, 0xf4,
0xc0, 0xaa, 0xe9, 0x42, 0x46, 0x39, 0x13, 0xfb, 0x25, 0xa8, 0x6d, 0x5e,
0x08, 0x62, 0x1c, 0x1c, 0x3e, 0x6f, 0x7d, 0xc9, 0xdb, 0xae, 0x9f, 0x1e,
0xa9, 0xfe, 0x75, 0x84, 0x96, 0x97, 0x6d, 0xe3, 0x0e, 0x2d, 0x88, 0xb9,
0x4f, 0x6f, 0x3e, 0x82, 0x2b, 0x74, 0x9b, 0x26, 0x70, 0x8c, 0xd7, 0x0b,
0x31, 0xa9, 0x49, 0xc8, 0xa9, 0x01, 0x7c, 0x90, 0x37, 0xf5, 0xc5, 0xc6,
0x1d, 0x59, 0x4e, 0x06, 0x80, 0x47, 0x0f, 0xee, 0x1b, 0x27, 0xfb, 0x9c,
0xc4, 0xbd, 0x7b, 0xfb, 0x98, 0x5c, 0x8e, 0x8d, 0xb1, 0x86, 0x16, 0x42,
0x64, 0x4b, 0x2e, 0xc5, 0xcc, 0x67, 0xf0, 0x6c, 0xb6, 0x05, 0x98, 0x71,
0xbe, 0x2d, 0xeb, 0x57, 0x6d, 0x14, 0xc0, 0x81, 0x91, 0x2b, 0x78, 0x7b,
0xfb, 0x9e, 0x14, 0xbe, 0x40, 0xcc, 0xb8, 0x02, 0x0a, 0x4c, 0x2d, 0x61,
0x20, 0xd7, 0xde, 0x09, 0x2a, 0x74, 0x22, 0x94, 0xde, 0x7a, 0xb7, 0x4b,
0x0d, 0xd0, 0xc0, 0xe6, 0x9d, 0x3b, 0xfa, 0xa1, 0xe0, 0x2a, 0x19, 0xf0,
0x71, 0x04, 0xb2, 0x8b, 0x08, 0xdc, 0x11, 0xc9, 0xb9, 0x17, 0xc6, 0xd4,
0x97, 0x6a, 0x3d, 0x3e, 0x3d, 0x8b, 0xde, 0x67, 0xa9, 0x2d, 0xe4, 0xac,
0x05, 0x04, 0xe1, 0xbb, 0xaf, 0xbf, 0xb4, 0x0d, 0x28, 0x91, 0x4f, 0x82,
0xab, 0x86, 0xa0, 0x23, 0xc7, 0xac, 0xe5, 0x9c, 0xcb, 0xdd, 0x50, 0xeb,
0xf4, 0xaa, 0xc7, 0xc4, 0xf2, 0x36, 0x4a, 0xb8, 0x58, 0xa4, 0x06, 0xd2,
0xb9, 0x06, 0xb0, 0x24, 0x88, 0xac, 0x10, 0x24, 0xe0, 0x38, 0x89, 0xc2,
0x2f, 0x48, 0xc1, 0xce, 0xde, 0xdd, 0xdc, 0x9a, 0xc0, 0xa9, 0xd9, 0x4a,
0xef, 0xc8, 0xda, 0x25, 0x01, 0xb8, 0x03, 0x4a, 0xc8, 0x8e, 0xda, 0xda,
0xdc, 0x88, 0x72, 0x16, 0x63, 0xa0, 0x4e, 0x4e, 0xc9, 0x88, 0x01, 0x80,
0x85, 0xd0, 0x02, 0x6d, 0xf8, 0x17, 0x31, 0xa8, 0x29, 0x9b, 0x26, 0x40,
0x86, 0x8a, 0x21, 0xce, 0x62, 0xaa, 0x16, 0x50, 0xe1, 0x99, 0xaa, 0x06,
0x80, 0x16, 0xa6, 0x63, 0xf1, 0xcc, 0xc6, 0x0c, 0xe9, 0xd2, 0x13, 0x60,
0x8a, 0xf9, 0x00, 0x09, 0xf0, 0x4e, 0x76, 0xc8, 0xb9, 0x92, 0x89, 0xea,
0x17, 0x64, 0xda, 0x1b, 0x41, 0xb6, 0xbd, 0xb4, 0xf0, 0x81, 0x82, 0x51,
0x40, 0x08, 0x2c, 0x4f, 0xbf, 0x73, 0x84, 0x0c, 0x95, 0x36, 0x66, 0x2c,
0x71, 0x47, 0x9e, 0x6b, 0x71, 0x0e, 0xa5, 0x6d, 0x3f, 0x39, 0x7a, 0x16,
0xaa, 0xd2, 0xf5, 0xb6, 0x17, 0xd3, 0xc4, 0xd5, 0x6b, 0x00, 0x4d, 0xf8,
0x64, 0x47, 0xe9, 0xd1, 0x82, 0xa5, 0x66, 0x40, 0x0a, 0xda, 0x96, 0x23,
0xf0, 0xe4, 0x0e, 0x72, 0x08, 0x8c, 0x35, 0x75, 0xab, 0x6d, 0x54, 0x4b,
0x80, 0x47, 0x0f, 0xee, 0xb3, 0x84, 0x2f, 0x60, 0x09, 0x19, 0x43, 0x3b,
0x38, 0x46, 0x18, 0x03, 0xa0, 0xcc, 0xd6, 0x39, 0x39, 0x50, 0x23, 0x01,
0x28, 0x21, 0xb0, 0x04, 0xb3, 0xb5, 0xb9, 0x91, 0x52, 0x31, 0xb4, 0x2e,
0x7e, 0xc1, 0x45, 0x1f, 0x49, 0x50, 0xab, 0x0f, 0x90, 0x4b, 0xc5, 0x76,
0x05, 0x69, 0xd3, 0xfb, 0x46, 0x82, 0x1a, 0x35, 0x40, 0x34, 0x5c, 0xa1,
0x62, 0x20, 0x06, 0x40, 0x70, 0x0d, 0x21, 0x5a, 0x73, 0x95, 0x44, 0xad,
0x1a, 0x20, 0x08, 0x84, 0xe0, 0x59, 0x1d, 0x9f, 0x61, 0x81, 0xeb, 0xa8,
0xdf, 0xad, 0x09, 0xbd, 0x6b, 0x30, 0x81, 0x90, 0x01, 0x21, 0x8d, 0x15,
0xcf, 0x19, 0x04, 0x08, 0x5e, 0x71, 0xa3, 0x6f, 0x58, 0x99, 0x1b, 0x61,
0x22, 0xa6, 0xc6, 0xde, 0xe5, 0x88, 0xa3, 0xce, 0xb1, 0x52, 0x37, 0xc3,
0x44, 0x15, 0x09, 0x98, 0x5a, 0xf0, 0x7f, 0x84, 0x1d, 0xed, 0xfe, 0xb1,
0x2a, 0x47, 0x9d, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
0x42, 0x60, 0x82
};
unsigned int icon_png_len = 3699;

View File

@@ -1,117 +0,0 @@
#include "midi.h"
#include <stdio.h>
#include <poll.h>
#include <stdatomic.h>
#include <alloca.h>
extern atomic_int gRunning;
int midiInit(MidiState *m, Synth *synth)
{
if (snd_seq_open(&m->seq, "default", SND_SEQ_OPEN_INPUT | SND_SEQ_NONBLOCK, 0) < 0) {
fprintf(stderr, "Failed to open ALSA sequencer\n");
return 0;
}
snd_seq_nonblock(m->seq, 1);
snd_seq_set_client_name(m->seq, "soundThing");
m->client = snd_seq_client_id(m->seq);
m->port = snd_seq_create_simple_port(m->seq, "MIDI In",
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
SND_SEQ_PORT_TYPE_APPLICATION);
m->synth = synth;
return 1;
}
void midiListInputs(MidiState *m)
{
snd_seq_client_info_t *cinfo;
snd_seq_port_info_t *pinfo;
snd_seq_client_info_alloca(&cinfo);
snd_seq_port_info_alloca(&pinfo);
printf("\nAvailable MIDI inputs:\n");
snd_seq_client_info_set_client(cinfo, -1);
while (snd_seq_query_next_client(m->seq, cinfo) >= 0) {
int client = snd_seq_client_info_get_client(cinfo);
if (client == m->client) continue;
snd_seq_port_info_set_client(pinfo, client);
snd_seq_port_info_set_port(pinfo, -1);
while (snd_seq_query_next_port(m->seq, pinfo) >= 0) {
unsigned int caps = snd_seq_port_info_get_capability(pinfo);
if (caps & SND_SEQ_PORT_CAP_READ && caps & SND_SEQ_PORT_CAP_SUBS_READ) {
printf(" [%d:%d] %s — %s\n",
client,
snd_seq_port_info_get_port(pinfo),
snd_seq_client_info_get_name(cinfo),
snd_seq_port_info_get_name(pinfo));
}
}
}
}
void midiConnect(MidiState *m, int srcClient, int srcPort)
{
snd_seq_port_subscribe_t *sub;
snd_seq_addr_t sender, dest;
sender.client = srcClient;
sender.port = srcPort;
dest.client = m->client;
dest.port = m->port;
snd_seq_port_subscribe_alloca(&sub);
snd_seq_port_subscribe_set_sender(sub, &sender);
snd_seq_port_subscribe_set_dest(sub, &dest);
snd_seq_subscribe_port(m->seq, sub);
printf("Connected [%d:%d] to Cricket\n", srcClient, srcPort);
}
void midiClose(MidiState *m)
{
snd_seq_close(m->seq);
}
void *midiThread(void *arg)
{
MidiState *m = (MidiState *)arg;
struct pollfd pfd;
snd_seq_poll_descriptors(m->seq, &pfd, 1, POLLIN);
while (gRunning) {
int ready = poll(&pfd, 1, 50);
if (ready <= 0) continue;
snd_seq_event_t *ev = NULL;
while (snd_seq_event_input(m->seq, &ev) >= 0 && ev) {
switch (ev->type) {
case SND_SEQ_EVENT_NOTEON:
if (ev->data.note.velocity == 0)
synthNoteOff(m->synth, ev->data.note.note);
else
synthNoteOn(m->synth, ev->data.note.note);
break;
case SND_SEQ_EVENT_NOTEOFF:
synthNoteOff(m->synth, ev->data.note.note);
break;
case SND_SEQ_EVENT_CONTROLLER:
// This are controller parameter changes.
// Map the Mod Wheel to PWM:
if (ev->data.control.param == 1)
m->synth->dutyCycle = 0.05f + (ev->data.control.value / 127.0f) * 0.90f;
// This naively maps parameter 70 to the waveform in a way that does not play so nice with real pots.
if (ev->data.control.param == 70)
m->synth->waveform = ev->data.control.value % WAVE_COUNT;
break;
case SND_SEQ_EVENT_PITCHBEND:
m->synth->pitchBend = ev->data.control.value / 8191.0f;
break;
}
snd_seq_free_event(ev);
ev = NULL;
}
}
return NULL;
}

157
source/midi_linux.c Normal file
View File

@@ -0,0 +1,157 @@
#include "midi.h"
#include <stdio.h>
#include <poll.h>
#include <stdatomic.h>
#include <alloca.h>
extern atomic_int gRunning;
int midiInit(MidiState *m, Synth *synth)
{
if (snd_seq_open(&m->seq, "default", SND_SEQ_OPEN_INPUT | SND_SEQ_NONBLOCK, 0) < 0) {
fprintf(stderr, "Failed to open ALSA sequencer\n");
return 0;
}
snd_seq_nonblock(m->seq, 1);
snd_seq_set_client_name(m->seq, "soundThing");
m->client = snd_seq_client_id(m->seq);
m->port = snd_seq_create_simple_port(m->seq, "MIDI In",
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
SND_SEQ_PORT_TYPE_APPLICATION);
m->synth = synth;
m->connectedClient = -1;
m->connectedPort = -1;
m->inputCount = 0;
atomic_store(&m->midiLearnMode, 0);
atomic_store(&m->midiLearnCC, -1);
for (int i = 0; i < 128; i++) m->ccMappings[i].active = 0;
return 1;
}
void midiScanInputs(MidiState *m)
{
m->inputCount = 0;
snd_seq_client_info_t *cinfo;
snd_seq_port_info_t *pinfo;
snd_seq_client_info_alloca(&cinfo);
snd_seq_port_info_alloca(&pinfo);
snd_seq_client_info_set_client(cinfo, -1);
while (snd_seq_query_next_client(m->seq, cinfo) >= 0) {
int client = snd_seq_client_info_get_client(cinfo);
if (client == m->client) continue;
snd_seq_port_info_set_client(pinfo, client);
snd_seq_port_info_set_port(pinfo, -1);
while (snd_seq_query_next_port(m->seq, pinfo) >= 0) {
if (m->inputCount >= MIDI_MAX_INPUTS) break;
unsigned int caps = snd_seq_port_info_get_capability(pinfo);
if ((caps & SND_SEQ_PORT_CAP_READ) && (caps & SND_SEQ_PORT_CAP_SUBS_READ)) {
MidiInputInfo *info = &m->inputs[m->inputCount++];
info->client = client;
info->port = snd_seq_port_info_get_port(pinfo);
snprintf(info->clientName, sizeof(info->clientName), "%s",
snd_seq_client_info_get_name(cinfo));
snprintf(info->portName, sizeof(info->portName), "%s",
snd_seq_port_info_get_name(pinfo));
}
}
if (m->inputCount >= MIDI_MAX_INPUTS) break;
}
}
void midiDevDisconnect(MidiState *m)
{
if (m->connectedClient < 0) return;
snd_seq_port_subscribe_t *sub;
snd_seq_addr_t sender, dest;
sender.client = m->connectedClient;
sender.port = m->connectedPort;
dest.client = m->client;
dest.port = m->port;
snd_seq_port_subscribe_alloca(&sub);
snd_seq_port_subscribe_set_sender(sub, &sender);
snd_seq_port_subscribe_set_dest(sub, &dest);
snd_seq_unsubscribe_port(m->seq, sub);
m->connectedClient = -1;
m->connectedPort = -1;
}
void midiDevConnect(MidiState *m, int srcClient, int srcPort)
{
midiDevDisconnect(m);
snd_seq_port_subscribe_t *sub;
snd_seq_addr_t sender, dest;
sender.client = srcClient;
sender.port = srcPort;
dest.client = m->client;
dest.port = m->port;
snd_seq_port_subscribe_alloca(&sub);
snd_seq_port_subscribe_set_sender(sub, &sender);
snd_seq_port_subscribe_set_dest(sub, &dest);
snd_seq_subscribe_port(m->seq, sub);
m->connectedClient = srcClient;
m->connectedPort = srcPort;
}
void midiClose(MidiState *m)
{
midiDevDisconnect(m);
snd_seq_close(m->seq);
}
void *midiThread(void *arg)
{
MidiState *m = (MidiState *)arg;
struct pollfd pfd;
snd_seq_poll_descriptors(m->seq, &pfd, 1, POLLIN);
while (gRunning) {
int ready = poll(&pfd, 1, 50);
if (ready <= 0) continue;
snd_seq_event_t *ev = NULL;
while (snd_seq_event_input(m->seq, &ev) >= 0 && ev) {
switch (ev->type) {
case SND_SEQ_EVENT_NOTEON:
if (ev->data.note.velocity == 0)
synthNoteOff(m->synth, ev->data.note.note);
else
synthNoteOn(m->synth, ev->data.note.note);
break;
case SND_SEQ_EVENT_NOTEOFF:
synthNoteOff(m->synth, ev->data.note.note);
break;
case SND_SEQ_EVENT_CONTROLLER: {
int cc = ev->data.control.param;
int val = ev->data.control.value;
if (cc < 128) {
if (atomic_load(&m->midiLearnMode))
atomic_store(&m->midiLearnCC, cc);
if (m->ccMappings[cc].active) {
float t = (float)val / 127.0f;
*m->ccMappings[cc].valuePtr =
m->ccMappings[cc].min +
t * (m->ccMappings[cc].max - m->ccMappings[cc].min);
}
}
break;
}
case SND_SEQ_EVENT_PITCHBEND:
m->synth->pitchBend = ev->data.control.value / 8191.0f;
break;
}
snd_seq_free_event(ev);
ev = NULL;
}
}
return NULL;
}

108
source/midi_windows.c Normal file
View File

@@ -0,0 +1,108 @@
#include "midi.h"
#include <windows.h>
#include <mmsystem.h>
#include <stdatomic.h>
#include <stdio.h>
extern atomic_int gRunning;
static void CALLBACK midiCallbackWin32(HMIDIIN hIn, UINT msg, DWORD_PTR instance,
DWORD_PTR param1, DWORD_PTR param2)
{
(void)hIn; (void)param2;
if (msg != MIM_DATA) return;
MidiState *m = (MidiState *)instance;
BYTE status = (BYTE)( param1 & 0xFF);
BYTE data1 = (BYTE)((param1 >> 8) & 0xFF);
BYTE data2 = (BYTE)((param1 >> 16) & 0xFF);
BYTE type = status & 0xF0;
if (type == 0x90 && data2 > 0) {
synthNoteOn(m->synth, data1);
} else if (type == 0x80 || (type == 0x90 && data2 == 0)) {
synthNoteOff(m->synth, data1);
} else if (type == 0xB0) {
int cc = data1, val = data2;
if (cc < 128) {
if (atomic_load(&m->midiLearnMode))
atomic_store(&m->midiLearnCC, cc);
if (m->ccMappings[cc].active) {
float t = (float)val / 127.0f;
*m->ccMappings[cc].valuePtr =
m->ccMappings[cc].min + t * (m->ccMappings[cc].max - m->ccMappings[cc].min);
}
}
} else if (type == 0xE0) {
int pb = (data2 << 7) | data1; // 0..16383, centre = 8192
m->synth->pitchBend = (pb - 8192) / 8191.0f;
}
}
int midiInit(MidiState *m, Synth *synth)
{
m->synth = synth;
m->hMidiIn = NULL;
m->connectedClient = -1;
m->connectedPort = -1;
m->inputCount = 0;
atomic_store(&m->midiLearnMode, 0);
atomic_store(&m->midiLearnCC, -1);
for (int i = 0; i < 128; i++) m->ccMappings[i].active = 0;
return 1;
}
void midiScanInputs(MidiState *m)
{
m->inputCount = 0;
UINT n = midiInGetNumDevs();
for (UINT i = 0; i < n && m->inputCount < MIDI_MAX_INPUTS; i++) {
MIDIINCAPSA caps;
if (midiInGetDevCapsA(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR) {
MidiInputInfo *info = &m->inputs[m->inputCount++];
info->client = (int)i;
info->port = 0;
snprintf(info->clientName, sizeof(info->clientName), "%s", caps.szPname);
snprintf(info->portName, sizeof(info->portName), "%s", caps.szPname);
}
}
}
void midiDevDisconnect(MidiState *m)
{
if (m->hMidiIn) {
midiInStop((HMIDIIN)m->hMidiIn);
midiInClose((HMIDIIN)m->hMidiIn);
m->hMidiIn = NULL;
}
m->connectedClient = -1;
m->connectedPort = -1;
}
void midiDevConnect(MidiState *m, int srcClient, int srcPort)
{
(void)srcPort;
midiDevDisconnect(m);
HMIDIIN hIn = NULL;
if (midiInOpen(&hIn, (UINT)srcClient, (DWORD_PTR)midiCallbackWin32,
(DWORD_PTR)m, CALLBACK_FUNCTION) == MMSYSERR_NOERROR) {
midiInStart(hIn);
m->hMidiIn = hIn;
m->connectedClient = srcClient;
m->connectedPort = 0;
}
}
void midiClose(MidiState *m)
{
midiDevDisconnect(m);
}
void *midiThread(void *arg)
{
// WinMM delivers MIDI events via midiCallbackWin32 on its own MM thread.
// This function exists only to satisfy the pthread_create call in soundThing.c.
(void)arg;
while (gRunning) Sleep(50);
return NULL;
}

288
source/patch.c Normal file
View File

@@ -0,0 +1,288 @@
#define _POSIX_C_SOURCE 200809L
#include "patch.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#ifdef _WIN32
# include <direct.h>
# include <sys/stat.h>
# define MAKE_DIR(p) _mkdir(p)
#else
# include <sys/stat.h>
# 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;
}

29
source/platform.c Normal file
View File

@@ -0,0 +1,29 @@
#define _POSIX_C_SOURCE 200809L
/* Isolated from raylib.h to avoid Rectangle/CloseWindow/ShowCursor conflicts
with windows.h on Windows cross-compile targets. */
#ifdef _WIN32
# include <windows.h>
# include <direct.h>
#else
# include <unistd.h>
# include <libgen.h>
#endif
#include <string.h>
#include "platform.h"
void chdirToExeDir(void)
{
char path[1024];
#ifdef _WIN32
DWORD len = GetModuleFileNameA(NULL, path, (DWORD)sizeof(path));
if (!len) return;
char *last = strrchr(path, '\\');
if (!last) last = strrchr(path, '/');
if (last) { *last = '\0'; _chdir(path); }
#else
ssize_t len = readlink("/proc/self/exe", path, sizeof(path) - 1);
if (len <= 0) return;
path[len] = '\0';
if (chdir(dirname(path)) != 0) { }
#endif
}

View File

@@ -1,60 +1,60 @@
#define _POSIX_C_SOURCE 200809L
#include "raylib.h"
#include <stdio.h>
#include <string.h>
#include <stdatomic.h>
#include <pthread.h>
#include <alloca.h>
#include <alsa/asoundlib.h>
#include "raylib.h"
#include "synth.h"
#include "midi.h"
#include "ui.h"
#include "config.h"
#include "icon_data.h"
#include "platform.h"
#define LOGICAL_WIDTH 1020
#define LOGICAL_HEIGHT 710
#define BORDER_WIDTH 8
atomic_int gRunning = 1;
static Synth gSynth = {0};
static MidiState gMidi = {0};
static UIState gUI = {0};
static void AudioOutputCallback(void *buffer, unsigned int frames)
{
synthFillBuffer(&gSynth, (int16_t *)buffer, (int)frames);
}
static void drawWaveform(Waveform w, float dutyCycle, int x, int y, int width, int height)
{
int steps = width;
for (int i = 0; i < steps - 1; i++) {
float phase0 = (float)i / steps;
float phase1 = (float)(i + 1) / steps;
float s0 = waveformSample(w, phase0, dutyCycle);
float s1 = waveformSample(w, phase1, dutyCycle);
int cx = x + i;
int cy = y + (int)((1.0f - (s0 * 0.5f + 0.5f)) * height);
int nx = x + i + 1;
int ny = y + (int)((1.0f - (s1 * 0.5f + 0.5f)) * height);
DrawLine(cx, cy, nx, ny, GREEN);
}
DrawRectangleLines(x, y, width, height, DARKGRAY);
}
int main(void)
{
chdirToExeDir();
const int sampleRate = 48000;
if (!midiInit(&gMidi, &gSynth)) return 1;
midiListInputs(&gMidi);
int srcClient, srcPort;
printf("\nEnter client and port to connect (e.g. '20 0'), or -1 -1 to skip: ");
if (scanf("%d %d", &srcClient, &srcPort) == 2 && srcClient != -1)
midiConnect(&gMidi, srcClient, srcPort);
midiScanInputs(&gMidi);
pthread_t midiTid;
pthread_create(&midiTid, NULL, midiThread, &gMidi);
synthInit(&gSynth, (float)sampleRate);
InitWindow(800, 450, "SoundThing");
// Window setup
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
InitWindow(LOGICAL_WIDTH + 2 * BORDER_WIDTH, LOGICAL_HEIGHT + 2 * BORDER_WIDTH, "SoundThing");
{
Image appIcon = LoadImageFromMemory(".png", icon_png, (int)icon_png_len);
SetWindowIcon(appIcon);
UnloadImage(appIcon);
}
// Camera2D setup
Camera2D ui_cam = { 0 };
ui_cam.zoom = 1.0f;
recomputeViewPort(&ui_cam, LOGICAL_WIDTH, LOGICAL_HEIGHT, BORDER_WIDTH);
// Audio device setup
InitAudioDevice();
SetAudioStreamBufferSizeDefault(1024);
@@ -63,34 +63,141 @@ int main(void)
PlayAudioStream(stream);
SetTargetFPS(120);
uiInit(&gUI, &gMidi, &ui_cam);
// Auto-connect to last used MIDI device if still available
{
char lastDev[CONFIG_NAME_LEN];
if (configLoadLastDevice(lastDev, sizeof(lastDev))) {
for (int i = 0; i < gMidi.inputCount; i++) {
if (strcmp(gMidi.inputs[i].clientName, lastDev) == 0) {
midiDevConnect(&gMidi, gMidi.inputs[i].client, gMidi.inputs[i].port);
uiLoadCcMappingsForDevice(&gUI, lastDev);
break;
}
}
}
}
// Layout constants
const float pad = 10.0f;
const float oscW = 220.0f;
const float oscH = 340.0f;
const float envW = 180.0f;
const float envH = 280.0f;
const float masterW = 180.0f;
const float masterH = 360.0f;
const float lfoW = 180.0f;
const float lfoH = 155.0f;
const float filterW = 200.0f;
const float filterH = 300.0f;
while (!WindowShouldClose()) {
if (IsKeyPressed(KEY_UP))
gSynth.waveform = (gSynth.waveform + 1) % WAVE_COUNT;
if (IsKeyPressed(KEY_DOWN))
gSynth.waveform = (gSynth.waveform + WAVE_COUNT - 1) % WAVE_COUNT;
// Sync voice settings from voice 0
synthSyncVoices(&gSynth);
BeginDrawing();
ClearBackground(BLACK);
BeginMode2D(ui_cam);
if(IsWindowResized()) {
recomputeViewPort(&ui_cam, LOGICAL_WIDTH, LOGICAL_HEIGHT, BORDER_WIDTH);
}
ClearBackground((Color){ 189, 168, 131, 255 }); // tan background
drawWaveform(gSynth.waveform, gSynth.dutyCycle, 20, 120, 400, 150);
// // Oscillator panels — left side, stacked vertically
// uiOscillatorPanel(&gUI, 0,
// padding, padding,
// oscW, oscH,
// &gSynth.voices[0].oscillators[0], "Oscillator 0");
//
// uiOscillatorPanel(&gUI, 100,
// padding, padding + oscH + padding,
// oscW, oscH,
// &gSynth.voices[0].oscillators[1], "Oscillator 1");
//
// // Envelope panels — to the right of oscillators
// float envX = padding + oscW + padding;
// uiEnvelopePanel(&gUI, 200,
// envX, padding,
// envW, envH,
// &gSynth.voices[0].ampEnv, "Amp Envelope");
//
// uiEnvelopePanel(&gUI, 250,
// envX, padding + envH + padding,
// envW, envH,
// &gSynth.voices[0].modEnv, "Mod Envelope");
//
// // Master panel — to the right of envelopes
// float masterX = envX + envW + padding;
// uiMasterPanel(&gUI,
// masterX, padding,
// masterW, masterH,
// &gSynth);
DrawText("SoundThing", 20, 20, 24, RAYWHITE);
DrawText(TextFormat("Waveform: %s (UP/DOWN to change)", waveformName(gSynth.waveform)),
20, 55, 20, RAYWHITE);
DrawText(TextFormat("MIDI: client %d port %d", gMidi.client, gMidi.port),
20, 90, 20, GRAY);
if (gSynth.waveform == WAVE_PULSE)
DrawText(TextFormat("Duty Cycle: %.2f", gSynth.dutyCycle), 20, 280, 20, GREEN);
// Left column — oscillator panels stacked vertically
float oscX = pad;
uiOscillatorPanel(&gUI, 0,
oscX, pad,
oscW, oscH,
&gSynth.voices[0].oscillators[0], "Oscillator 0");
uiOscillatorPanel(&gUI, 100,
oscX, pad + oscH + pad,
oscW, oscH,
&gSynth.voices[0].oscillators[1], "Oscillator 1");
// Middle column — envelope panels stacked vertically
float envX = pad + oscW + pad;
uiEnvelopePanel(&gUI, 200,
envX, pad,
envW, envH,
&gSynth.voices[0].ampEnv, "Envelope 0 - Amp");
uiEnvelopePanel(&gUI, 250,
envX, pad + envH + pad,
envW, envH,
&gSynth.voices[0].modEnv, "Envelope 1 - Mod");
// LFO column
float lfoX = pad + oscW + pad + envW + pad;
uiLfoPanel(&gUI, 500,
lfoX, pad,
lfoW, lfoH,
&gSynth.lfos[0], "LFO 0");
uiLfoPanel(&gUI, 600,
lfoX, pad + lfoH + pad,
lfoW, lfoH,
&gSynth.lfos[1], "LFO 1");
// Filter panel
float filterX = lfoX + lfoW + pad;
uiFilterPanel(&gUI,
filterX, pad,
filterW, filterH,
&gSynth.voices[0].filter, "Filter");
// Master panel — rightmost
float masterX = filterX + filterW + pad;
uiMasterPanel(&gUI,
masterX, pad,
masterW, masterH,
&gSynth);
// Floating overlays — drawn last, each one on top of the previous
uiMidiMenu(&gUI, (LOGICAL_WIDTH - 420) * 0.5f, 100.0f, 420.0f, &gMidi);
uiPatchMenu(&gUI, (LOGICAL_WIDTH - 420) * 0.5f, 80.0f, 420.0f, &gSynth);
uiAboutMenu(&gUI, (LOGICAL_WIDTH - 420) * 0.5f, 160.0f, 420.0f);
EndDrawing();
}
gRunning = 0;
StopAudioStream(stream);
UnloadAudioStream(stream);
CloseAudioDevice();
CloseWindow();
pthread_join(midiTid, NULL);
uiClose(&gUI);
CloseWindow();
midiClose(&gMidi);
return 0;
}

49
source/sprites_data.c Normal file
View File

@@ -0,0 +1,49 @@
unsigned char sprites_png[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x10,
0x08, 0x06, 0x00, 0x00, 0x00, 0x92, 0x9e, 0xb8, 0x35, 0x00, 0x00, 0x00,
0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
0x01, 0xd8, 0x49, 0x44, 0x41, 0x54, 0x58, 0x85, 0xed, 0x58, 0xc1, 0x91,
0xc2, 0x30, 0x0c, 0xdc, 0x30, 0x14, 0xa0, 0x12, 0x5c, 0x82, 0x4b, 0xa0,
0x83, 0xb8, 0x84, 0x94, 0x40, 0x29, 0xbe, 0x0e, 0x28, 0x21, 0xa5, 0xa8,
0x04, 0x4a, 0x50, 0x07, 0xb9, 0x07, 0xe3, 0xa0, 0x18, 0xc5, 0x36, 0x1c,
0x30, 0xc7, 0x5d, 0x76, 0x26, 0x0f, 0xbc, 0x96, 0xac, 0xac, 0x14, 0x45,
0xa4, 0xc3, 0x87, 0x83, 0x88, 0xa6, 0x57, 0xfa, 0x17, 0x91, 0xae, 0xf5,
0x4c, 0x6b, 0xef, 0x1e, 0x00, 0x98, 0x79, 0x35, 0x48, 0xef, 0x7d, 0xa7,
0xf9, 0x10, 0x02, 0xc6, 0x71, 0x5c, 0xf0, 0xce, 0xb9, 0x09, 0xc0, 0x62,
0x7d, 0xcd, 0x5e, 0xfb, 0x01, 0x80, 0xf3, 0xf9, 0x6c, 0xf2, 0x35, 0xfb,
0xe4, 0x43, 0x44, 0xe0, 0xbd, 0x37, 0x6d, 0x99, 0x39, 0xf9, 0x68, 0xe2,
0x87, 0x61, 0x00, 0x00, 0x9c, 0x4e, 0xa7, 0x05, 0xaf, 0x41, 0x44, 0x53,
0xc1, 0xdf, 0x94, 0x8b, 0xbc, 0xd7, 0xc1, 0x02, 0xb6, 0x48, 0x7a, 0xcf,
0x38, 0x8e, 0x37, 0x22, 0x27, 0x3b, 0x6b, 0xbd, 0xe6, 0xc7, 0xe2, 0x6a,
0x76, 0xfa, 0xcc, 0xc3, 0xe1, 0x00, 0xe0, 0x22, 0x0e, 0x11, 0x01, 0x00,
0x44, 0x64, 0x16, 0x29, 0xa1, 0xc6, 0x13, 0xd1, 0xec, 0x9b, 0x88, 0x20,
0x22, 0x37, 0xe7, 0x97, 0xc4, 0x05, 0x2e, 0x89, 0xca, 0x45, 0xde, 0xe9,
0xc0, 0xf3, 0x9b, 0x2e, 0xdd, 0xac, 0x85, 0x35, 0xd1, 0x34, 0x5a, 0x92,
0x90, 0x62, 0xb1, 0x92, 0x58, 0x8a, 0xab, 0x0f, 0x01, 0x7d, 0x29, 0x41,
0x7d, 0x40, 0xe8, 0x6d, 0x5e, 0x44, 0x10, 0x42, 0x98, 0x9f, 0x8a, 0x1c,
0xb9, 0xb8, 0x44, 0xb4, 0xb8, 0x12, 0xbc, 0xf7, 0x8b, 0x16, 0xb2, 0x47,
0x23, 0xb4, 0x30, 0xa5, 0x6a, 0x2d, 0x71, 0xad, 0x15, 0x5e, 0xc3, 0x9a,
0x8f, 0xaf, 0x18, 0x01, 0x00, 0x64, 0xb2, 0x40, 0xfc, 0xba, 0xf0, 0xd6,
0x86, 0x52, 0x05, 0x5b, 0xe2, 0xe6, 0x49, 0xd0, 0x6b, 0xba, 0x92, 0x77,
0x79, 0xc0, 0x49, 0xa0, 0x9f, 0x88, 0x61, 0x55, 0x72, 0xc9, 0x5f, 0x3a,
0x6f, 0xad, 0x32, 0x5b, 0x9f, 0x2a, 0xe7, 0x1c, 0x9c, 0x73, 0x0f, 0xf1,
0x6b, 0x15, 0xdc, 0x22, 0x6e, 0xb2, 0xb7, 0x2a, 0xd9, 0xac, 0xe0, 0x5c,
0x88, 0x52, 0xcf, 0x5d, 0x83, 0xe6, 0x6b, 0xc9, 0x6a, 0x69, 0x19, 0xba,
0x8d, 0x59, 0x3e, 0xf3, 0x9e, 0x9a, 0xa3, 0xc6, 0x37, 0xf6, 0x60, 0x73,
0x3d, 0x21, 0x89, 0xac, 0xf7, 0xdc, 0x8c, 0x15, 0x9f, 0x86, 0x57, 0x8f,
0x69, 0xc0, 0x75, 0xca, 0xa8, 0x09, 0x9c, 0xef, 0x61, 0xe6, 0xf6, 0x1e,
0xfc, 0x5b, 0x61, 0xcd, 0x9e, 0xcf, 0x44, 0x29, 0x81, 0xc3, 0x30, 0x54,
0x9f, 0x8c, 0xdd, 0xb3, 0x03, 0xfa, 0x2f, 0x68, 0x11, 0x17, 0xb8, 0x63,
0x8a, 0xd8, 0x70, 0xed, 0xb1, 0x21, 0x04, 0x53, 0x5c, 0xab, 0x85, 0x6c,
0x15, 0x5c, 0x81, 0x88, 0x74, 0xfa, 0x1f, 0xdd, 0xda, 0x0b, 0x3b, 0x17,
0x97, 0x99, 0x21, 0x22, 0xdd, 0xc7, 0xbf, 0xe4, 0xde, 0x01, 0xe7, 0xdc,
0x24, 0x22, 0x88, 0x31, 0xce, 0x95, 0xab, 0x47, 0x32, 0x00, 0xa6, 0xb8,
0xc0, 0x56, 0xc1, 0xcd, 0x88, 0x31, 0xe2, 0x78, 0x3c, 0xce, 0xbf, 0x45,
0x64, 0x71, 0x25, 0x68, 0x71, 0x81, 0x3f, 0x30, 0xa6, 0xbd, 0x03, 0xcc,
0x3c, 0xa5, 0x99, 0xbe, 0xf6, 0x71, 0x29, 0x9f, 0x6a, 0x36, 0x81, 0x1f,
0xc0, 0x3d, 0x9f, 0x2b, 0xbf, 0x01, 0xc8, 0x27, 0x72, 0x5f, 0xca, 0xe5,
0xdb, 0x35, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42,
0x60, 0x82
};
unsigned int sprites_png_len = 542;

View File

@@ -2,6 +2,7 @@
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
@@ -9,26 +10,231 @@
void synthInit(Synth *s, float sampleRate)
{
s->sampleRate = sampleRate;
s->attackSec = 0.005f;
s->releaseSec = 0.050f;
s->dutyCycle = 0.5f;
s->waveform = WAVE_SINE;
s->lastStolenVoice = 0;
s->sampleRate = sampleRate;
s->pitchBend = 0.0f;
s->pitchBendRange = 2.0f;
s->lastStolenVoice = 0;
s->volume = 0.8f;
for (int i = 0; i < VOICE_COUNT; i++) {
s->voices[i].phase = 0.0f;
s->voices[i].freqHz = 440.0f;
s->voices[i].amp = 0.0f;
s->voices[i].targetAmp = 0.0f;
s->voices[i].active = 0;
s->voices[i].midiNote = -1;
s->pitchBend = 0.0f;
s->pitchBendRange = 2.0f;
s->voices[i].freqHz = 440.0f;
s->voices[i].active = 0;
s->voices[i].midiNote = -1;
oscillatorInit(&s->voices[i].oscillators[0], WAVE_TRIANGLE, 0.5f, 0.0f, OSC_MAX_GAIN);
oscillatorInit(&s->voices[i].oscillators[1], WAVE_TRIANGLE, 0.5f, 0.0f, 0.0f);
envelopeInit(&s->voices[i].ampEnv,
0.005f, // attack
0.10f, // decay
0.70f, // sustain
0.50f); // release
envelopeInit(&s->voices[i].modEnv,
0.005f, // attack
0.50f, // decay
0.0f, // sustain
0.10f); // release
s->voices[i].filter.cutoff = 8000.0f;
s->voices[i].filter.resonance = 0.0f;
s->voices[i].filter.type = FILTER_LOWPASS;
s->voices[i].filter.active = 0;
s->voices[i].filter.low = 0.0f;
s->voices[i].filter.band = 0.0f;
s->voices[i].filter.modRouting = MOD_SOURCE_NONE;
s->voices[i].filter.modDepth = 0.0f;
s->voices[i].filter.resModRouting = MOD_SOURCE_NONE;
s->voices[i].filter.resModDepth = 0.0f;
}
s->voices[0].oscillators[0].active = 1;
for (int l = 0; l < LFO_COUNT; l++) {
s->lfos[l].phase = 0.0f;
s->lfos[l].rate = 1.0f;
s->lfos[l].waveform = WAVE_SINE;
s->lfos[l].active = 0;
s->lfos[l].noiseHeld = 0.0f;
s->lfos[l].noisePhase = 0.0f;
}
}
float waveformSample(Waveform w, float phase, float dutyCycle)
void synthResetPatch(Synth *s)
{
Voice *v = &s->voices[0];
oscillatorInit(&v->oscillators[0], WAVE_TRIANGLE, 0.5f, 0.0f, OSC_MAX_GAIN);
v->oscillators[0].active = 1;
for (int m = 0; m < 3; m++) {
v->oscillators[0].modRouting[m] = MOD_SOURCE_NONE;
v->oscillators[0].modDepth[m] = 0.0f;
}
oscillatorInit(&v->oscillators[1], WAVE_TRIANGLE, 0.5f, 0.0f, 0.0f);
v->oscillators[1].active = 0;
for (int m = 0; m < 3; m++) {
v->oscillators[1].modRouting[m] = MOD_SOURCE_NONE;
v->oscillators[1].modDepth[m] = 0.0f;
}
envelopeInit(&v->ampEnv, 0.005f, 0.10f, 0.70f, 0.50f);
envelopeInit(&v->modEnv, 0.005f, 0.50f, 0.0f, 0.10f);
v->filter.cutoff = 8000.0f;
v->filter.resonance = 0.0f;
v->filter.type = FILTER_LOWPASS;
v->filter.active = 0;
v->filter.low = 0.0f;
v->filter.band = 0.0f;
v->filter.modRouting = MOD_SOURCE_NONE;
v->filter.modDepth = 0.0f;
v->filter.resModRouting = MOD_SOURCE_NONE;
v->filter.resModDepth = 0.0f;
for (int l = 0; l < LFO_COUNT; l++) {
s->lfos[l].phase = 0.0f;
s->lfos[l].rate = 1.0f;
s->lfos[l].waveform = WAVE_SINE;
s->lfos[l].active = 0;
s->lfos[l].noiseHeld = 0.0f;
s->lfos[l].noisePhase = 0.0f;
}
s->volume = 0.8f;
s->pitchBendRange = 2.0f;
}
void oscillatorInit(Oscillator *o, Waveform waveform, float dutyCycle, float detune, float gain)
{
o->phase = 0.0f;
o->waveform = waveform;
o->dutyCycle = dutyCycle;
o->detune = detune;
o->noiseHeld = 0.0f;
o->noisePhase = 0.0f;
o->gain = gain;
o->octave = 0;
}
static float getModValue(float ampEnv, float modEnv, float lfo0, float lfo1, ModSource source)
{
switch (source) {
case MOD_SOURCE_AMP_ENV: return ampEnv;
case MOD_SOURCE_MOD_ENV: return modEnv;
case MOD_SOURCE_LFO: return lfo0;
case MOD_SOURCE_LFO2: return lfo1;
default: return 0.0f;
}
}
float lfoTick(LFO *l, float sampleRate)
{
if (!l->active) return 0.0f;
l->phase += l->rate / sampleRate;
if (l->phase >= 1.0f) l->phase -= 1.0f;
if (l->waveform == WAVE_NOISE) {
l->noisePhase += l->rate / sampleRate;
if (l->noisePhase >= 1.0f) {
l->noisePhase -= 1.0f;
l->noiseHeld = ((float)rand() / (float)RAND_MAX) * 2.0f - 1.0f;
}
}
return waveformSample(l->waveform, l->phase, 0.5f, l->noiseHeld);
}
float filterTick(Filter *f, float input, float cutoff, float resonance, float sampleRate)
{
if (!f->active) return input;
if (cutoff < 20.0f) cutoff = 20.0f;
if (cutoff > sampleRate * 0.499f) cutoff = sampleRate * 0.499f;
if (resonance < 0.0f) resonance = 0.0f;
if (resonance > 0.99f) resonance = 0.99f;
// Andy Simper TPT SVF (bilinear integration — unconditionally stable)
float g = tanf((float)M_PI * cutoff / sampleRate);
float Q = 0.5f + resonance * 9.5f; // resonance 0..0.99 → Q 0.5..10.0
float k = 1.0f / Q;
float a1 = 1.0f / (1.0f + g * (g + k));
float a2 = g * a1;
float a3 = g * a2;
// f->band = s1, f->low = s2 (integrator states)
float v3 = input - f->low;
float v1 = a1 * f->band + a2 * v3;
float v2 = f->low + a2 * f->band + a3 * v3;
f->band = 2.0f * v1 - f->band;
f->low = 2.0f * v2 - f->low;
switch (f->type) {
case FILTER_LOWPASS: return v2;
case FILTER_HIGHPASS: return input - k * v1 - v2;
case FILTER_BANDPASS: return v1;
default: return v2;
}
}
const char *filterTypeName(FilterType t)
{
switch (t) {
case FILTER_LOWPASS: return "LP";
case FILTER_HIGHPASS: return "HP";
case FILTER_BANDPASS: return "BP";
default: return "??";
}
}
// float oscillatorTick(Oscillator *o, float freqHz, float bendMultiplier, float sampleRate,
// float dutyCycle, float detune, float gain)
// {
// float detuneMultiplier = powf(2.0f, o->detune / 1200.0f);
// float freq = freqHz * detuneMultiplier * bendMultiplier;
//
// // Advance phase
// o->phase += freq / sampleRate;
// if (o->phase >= 1.0f) o->phase -= 1.0f;
//
// // Clocked noise — draw a new random value once per cycle
// if (o->waveform == WAVE_NOISE) {
// o->noisePhase += freq / sampleRate;
// if (o->noisePhase >= 1.0f) {
// o->noisePhase -= 1.0f;
// o->noiseHeld = ((float)rand() / (float)RAND_MAX) * 2.0f - 1.0f;
// }
// }
//
// return waveformSample(o->waveform, o->phase, o->dutyCycle, o->noiseHeld) * o->gain;
// }
float oscillatorTick(Oscillator *o, float freqHz, float bendMultiplier, float sampleRate,
float dutyCycle, float detune, float gain)
{
float detuneMultiplier = powf(2.0f, detune / 1200.0f);
float freq = freqHz * detuneMultiplier * bendMultiplier;
// Advance phase
o->phase += freq / sampleRate;
if (o->phase >= 1.0f) o->phase -= 1.0f;
// Clocked noise — draw a new random value once per cycle
if (o->waveform == WAVE_NOISE) {
o->noisePhase += freq / sampleRate;
if (o->noisePhase >= 1.0f) {
o->noisePhase -= 1.0f;
o->noiseHeld = ((float)rand() / (float)RAND_MAX) * 2.0f - 1.0f;
}
}
return waveformSample(o->waveform, o->phase, dutyCycle, o->noiseHeld) * gain;
}
float waveformSample(Waveform w, float phase, float dutyCycle, float noiseHeld)
{
switch (w) {
case WAVE_SINE:
@@ -44,7 +250,7 @@ float waveformSample(Waveform w, float phase, float dutyCycle)
case WAVE_PULSE:
return (phase < dutyCycle) ? 1.0f : -1.0f;
case WAVE_NOISE:
return ((float)rand() / (float)RAND_MAX) * 2.0f - 1.0f;
return noiseHeld;
default:
return 0.0f;
}
@@ -69,67 +275,95 @@ void synthNoteOn(Synth *s, int midiNote)
for (int i = 0; i < VOICE_COUNT; i++) {
if (!s->voices[i].active) {
s->voices[i].freqHz = hz;
s->voices[i].midiNote = midiNote;
s->voices[i].targetAmp = 1.0f;
s->voices[i].active = 1;
s->voices[i].freqHz = hz;
s->voices[i].midiNote = midiNote;
s->voices[i].active = 1;
s->voices[i].filter.low = 0.0f;
s->voices[i].filter.band = 0.0f;
envelopeNoteOn(&s->voices[i].ampEnv);
envelopeNoteOn(&s->voices[i].modEnv);
return;
}
}
// No free voice — steal round-robin
// Steal round-robin
int i = s->lastStolenVoice % VOICE_COUNT;
s->lastStolenVoice++;
s->voices[i].freqHz = hz;
s->voices[i].midiNote = midiNote;
s->voices[i].targetAmp = 1.0f;
s->voices[i].phase = 0.0f;
s->voices[i].active = 1;
s->voices[i].freqHz = hz;
s->voices[i].midiNote = midiNote;
s->voices[i].active = 1;
s->voices[i].filter.low = 0.0f;
s->voices[i].filter.band = 0.0f;
envelopeNoteOn(&s->voices[i].ampEnv);
envelopeNoteOn(&s->voices[i].modEnv);
}
void synthNoteOff(Synth *s, int midiNote)
{
void synthNoteOff(Synth *s, int midiNote) {
for (int i = 0; i < VOICE_COUNT; i++) {
if (s->voices[i].active && s->voices[i].midiNote == midiNote)
s->voices[i].targetAmp = 0.0f;
if (s->voices[i].active && s->voices[i].midiNote == midiNote) {
envelopeNoteOff(&s->voices[i].ampEnv);
envelopeNoteOff(&s->voices[i].modEnv);
}
}
}
void synthFillBuffer(Synth *s, int16_t *out, int frames)
{
const float sr = s->sampleRate;
const float attackInc = (s->attackSec <= 0.0f) ? 1.0f : (1.0f / (s->attackSec * sr));
const float releaseInc = (s->releaseSec <= 0.0f) ? 1.0f : (1.0f / (s->releaseSec * sr));
void synthFillBuffer(Synth *s, int16_t *out, int frames) {
const float sr = s->sampleRate;
const float bendMultiplier = powf(2.0f, (s->pitchBend * s->pitchBendRange) / 12.0f);
for (int i = 0; i < frames; i++) {
float mix = 0.0f;
float lfo0 = lfoTick(&s->lfos[0], sr);
float lfo1 = lfoTick(&s->lfos[1], sr);
for (int v = 0; v < VOICE_COUNT; v++) {
Voice *vv = &s->voices[v];
if (!vv->active) continue;
if (vv->targetAmp > vv->amp) {
vv->amp += attackInc;
if (vv->amp > vv->targetAmp) vv->amp = vv->targetAmp;
} else if (vv->targetAmp < vv->amp) {
vv->amp -= releaseInc;
if (vv->amp < vv->targetAmp) vv->amp = vv->targetAmp;
}
float amp = envelopeTick(&vv->ampEnv, sr);
float mod = envelopeTick(&vv->modEnv, sr);
if (vv->amp <= 0.0f && vv->targetAmp == 0.0f) {
if (vv->ampEnv.stage == ENV_IDLE) {
vv->active = 0;
continue;
}
//vv->phase += vv->freqHz / sr;
float bendMultiplier = powf(2.0f, (s->pitchBend * s->pitchBendRange) / 12.0f);
vv->phase += (vv->freqHz * bendMultiplier) / sr;
if (vv->phase >= 1.0f) vv->phase -= 1.0f;
float oscMix = 0.0f;
int activeOscs = 0;
for (int o = 0; o < OSC_COUNT; o++) {
Oscillator *osc = &vv->oscillators[o];
if (!osc->active) continue;
mix += waveformSample(s->waveform, vv->phase, s->dutyCycle) * vv->amp;
float dutyCycle = osc->dutyCycle +
getModValue(amp, mod, lfo0, lfo1, osc->modRouting[0]) * osc->modDepth[0];
float detune = osc->detune + (float)osc->octave * 1200.0f +
getModValue(amp, mod, lfo0, lfo1, osc->modRouting[1]) * osc->modDepth[1];
float gain = osc->gain +
getModValue(amp, mod, lfo0, lfo1, osc->modRouting[2]) * osc->modDepth[2];
if (dutyCycle < 0.05f) dutyCycle = 0.05f;
if (dutyCycle > 0.95f) dutyCycle = 0.95f;
if (gain < 0.0f) gain = 0.0f;
if (gain > OSC_MAX_GAIN) gain = OSC_MAX_GAIN;
oscMix += oscillatorTick(osc, vv->freqHz, bendMultiplier, sr, dutyCycle, detune, gain);
activeOscs++;
}
if (activeOscs > 0) oscMix /= activeOscs;
float cutoff = vv->filter.cutoff +
getModValue(amp, mod, lfo0, lfo1, vv->filter.modRouting) * vv->filter.modDepth;
float resonance = vv->filter.resonance +
getModValue(amp, mod, lfo0, lfo1, vv->filter.resModRouting) * vv->filter.resModDepth;
oscMix = filterTick(&vv->filter, oscMix, cutoff, resonance, sr);
mix += oscMix * amp;
}
mix *= (0.2f / VOICE_COUNT) * 4.0f;
mix *= (0.2f / VOICE_COUNT) * 4.0f * s->volume;
int32_t sample = (int32_t)lrintf(mix * 32767.0f);
if (sample > 32767) sample = 32767;
@@ -137,3 +371,109 @@ void synthFillBuffer(Synth *s, int16_t *out, int frames)
out[i] = (int16_t)sample;
}
}
void envelopeInit(Envelope *e, float attackSec, float decaySec, float sustainLevel, float releaseSec)
{
e->stage = ENV_IDLE;
e->value = 0.0f;
e->attackSec = attackSec;
e->decaySec = decaySec;
e->sustainLevel = sustainLevel;
e->releaseSec = releaseSec;
}
void envelopeNoteOn(Envelope *e)
{
e->value = 0.0f;
e->stage = ENV_ATTACK;
}
void envelopeNoteOff(Envelope *e)
{
// Only trigger release if we're actually playing
if (e->stage != ENV_IDLE)
e->stage = ENV_RELEASE;
}
float envelopeTick(Envelope *e, float sampleRate)
{
switch (e->stage) {
case ENV_ATTACK: {
float inc = (e->attackSec <= 0.0f) ? 1.0f : (1.0f / (e->attackSec * sampleRate));
e->value += inc;
if (e->value >= 1.0f) {
e->value = 1.0f;
e->stage = ENV_DECAY;
}
break;
}
case ENV_DECAY: {
float inc = (e->decaySec <= 0.0f) ? 1.0f : (1.0f / (e->decaySec * sampleRate));
e->value -= inc;
if (e->value <= e->sustainLevel) {
e->value = e->sustainLevel;
e->stage = ENV_SUSTAIN;
}
break;
}
case ENV_SUSTAIN:
e->value = e->sustainLevel;
break;
case ENV_RELEASE: {
float inc = (e->releaseSec <= 0.0f) ? 1.0f : (1.0f / (e->releaseSec * sampleRate));
e->value -= inc;
if (e->value <= 0.0f) {
e->value = 0.0f;
e->stage = ENV_IDLE;
}
break;
}
case ENV_IDLE:
e->value = 0.0f;
break;
}
return e->value;
}
void synthSyncVoices(Synth *s)
{
for (int v = 1; v < VOICE_COUNT; v++) {
// Sync oscillator settings
for (int o = 0; o < OSC_COUNT; o++) {
s->voices[v].oscillators[o].waveform = s->voices[0].oscillators[o].waveform;
s->voices[v].oscillators[o].dutyCycle = s->voices[0].oscillators[o].dutyCycle;
s->voices[v].oscillators[o].detune = s->voices[0].oscillators[o].detune;
s->voices[v].oscillators[o].gain = s->voices[0].oscillators[o].gain;
s->voices[v].oscillators[o].active = s->voices[0].oscillators[o].active;
s->voices[v].oscillators[o].octave = s->voices[0].oscillators[o].octave;
s->voices[v].oscillators[o].modRouting[0] = s->voices[0].oscillators[o].modRouting[0];
s->voices[v].oscillators[o].modRouting[1] = s->voices[0].oscillators[o].modRouting[1];
s->voices[v].oscillators[o].modRouting[2] = s->voices[0].oscillators[o].modRouting[2];
s->voices[v].oscillators[o].modDepth[0] = s->voices[0].oscillators[o].modDepth[0];
s->voices[v].oscillators[o].modDepth[1] = s->voices[0].oscillators[o].modDepth[1];
s->voices[v].oscillators[o].modDepth[2] = s->voices[0].oscillators[o].modDepth[2];
}
// Sync filter params but not state (low/band are per-voice)
s->voices[v].filter.cutoff = s->voices[0].filter.cutoff;
s->voices[v].filter.resonance = s->voices[0].filter.resonance;
s->voices[v].filter.type = s->voices[0].filter.type;
s->voices[v].filter.active = s->voices[0].filter.active;
s->voices[v].filter.modRouting = s->voices[0].filter.modRouting;
s->voices[v].filter.modDepth = s->voices[0].filter.modDepth;
s->voices[v].filter.resModRouting = s->voices[0].filter.resModRouting;
s->voices[v].filter.resModDepth = s->voices[0].filter.resModDepth;
// Sync envelope settings but NOT runtime state
// Each voice needs its own stage, value — just copy the parameters
s->voices[v].ampEnv.attackSec = s->voices[0].ampEnv.attackSec;
s->voices[v].ampEnv.decaySec = s->voices[0].ampEnv.decaySec;
s->voices[v].ampEnv.sustainLevel = s->voices[0].ampEnv.sustainLevel;
s->voices[v].ampEnv.releaseSec = s->voices[0].ampEnv.releaseSec;
// Sync the mod envelope, too.
s->voices[v].modEnv.attackSec = s->voices[0].modEnv.attackSec;
s->voices[v].modEnv.decaySec = s->voices[0].modEnv.decaySec;
s->voices[v].modEnv.sustainLevel = s->voices[0].modEnv.sustainLevel;
s->voices[v].modEnv.releaseSec = s->voices[0].modEnv.releaseSec;
}
}

1719
source/ui.c Normal file

File diff suppressed because it is too large Load Diff