230 lines
7.1 KiB
C
230 lines
7.1 KiB
C
#ifndef DISPATCHER_H
|
|
#define DISPATCHER_H
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdatomic.h>
|
|
#include <jack/jack.h>
|
|
#include <pthread.h>
|
|
#include "carla.h"
|
|
|
|
// ============================================================
|
|
// Application State - contains ALL application state
|
|
// ============================================================
|
|
|
|
#define MAX_SCENES 8
|
|
#define MAX_CHANNELS 8
|
|
#define MAX_CLIPS 512 // 8 grids * 8 scenes * 8 channels
|
|
#define GRID_ROWS MAX_SCENES
|
|
#define GRID_COLS MAX_CHANNELS
|
|
#define MAX_BUFFER_SIZE 441000 // 10 seconds at 44.1kHz
|
|
#define MAX_UNDO_HISTORY 256
|
|
|
|
#define CLIP_INDEX(scene, channel) ((scene) * MAX_CHANNELS + (channel))
|
|
|
|
typedef enum {
|
|
CLIP_EMPTY,
|
|
CLIP_RECORDING,
|
|
CLIP_LOOPING,
|
|
CLIP_STOPPED
|
|
} ClipState;
|
|
|
|
typedef enum {
|
|
QUANTIZE_OFF,
|
|
QUANTIZE_BEAT,
|
|
QUANTIZE_BAR
|
|
} QuantizeMode;
|
|
|
|
typedef enum {
|
|
TRANSPORT_STOPPED,
|
|
TRANSPORT_PLAYING,
|
|
TRANSPORT_PAUSED
|
|
} TransportState;
|
|
|
|
typedef enum {
|
|
CLOCK_SOURCE_INTERNAL,
|
|
CLOCK_SOURCE_MIDI
|
|
} ClockSource;
|
|
|
|
typedef struct {
|
|
uint8_t note;
|
|
uint8_t velocity;
|
|
uint32_t timestamp; // sample offset within the clip
|
|
} MidiEvent;
|
|
|
|
#define MAX_MIDI_EVENTS 44100 // ~1 second of dense MIDI data
|
|
|
|
typedef struct {
|
|
ClipState state;
|
|
MidiEvent *events; // Dynamically allocated
|
|
int event_count;
|
|
int max_events; // Allocated size
|
|
int read_index;
|
|
char channel_name[64]; // per-channel name for MIDI grid
|
|
} MidiClip;
|
|
|
|
typedef struct {
|
|
ClipState state;
|
|
float *buffer;
|
|
size_t buffer_size;
|
|
size_t write_position;
|
|
size_t read_position;
|
|
} Clip;
|
|
|
|
typedef struct {
|
|
// Transport state
|
|
TransportState transport_state;
|
|
ClockSource clock_source;
|
|
uint32_t clock_count;
|
|
uint32_t beat_position;
|
|
uint32_t bar_position;
|
|
uint32_t sample_position;
|
|
double bpm;
|
|
double samples_per_beat;
|
|
double sample_accumulator;
|
|
|
|
// Quantization
|
|
QuantizeMode quantize_mode;
|
|
jack_nframes_t quantize_threshold;
|
|
|
|
// Clips
|
|
Clip clips[MAX_CLIPS];
|
|
|
|
// MIDI clips (separate grid)
|
|
MidiClip midi_clips[MAX_CLIPS];
|
|
bool show_midi_grid; // View toggle: true = MIDI grid, false = Audio grid
|
|
char channel_names[MAX_CHANNELS][64]; // Channel names for audio grid
|
|
|
|
// Undo history
|
|
struct {
|
|
int undo_index;
|
|
int redo_index;
|
|
int count;
|
|
ClipState prev_clip_states[MAX_UNDO_HISTORY];
|
|
int prev_clip_indices[MAX_UNDO_HISTORY];
|
|
size_t prev_buffer_sizes[MAX_UNDO_HISTORY];
|
|
size_t prev_write_positions[MAX_UNDO_HISTORY];
|
|
size_t prev_read_positions[MAX_UNDO_HISTORY];
|
|
int batch_sizes[MAX_UNDO_HISTORY]; // NEW: track batch sizes
|
|
int current_batch_size; // NEW: current batch being recorded
|
|
} undo;
|
|
|
|
// Ring buffers for real-time recording (written by JACK callback, read by dispatcher)
|
|
// Lock-free: single producer (JACK callback), single consumer (dispatcher thread)
|
|
float record_buffer[MAX_CHANNELS][MAX_BUFFER_SIZE];
|
|
atomic_size_t record_write_pos[MAX_CHANNELS]; // Only written by JACK callback
|
|
atomic_size_t record_read_pos[MAX_CHANNELS]; // Only written by dispatcher thread
|
|
|
|
// Carla host
|
|
CarlaHost carla_host;
|
|
|
|
// JACK info (needed by audio thread)
|
|
jack_nframes_t sample_rate;
|
|
bool running;
|
|
} AppState;
|
|
|
|
// ============================================================
|
|
// Actions
|
|
// ============================================================
|
|
|
|
typedef enum {
|
|
ACTION_TRIGGER_CLIP,
|
|
ACTION_TRIGGER_SCENE,
|
|
ACTION_RESET_CLIP,
|
|
ACTION_SET_QUANTIZE_MODE,
|
|
ACTION_SET_QUANTIZE_THRESHOLD,
|
|
ACTION_TRANSPORT_PLAY,
|
|
ACTION_TRANSPORT_PAUSE,
|
|
ACTION_TRANSPORT_STOP,
|
|
ACTION_TRANSPORT_TOGGLE_PLAY,
|
|
ACTION_SET_CLOCK_SOURCE,
|
|
ACTION_SET_BPM,
|
|
ACTION_RESET_TRANSPORT,
|
|
ACTION_UNDO,
|
|
ACTION_REDO,
|
|
ACTION_SAVE_CLIP,
|
|
ACTION_LOAD_CLIP,
|
|
ACTION_MIDI_NOTE_ON,
|
|
ACTION_MIDI_SCENE_LAUNCH,
|
|
ACTION_RACK_ADD_PLUGIN,
|
|
ACTION_RACK_REMOVE_PLUGIN,
|
|
ACTION_RACK_SET_PARAMETER,
|
|
ACTION_RACK_SET_VOLUME,
|
|
ACTION_RACK_BYPASS,
|
|
ACTION_SAVE_PROJECT,
|
|
ACTION_LOAD_PROJECT,
|
|
ACTION_PROCESS_AUDIO,
|
|
ACTION_QUIT,
|
|
ACTION_SET_SHOW_MIDI_GRID,
|
|
ACTION_MIDI_CLIP_TRIGGER,
|
|
ACTION_MIDI_CLIP_RESET,
|
|
ACTION_SET_CHANNEL_NAME
|
|
} ActionType;
|
|
|
|
typedef struct {
|
|
ActionType type;
|
|
union {
|
|
struct { int clip_index; } trigger_clip;
|
|
struct { int scene_index; } trigger_scene;
|
|
struct { int clip_index; } reset_clip;
|
|
struct { QuantizeMode mode; } set_quantize_mode;
|
|
struct { jack_nframes_t threshold; } set_quantize_threshold;
|
|
struct { ClockSource source; } set_clock_source;
|
|
struct { double bpm; } set_bpm;
|
|
struct { int clip_index; } save_clip;
|
|
struct { int clip_index; char filename[256]; } load_clip;
|
|
struct { int note; int velocity; int channel; jack_nframes_t time; } midi_note_on;
|
|
struct { int scene_index; jack_nframes_t time; } midi_scene_launch;
|
|
struct { int channel; char uri[512]; PluginType type; } rack_add_plugin;
|
|
struct { int channel; int plugin_index; } rack_remove_plugin;
|
|
struct { int channel; int plugin_index; int param_index; float value; } rack_set_parameter;
|
|
struct { int channel; float volume; } rack_set_volume;
|
|
struct { int channel; bool bypass; } rack_bypass;
|
|
struct { char filename[512]; } save_project;
|
|
struct { char filename[512]; } load_project;
|
|
struct { jack_nframes_t nframes; } process_audio;
|
|
struct { bool show; } set_show_midi_grid;
|
|
struct { int clip_index; } midi_clip_trigger;
|
|
struct { int clip_index; } midi_clip_reset;
|
|
struct { int channel; char name[64]; } set_channel_name;
|
|
} data;
|
|
} Action;
|
|
|
|
// ============================================================
|
|
// Dispatcher API
|
|
// ============================================================
|
|
|
|
// Thread-safe dispatch function - called by any thread
|
|
typedef void (*DispatchFn)(Action action);
|
|
|
|
// Subscriber callback - called after each state update (from dispatcher thread)
|
|
typedef void (*SubscriberFn)(AppState *state, void *user_data);
|
|
|
|
// Initialize the dispatcher with initial state
|
|
// Returns the dispatch function that consumers can call
|
|
DispatchFn dispatcher_init(AppState *initial_state);
|
|
|
|
// Register a subscriber (call before dispatcher_start)
|
|
void dispatcher_subscribe(SubscriberFn fn, void *user_data);
|
|
|
|
// Start the dispatcher thread
|
|
void dispatcher_start(void);
|
|
|
|
// Stop the dispatcher and cleanup
|
|
void dispatcher_stop(void);
|
|
|
|
// Get current state (thread-safe snapshot)
|
|
void dispatcher_get_state(AppState *out);
|
|
|
|
// Get pointer to dispatcher's state (for real-time threads that can't block)
|
|
// WARNING: This is NOT thread-safe. Only use in JACK process callback
|
|
// which is serialized per client.
|
|
AppState* dispatcher_get_state_ptr(void);
|
|
|
|
// ============================================================
|
|
// Reducer - pure function (takes pointer to avoid stack copy)
|
|
// ============================================================
|
|
void reducer(AppState *state, Action action);
|
|
|
|
#endif // DISPATCHER_H
|