Files
soundThing/source/soundThing.c
Anachronaut 75e0894ff8 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>
2026-07-07 19:18:34 -04:00

204 lines
6.5 KiB
C

#define _POSIX_C_SOURCE 200809L
#include "raylib.h"
#include <stdio.h>
#include <string.h>
#include <stdatomic.h>
#include <pthread.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);
}
int main(void)
{
chdirToExeDir();
const int sampleRate = 48000;
if (!midiInit(&gMidi, &gSynth)) return 1;
midiScanInputs(&gMidi);
pthread_t midiTid;
pthread_create(&midiTid, NULL, midiThread, &gMidi);
synthInit(&gSynth, (float)sampleRate);
// 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);
AudioStream stream = LoadAudioStream(sampleRate, 16, 1);
SetAudioStreamCallback(stream, AudioOutputCallback);
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()) {
// Sync voice settings from voice 0
synthSyncVoices(&gSynth);
BeginDrawing();
BeginMode2D(ui_cam);
if(IsWindowResized()) {
recomputeViewPort(&ui_cam, LOGICAL_WIDTH, LOGICAL_HEIGHT, BORDER_WIDTH);
}
ClearBackground((Color){ 189, 168, 131, 255 }); // tan background
// // 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);
// 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();
pthread_join(midiTid, NULL);
uiClose(&gUI);
CloseWindow();
midiClose(&gMidi);
return 0;
}