summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--b.c37
-rw-r--r--src/gui.h8
-rw-r--r--src/osc.c51
-rw-r--r--src/osc_sound.c4
-rw-r--r--src/synth_engine.h1
-rw-r--r--src/synth_engine_v2.c116
-rw-r--r--src/synth_gui.c280
-rw-r--r--src/web.c74
-rw-r--r--tests/example_test.c70
-rw-r--r--tmpl/index.html55
10 files changed, 561 insertions, 135 deletions
diff --git a/b.c b/b.c
index 6ac5db6..f8c56a2 100644
--- a/b.c
+++ b/b.c
@@ -6,6 +6,8 @@
#define TEMPLATE_DIR BUILD_DIR"templates/"
int debug_level = 0;
+bool force = false;
+bool tests = false;
void
debug_or_release(B_Cmd* cmd)
@@ -56,8 +58,6 @@ void cc(B_Cmd *cmd)
cflags_common(cmd);
}
-bool force = false;
-
bool
build_c(B_Cmd* cmd,
const char** input_paths,
@@ -115,6 +115,34 @@ bool build_templates(B_Cmd *cmd, const char **templates, size_t len) {
return true;
}
+bool
+build_tests(B_Cmd *cmd, const char * output_path) {
+ int rebuild_is_needed =
+ b_needs_rebuild1(B_COMPILE, output_path, "tests/example_test.c");
+
+ /* int dep_rebuild = 0; */
+ /* if (rebuild_is_needed == 0) */
+ /* dep_rebuild = */
+ /* b_needs_rebuild(B_COMPILE, output_path, dep_paths, dep_paths_len); */
+
+
+ if (rebuild_is_needed < 0 /* || dep_rebuild < 0 */) return false;
+
+
+ if (force || rebuild_is_needed /* || dep_rebuild */) {
+ cmd->count = 0;
+ cc(cmd);
+ b_cmd_append(cmd, "-o", output_path);
+ b_cmd_append(cmd, "-l", "check");
+ b_cmd_append(cmd, "tests/example_test.c");
+ synth_libs(cmd);
+ return b_cmd_run_sync(*cmd);
+ }
+
+ b_log(B_INFO, "%s is up-to-date", output_path);
+ return true;
+}
+
int
main(int argc, char *argv[])
{
@@ -127,6 +155,8 @@ main(int argc, char *argv[])
const char *flag = b_shift_args(&argc, &argv);
if (strcmp(flag, "-f") == 0) {
force = true;
+ } else if (strcmp(flag, "-t") == 0) {
+ tests = true;
} else {
b_log(B_ERROR, "Unknown flag `%s`", flag);
return 1;
@@ -203,5 +233,8 @@ main(int argc, char *argv[])
B_ARRAY_LEN(synth_deps), BUILD_DIR "synth"))
return 1;
+ if (tests && !build_tests(&cmd, BUILD_DIR "test"))
+ return 1;
+
return 0;
}
diff --git a/src/gui.h b/src/gui.h
index f151e35..ac1436f 100644
--- a/src/gui.h
+++ b/src/gui.h
@@ -7,8 +7,16 @@ typedef enum screen_e {
SCREEN_ERR
} screen_e;
+typedef enum popup_e {
+ POPUP_NONE = 0,
+ POPUP_SAVE_PATCH,
+ POPUP_LOAD_PATCH,
+ POPUP_ERR
+} popup_e;
+
typedef struct synth_gui {
screen_e screen;
+ popup_e popup;
int audiomidi_initialized;
} synth_gui;
diff --git a/src/osc.c b/src/osc.c
index 28e35b4..70e7c32 100644
--- a/src/osc.c
+++ b/src/osc.c
@@ -18,6 +18,12 @@ osc_interpolate(float offset, float start, float end) {
return m * x + b;
}
+/**
+ * Takes a floating-point position (offset) in the oscillator/buffer.
+ * Wraps it so it stays between osc->start and osc->len - 1 (cyclically).
+ * Converts it to an integer index.
+ * Returns the next index, wrapping back to osc->start if it reaches the end.
+ */
int
osc_next_index(osc_t * osc, float offset)
{
@@ -27,7 +33,18 @@ osc_next_index(osc_t * osc, float offset)
int current_index = (int)offset;
return current_index + 1 >= osc->len ? osc->start : current_index + 1;
}
+// TODO same from AI: check it
+/* int */
+/* osc_next_index(osc_t *osc, float offset) { */
+/* float range = osc->len - osc->start; */
+/* // Wrap offset into the [start, len) range */
+/* offset = osc->start + fmodf(offset - osc->start + range, range); */
+
+/* // Get integer index and advance */
+/* int next = (int)offset + 1; */
+/* return (next >= osc->len) ? osc->start : next; */
+/* } */
float
osc_next_offset(osc_t * osc, float f, float offset)
@@ -52,6 +69,40 @@ osc_next_offset(osc_t * osc, float f, float offset)
return offset;
}
+/* static inline float wrap_offset(float x, float start, float len) { */
+/* float range = len - start; */
+/* x -= start; */
+/* x -= range * floorf(x / range); // efficient wrap to [0, range) */
+/* return start + x; */
+/* } */
+
+/* float osc_next_offset(osc_t * osc, float f, float offset) */
+/* { */
+/* // Wrap once at the start */
+/* if (offset < osc->start || offset >= osc->len) { */
+/* offset = wrap_offset(offset, osc->start, osc->len); */
+/* } */
+
+/* if (osc->type == SAMPLE) { */
+/* // Precomputed constant for (16.35160 * 2^(2 + 5/12)) */
+/* // = base frequency for A2 mapping */
+/* #define BASE_A2_FACTOR 220.00000f */
+/* f /= BASE_A2_FACTOR; */
+/* } */
+
+/* // Precompute conversion factor */
+/* float step = f * ((osc->len - osc->start) / (float)SAMPLE_RATE); */
+
+/* offset += step; */
+
+/* // Single wrap without fmodf (branchless for most cases) */
+/* if (offset >= osc->len) { */
+/* offset -= osc->len; */
+/* } */
+
+/* return offset; */
+/* } */
+
int
osc_load_wav(osc_t * osc, const char * path)
{
diff --git a/src/osc_sound.c b/src/osc_sound.c
index 4d34834..10da73b 100644
--- a/src/osc_sound.c
+++ b/src/osc_sound.c
@@ -23,8 +23,8 @@ osc_sound(float offset)
//osc_load_wav(&OSC_sound, "/home/grm/code/synth-project/waves/Free Wavetables[2048]/Melda Oscillator[2048-44.1khz-32bit]/Melda SKEW.wav");
//osc_load_wav(&OSC_sound, "/home/grm/code/synth-project/waves/Free Wavetables[2048]/Filter Sweep[2048-44.1khz-32bit]/SweepSaw.wav");
//osc_load_wav(&OSC_sound, "/home/grm/code/synth-project/waves/Free Wavetables[2048]/Additive Synth[2048-44.1khz-32bit]/Add Synth7.wav");
- //osc_load_wav(&OSC_sound, "/home/grm/code/synth-project/waves/Free Wavetables[2048]/Korg Analog Synth PhaseShift[2048-44.1khz-32bit]/MS 20 Saw MPS.wav");
- osc_load_wav(&OSC_sound, "/home/grm/code/synth-project-b/waves/Free Wavetables[2048]/Korg Analog Synth PhaseShift[2048-44.1khz-32bit]/MonoPoly Saw PS1.wav");
+ osc_load_wav(&OSC_sound, "/home/grm/code/synth-project-b/waves/Free Wavetables[2048]/Korg Analog Synth PhaseShift[2048-44.1khz-32bit]/MS 20 Saw MPS.wav");
+ //osc_load_wav(&OSC_sound, "/home/grm/code/synth-project-b/waves/Free Wavetables[2048]/Korg Analog Synth PhaseShift[2048-44.1khz-32bit]/MonoPoly Saw PS1.wav");
OSC_sound.start = wvt_size*0;
OSC_sound.len = OSC_sound.start + wvt_size;
}
diff --git a/src/synth_engine.h b/src/synth_engine.h
index e198c22..8330daa 100644
--- a/src/synth_engine.h
+++ b/src/synth_engine.h
@@ -52,6 +52,7 @@ typedef struct synth_viz {
int wave_enabled;
int adsr_enabled;
int adsr_graph_enabled;
+ int filter_adsr_graph_enabled;
int osc_enabled;
int freeze;
int rate_divider;
diff --git a/src/synth_engine_v2.c b/src/synth_engine_v2.c
index 4d9721c..14d3e7e 100644
--- a/src/synth_engine_v2.c
+++ b/src/synth_engine_v2.c
@@ -123,7 +123,6 @@ float prev_sample = 0.0f;
biquad_filter_t biquad = {0};
-
void
do_fliter(synth_t *synth, float *sample, unsigned int sample_rate, int frame)
{
@@ -149,11 +148,13 @@ do_fliter(synth_t *synth, float *sample, unsigned int sample_rate, int frame)
}
}
- cutoff = 50 + cutoff * fix_adsr(&synth->f_adsr,
- note->noteOn,
- note->noteOff,
- note->elapsed,
- note->noteOffSample);
+ float adsr = fix_adsr(&synth->f_adsr,
+ note->noteOn,
+ note->noteOff,
+ note->elapsed,
+ note->noteOffSample);
+ if (adsr < 0.1) adsr = 0.1;
+ cutoff = cutoff * adsr;
}
}
@@ -207,8 +208,6 @@ get_max_sample(synth_t *synth, int test_size)
return max;
}
-# include <stdint.h> // uint32_t
-
float Q_rsqrt(float number)
{
union {
@@ -220,6 +219,19 @@ float Q_rsqrt(float number)
return conv.f;
}
+/**
+ * @brief generate a sample from the currently selected generator
+ *
+ * Applies ADSR, filter and merges all the `voices` using root mean square
+ *
+ * @param synth synth struct
+ * @param sample_rate sample rate
+ * @param frame # of current frame
+ *
+ * @return float returns the generated sample
+ *
+ * @note This is called SAMPLE_RATE times per second (on average), needs to be quick
+ */
float
make_sample(synth_t * synth, unsigned int sample_rate, int frame)
{
@@ -261,21 +273,21 @@ make_sample(synth_t * synth, unsigned int sample_rate, int frame)
sample_rate);
}
-
/* filter */
do_fliter(synth, &sample, sample_rate, frame);
-
sample = CC_GET(gain) * sample;
// band stop for high freqs
//sample = bw_band_stop(synth->fff2, sample);
- //if (synth->clamp) sample = clamp(sample, -1, 1);
+ if (synth->clamp) sample = clamp(sample, -1, 1);
// autogain
- if (synth->autogain && (sample >= 1 || sample <= -1)) {
- synth->cc_gain.target *= 0.999;
+ if (synth->autogain) {
+ if (sample >= 1 || sample <= -1) {
+ synth->cc_gain.target *= 0.999;
+ }
}
//printf("CLICK! %f\n", fabsf(prev_sample) - fabsf(sample));
@@ -309,6 +321,25 @@ increment_synth(synth_t *synth)
float prev = 0;
+/**
+ * @brief generates a single frame, also handles the delay
+ *
+ * each frame in the current implementation is a pair of samples one for the
+ * left channel and one for the right. We first check with notes_active if
+ * there are any notes still ringing so we can set the active timer to 0. Then
+ * we get the sample from the generators, and we blend any possible delay sound
+ * from before as well as add the new sample to the delay for future use. Then
+ * we increment the synth counters to be ready for the next sample and we make
+ * sure to write our sample to the viz buffer as well for visuals.
+ *
+ * @param outputBuffer where to store the generated samples
+ * @param synth synth struct
+ * @param frame # of frame in the buffer
+ *
+ * @return void void
+ *
+ * @note <-->
+ */
void
get_frame(void *outputBuffer, synth_t *synth, int frame)
{
@@ -342,8 +373,6 @@ get_frame(void *outputBuffer, synth_t *synth, int frame)
add_to_delay(synth, sample);
-
-
//sample = clamp(sample, -1, 1);
*out++ = sample;
@@ -393,7 +422,37 @@ void smooth_buffer(float *buffer, int frames_per_buffer, float smooth_factor) {
}
}
-
+/**
+ * @brief portaudio callback to generate next batch of samples
+ *
+ * For a given SAMPLE_RATE we need to create SAMPLE_RATE frames per second so
+ * we get sound from the speaker. A frame is all the different channel values
+ * for a given timestamp. A stereo setup (like this) has 2 samples in each
+ * frame (left and right). The soundcard requests frames in a buffer where we
+ * control the size. The more frames the buffer has, the more latency we
+ * introduce to the system since in order for our changes in the synth to take
+ * effect the soundcard needs to request the next buffer. We handle this by
+ * picking a low enough framesPerBuffer number (FRAMES_PER_BUFFER). Since each
+ * second the soundcard needs to generate SAMPLE_RATE frames, it will call the
+ * sound_gen function SAMPLE_RATE / FRAMES_PER_BUFFER times. It follows that
+ * this function should be as fast as possible for realtime performance (with
+ * the upper bound of 1 / (SAMPLE_RATE / FRAMES_PER_BUFFER) seconds). Failing
+ * to meet this *will* result in loud clicks and noise.
+ *
+ * This function is called asynchronously by the portaudio thread. It prepares
+ * all the CCs and the fills the outputBuffer frame by frame.
+ *
+ * @param inputBuffer unused
+ * @param outputBuffer where the resulting samples are written
+ * @param framesPerBuffer how many frames the buffer has
+ * @param timeInfo unused
+ * @param statusFlags unused
+ * @param synthData user data, contains the synth struct
+ *
+ * @return int paContinue(0) on success
+ *
+ * @note this funcion shouldn't fail
+ */
int
sound_gen(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
@@ -402,12 +461,10 @@ sound_gen(const void *inputBuffer, void *outputBuffer,
void *synthData)
{
synth_t *synth = (synth_t*)synthData;
- float *out = (float*)outputBuffer;
-
if (!synth->sound_active) return 0; //paContinue;
float buffer[2 * FRAMES_PER_BUFFER];
-
+ float *out = (float*)outputBuffer;
(void) timeInfo;
(void) statusFlags;
(void) inputBuffer;
@@ -424,7 +481,7 @@ sound_gen(const void *inputBuffer, void *outputBuffer,
get_frame(buffer, synth, frame);
}
- smooth_buffer(buffer, framesPerBuffer, 0.1f);
+ //smooth_buffer(buffer, framesPerBuffer, 0.1f);
// output buffer
for( unsigned long i=0; i<framesPerBuffer * 2; i += 2 ) {
@@ -439,7 +496,7 @@ sound_gen(const void *inputBuffer, void *outputBuffer,
clock_t end = clock();
double time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
if (time_spent > (double)FRAMES_PER_BUFFER / SAMPLE_RATE) {
- printf("To generate %d samples per second we need to generate a sample every 1 / %d second. Since we generate samples by batching them in groups of %d, each group should take longer than %f. This one took %f\n", SAMPLE_RATE, SAMPLE_RATE, FRAMES_PER_BUFFER, (float)FRAMES_PER_BUFFER / SAMPLE_RATE, time_spent);
+ printf("To generate %d samples per second we need to generate a sample every 1 / %d second. Since we generate samples by batching them in groups of %d, each group should not take longer than %f. This one took %f\n", SAMPLE_RATE, SAMPLE_RATE, FRAMES_PER_BUFFER, (float)FRAMES_PER_BUFFER / SAMPLE_RATE, time_spent);
}
return paContinue;
@@ -554,11 +611,11 @@ init_synth(void)
synth->viz.fft_output_buffer = (float *)calloc(sizeof(float), RING_SIZE * 8);
synth->viz.fft_smooth_buffer = (float *)calloc(sizeof(float), RING_SIZE * 8);
- synth->viz.spectrum_enabled = 0;
+ synth->viz.spectrum_enabled = 1;
synth->viz.wave_enabled = 0;
synth->viz.adsr_enabled = 0;
- synth->viz.adsr_graph_enabled = 1;
- synth->viz.osc_enabled = 0;
+ synth->viz.adsr_graph_enabled = 0;
+ synth->viz.osc_enabled = 1;
synth->viz.freeze = 0;
synth->viz.tmp_index = 0;
@@ -630,7 +687,7 @@ load_synth(synth_t *synth, const char *path)
config_init(&cfg);
/* Read the file. If there is an error, report it and exit. */
- if(! config_read_file(&cfg, "TEST.cfg"))
+ if(! config_read_file(&cfg, path))
{
fprintf(stderr, "%s:%d - %s\n", config_error_file(&cfg),
config_error_line(&cfg), config_error_text(&cfg));
@@ -663,6 +720,7 @@ load_synth(synth_t *synth, const char *path)
config_lookup_float(&cfg, "synth.delay.feedback", &FLOAT);
synth->cc_del_feedback.target = FLOAT;
+ config_lookup_int(&cfg, "synth.biquad.enable", &synth->filter);
config_lookup_int(&cfg, "synth.filter.enable", &synth->filter);
config_lookup_float(&cfg, "synth.filter.cutoff", &FLOAT);
synth->cc_cutoff.target = FLOAT;
@@ -687,7 +745,7 @@ save_synth(synth_t *synth, const char *path)
{
(void)path;
- static const char *output_file = "TEST.cfg";
+ const char *output_file = path;//"TEST.cfg";
config_t cfg;
config_setting_t *root, *setting, *group, *adsr, *delay, *lfo, *filter;
@@ -699,7 +757,7 @@ save_synth(synth_t *synth, const char *path)
group = config_setting_add(root, "synth", CONFIG_TYPE_GROUP);
setting = config_setting_add(group, "name", CONFIG_TYPE_STRING);
- config_setting_set_string(setting, "example synth name");
+ config_setting_set_string(setting, path);
setting = config_setting_add(group, "generator", CONFIG_TYPE_INT);
config_setting_set_int(setting, synth->geni);
@@ -724,6 +782,10 @@ save_synth(synth_t *synth, const char *path)
setting = config_setting_add(delay, "feedback", CONFIG_TYPE_FLOAT);
config_setting_set_float(setting, synth->cc_del_feedback.target);
+ filter = config_setting_add(group, "biquad", CONFIG_TYPE_GROUP);
+ setting = config_setting_add(filter, "enable", CONFIG_TYPE_INT);
+ config_setting_set_int(setting, synth->biquad);
+
filter = config_setting_add(group, "filter", CONFIG_TYPE_GROUP);
setting = config_setting_add(filter, "enable", CONFIG_TYPE_INT);
config_setting_set_int(setting, synth->filter);
diff --git a/src/synth_gui.c b/src/synth_gui.c
index 215b8f5..62a6ab7 100644
--- a/src/synth_gui.c
+++ b/src/synth_gui.c
@@ -364,42 +364,32 @@ void
draw_osc(synth_t * synth, int x, int y, int width, int height)
{
DrawRectangle(x, y, width, height, BLACK);
- DrawRectangleLines(x, y, width, height, WHITE);
+ DrawRectangleLines(x, y, width, height, GRAY);
+
float osc_wave[width];
+ float osc_max = 0;
+
for (int i = 0; i < width; i++) {
osc_wave[i] = 0;
}
- for (int i = 0; i < synth->midi_active_n; i++) {
- midi_note_t * note = synth->midi_active[i];
- midi_note_t note_dup;
- note_dup.freq = note->freq;
- note_dup.channel = note->channel;
- note_dup.noteOn = note->noteOn;
- note_dup.noteOff = note->noteOff;
- note_dup.velocity = note->velocity;
- note_dup.wvt_index = 0;
- note_dup.lfo_index = note->lfo_index;
- note_dup.elapsed = note->elapsed;
- note_dup.noteOffSample = note->noteOffSample;
- note_dup.adsr = note->adsr;
- note_dup.active = note->active;
- for (int i = 0; i < width; i++) {
- float freq = SAMPLE_RATE/(float)width;
- osc_wave[i] += synth->gen[synth->geni](freq, &note_dup, synth->x, SAMPLE_RATE);
- }
- break;
+ midi_note_t note_dup = {0};
+ for (int i = 0; i < width; i++) {
+ float freq = SAMPLE_RATE/(float)width;
+ osc_wave[i] += synth->gen[synth->geni](freq, &note_dup, synth->x, SAMPLE_RATE);
}
- float max = 0;
for (int i = 0; i < width; i++) {
- if (fabs(osc_wave[i]) > max) max = fabs(osc_wave[i]);
+ if (fabs(osc_wave[i]) > osc_max) osc_max = fabs(osc_wave[i]);
}
- if (synth->midi_active_n) {
- for (int i = 0; i < width; i++) {
- DrawPixel(i + x , y + height / 2.0 + floor(height/2.0 * osc_wave[i] / max), RED);
- }
+ Color color = GRAY;
+ if (synth->midi_active_n > 0) {
+ color = RED;
+ }
+ for (int i = 0; i < width; i++) {
+ //DrawPixel(i + x , y + height / 2.0 + floor(height/2.0 * osc_wave[i] / osc_max), WHITE);
+ DrawCircle(i + x , y + height / 2.0 + floor(height/2.0 * osc_wave[i] / osc_max), 2, color);
}
}
@@ -482,6 +472,85 @@ draw_adsr_graph(synth_t * synth, int x, int y, int width, int height)
}
void
+draw_filter_adsr_graph(synth_t * synth, int x, int y, int width, int height)
+{
+ adsr_t f_adsr;
+
+ f_adsr.a = synth->cc_f_adsr_a.target;
+ f_adsr.peak = synth->cc_f_adsr_peak.target;
+ f_adsr.d = synth->cc_f_adsr_d.target;
+ f_adsr.s = synth->cc_f_adsr_s.target;
+ f_adsr.r = synth->cc_f_adsr_r.target;
+
+ float total = synth->cc_f_adsr_a.target + synth->cc_f_adsr_d.target + synth->cc_f_adsr_r.target + 0.3; /*sustain*/
+ int total_samples = total * SAMPLE_RATE;
+ int a = synth->cc_f_adsr_a.target/total * width;
+ int d = synth->cc_f_adsr_d.target/total * width;
+ int s = 0.3/total * width;
+ int r = synth->cc_f_adsr_r.target/total * width;
+
+ float f_adsr_graph[width];
+ for (int i = 0; i < width; i++) {
+ float point = 0;
+
+ if (i < a) { //draw attack
+ point = fix_adsr(&f_adsr,
+ 1, // note On
+ 0, // note Off
+ (float)i/width * total_samples,
+ 0);
+ } else if (i < a + d) { // draw decay
+ point = fix_adsr(&f_adsr,
+ 1, // note On
+ 0, // note Off
+ (float)i/width * total_samples,
+ 0);
+ } else if (i < a + d + s) { // draw sustain
+ point = fix_adsr(&f_adsr,
+ 1, // note On
+ 0, // note Off
+ (float)i/width * total_samples,
+ 0);
+ } else if (r && i < a + d + s + r) { // draw release
+ point = fix_adsr(&f_adsr,
+ 0, // note On
+ 1, // note Off
+ (float)i/width * total_samples,
+ (float)(a + d + s)/width * total_samples);
+ }
+ f_adsr_graph[i] = point * height;
+ }
+ f_adsr_graph[0] = f_adsr_graph[1]; // remove 1st 0
+
+ // Draw filter ADSR in blue to distinguish from amplitude ADSR
+ for (int i = 0; i < width; i++) {
+ DrawPixel(i + x , y + height - f_adsr_graph[i], BLUE);
+ }
+
+ // Draw current position indicators for active notes
+ for (int i = 0; i < synth->midi_active_n; i++) {
+ midi_note_t * note = synth->midi_active[i];
+ int elapsed_samples = note->elapsed;
+ float elapsed_secs = (float)elapsed_samples / SAMPLE_RATE;
+ int j = elapsed_secs / total * width;
+
+ if (note->noteOff == 0) {
+ if (j < a + d) {
+ DrawCircle(j + x , y + height - f_adsr_graph[j], 3, BLUE);
+ } else if (j < a + d + s - 1) {
+ DrawCircle(j + x , y + height - f_adsr_graph[j], 3, BLUE);
+ } else if (j > a + d + s - 1) {
+ DrawCircle(a + d + s + x , y + height - f_adsr_graph[a + d + s], 3, BLUE);
+ }
+ } else { // note off
+ int id = a + d + s + (float)(note->elapsed - note->noteOffSample)/SAMPLE_RATE / total * width;
+ if (id > 0 && id < width)
+ DrawCircle(id + x , y + height - f_adsr_graph[id], 3, BLUE);
+ }
+ }
+}
+
+void
draw_signals(synth_t * synth, int x, int y, int width, int height)
{
DrawRectangleLines(x, y, width, height, WHITE);
@@ -524,15 +593,15 @@ draw_signals(synth_t * synth, int x, int y, int width, int height)
draw_adsr(synth, x, y, width, height);
}
- synth->viz.osc_enabled = GuiCheckBox((Rectangle){ x + width - 170, y, 16, 16 }, "osc", synth->viz.osc_enabled);
- if (synth->viz.osc_enabled) {
- draw_osc(synth, x + width - 80, y + height - 80, 80, 80);
- }
-
synth->viz.adsr_graph_enabled = GuiCheckBox((Rectangle){ x + width - 270, y, 16, 16 }, "adsr graph", synth->viz.adsr_graph_enabled);
if (synth->viz.adsr_graph_enabled) {
draw_adsr_graph(synth, x + 20, y + 20, width - 40, height - 40);
}
+
+ synth->viz.filter_adsr_graph_enabled = GuiCheckBox((Rectangle){ x + width - 370, y, 16, 16 }, "filter adsr", synth->viz.filter_adsr_graph_enabled);
+ if (synth->viz.filter_adsr_graph_enabled) {
+ draw_filter_adsr_graph(synth, x + 20, y + 20, width - 40, height - 40);
+ }
}
char * flag = NULL;
@@ -707,10 +776,70 @@ void get_nth_entry(const char *str, int n, char *result) {
result[0] = '\0';
}
+void draw_load_patch(synth_t *synth) {
+ static bool save_name_edit = false;
+ static char save_text[1024] = ""; // TODO default to current patch name
+
+ GuiPanel((Rectangle){ WIDTH / 2.0 - 200, HEIGHT / 2.0 - 140, 400, 280 }, "Save patch");
+ if (GuiButton(
+ (Rectangle){WIDTH / 2.0 - 50 - 50 - 3, HEIGHT / 2.0 - 12, 100, 24},
+ "load")) {
+ if (strlen(save_text) > 0) {
+ if (load_synth(synth, save_text)) {
+ fprintf(stderr, "Can't load %s\n", save_text);
+ return;
+ }
+ synth->gui.popup = POPUP_NONE;
+ }
+ }
+ if (GuiButton((Rectangle){WIDTH / 2.0 + 3, HEIGHT / 2.0 - 12, 100, 24},
+ "cancel")) {
+ synth->gui.popup = POPUP_NONE;
+ }
+
+
+ if (GuiTextBox(
+ (Rectangle){WIDTH / 2.0 - 125, HEIGHT / 2.0 - 18 - 24 - 6, 250, 36},
+ save_text, 20, save_name_edit)) {
+ save_name_edit = true;
+ }
+}
+
+void draw_save_patch(synth_t *synth)
+{
+ static bool save_name_edit = false;
+ static char save_text[1024] = ""; // TODO default to current patch name
+
+ GuiPanel((Rectangle){ WIDTH / 2.0 - 200, HEIGHT / 2.0 - 140, 400, 280 }, "Save patch");
+ if (GuiButton(
+ (Rectangle){WIDTH / 2.0 - 50 - 50 - 3, HEIGHT / 2.0 - 12, 100, 24},
+ "save")) {
+ if (strlen(save_text) > 0) {
+ if (save_synth(synth, save_text)) {
+ fprintf(stderr, "Can't save at %s\n", save_text);
+ return;
+ }
+ synth->gui.popup = POPUP_NONE;
+ }
+ }
+ if (GuiButton((Rectangle){WIDTH / 2.0 + 3, HEIGHT / 2.0 - 12, 100, 24},
+ "cancel")) {
+ synth->gui.popup = POPUP_NONE;
+ }
+
+
+ if (GuiTextBox(
+ (Rectangle){WIDTH / 2.0 - 125, HEIGHT / 2.0 - 18 - 24 - 6, 250, 36},
+ save_text, 20, save_name_edit)) {
+ save_name_edit = true;
+ }
+}
+
+
char *soundcards = NULL;
char *midi_devices = NULL;
void
-draw_audiomidisetup(synth_t *synth, const char *midi_devices)
+draw_audiomidisetup(synth_t *synth)
{
static int edit_midi = 0;
static int edit_sound = 0;
@@ -727,9 +856,6 @@ draw_audiomidisetup(synth_t *synth, const char *midi_devices)
printf("================ AUDIOMIDISETUP GUI INITIALIZED!\n");
}
- BeginDrawing();
- ClearBackground(BLACK);
-
if (GuiButton((Rectangle){WIDTH - 100 - 50 - 6 - 100,
50 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6, 100, 24},
"cancel")) {
@@ -767,14 +893,6 @@ draw_audiomidisetup(synth_t *synth, const char *midi_devices)
soundcards, &soundcard_pick, edit_sound)) {
edit_sound = !edit_sound;
}
-
- /* if (old_midi_device_id != synth->midi_device_id) { */
- /* old_midi_device_id = synth->midi_device_id; */
- /* } */
-
-
-
- EndDrawing();
}
void
@@ -785,8 +903,6 @@ draw_main(synth_t *synth)
if (midi_devices) free(midi_devices);
synth->gui.audiomidi_initialized = 0;
}
- BeginDrawing();
- ClearBackground(BLACK);
int fb = 0;
int foffset = 9;
@@ -820,6 +936,11 @@ draw_main(synth_t *synth)
draw_bars(synth, 20, 20, 200, 16, 3);
draw_text(synth, WIDTH / 2 - 108, 20);
+ //synth->viz.osc_enabled = GuiCheckBox((Rectangle){ x + width - 170, y, 16, 16 }, "osc", synth->viz.osc_enabled);
+ if (synth->viz.osc_enabled) {
+ draw_osc(synth, WIDTH / 2 + 180, 140, 80, 80);
+ }
+
if ( GuiButton((Rectangle){ WIDTH / 2.0 - 108, 150 - 6 - 6 + 42, 216, 6 }, "")) {
synth->x = 1;
}
@@ -850,12 +971,15 @@ draw_main(synth_t *synth)
if ( GuiButton((Rectangle){ WIDTH - 100 - 50, 50 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6, 100, 24 }, "audiomidi")) {
synth->gui.screen = SCREEN_AUDIOMIDISETUP;
}
- if ( GuiButton((Rectangle){ WIDTH - 100 - 50, 50 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6, 100, 24 }, "SAVE!")) {
- save_synth(synth, "asdas");
+ if (GuiButton((Rectangle){WIDTH - 100 - 50,
+ 50 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6,
+ 100, 24},
+ "SAVE!")) {
+ synth->gui.popup = POPUP_SAVE_PATCH;
}
if ( GuiButton((Rectangle){ WIDTH - 100 - 50, 50 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6 + 24 + 6, 100, 24 }, "LOAD!")) {
- load_synth(synth, "asdas");
+ synth->gui.popup = POPUP_LOAD_PATCH;
}
@@ -867,34 +991,76 @@ draw_main(synth_t *synth)
/* DrawText("KEYBOARD: Q .. ]", WIDTH / 2 -300, HEIGHT - 300 - 50, 20, LIGHTGRAY); */
/* snprintf(buf, sizeof buf, "stream time: %f", Pa_GetStreamTime(synth->stream)); */
/* DrawText(buf, WIDTH / 2 -300, HEIGHT - 300, 11, LIGHTGRAY); */
- EndDrawing();
}
void
rayrun(synth_t *synth){
PaTime current_time = 0;
PaTime prev_time = 0;
+ int mod = 0;
osc_sound(0);
- /* //char *midi_devices = "test1;test2;test3"; */
- char *midi_devices = get_midi_devices();
- /* int old_soundcard_id = synth->soundcard_id; */
- /* int old_midi_device_id = synth->midi_device_id; */
-
SetTraceLogLevel(LOG_ERROR);
InitWindow(WIDTH, HEIGHT, "Raylib synth");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
while (!WindowShouldClose()) {
- keyboard(synth);
- mouse(synth);
+ if (synth->gui.popup == POPUP_NONE) {
+ keyboard(synth);
+ mouse(synth);
+ }
+ if (true || mod++ % 10 == 0) {
+ char b[256];
+ sprintf(b, "wvt_pos:%d", synth->wvt_pos);
+ ws_send_message(b);
+ sprintf(b, "cci:%d", synth->cci);
+ ws_send_message(b);
+ sprintf(b, "autogain:%d", synth->autogain);
+ ws_send_message(b);
+ sprintf(b, "x:%f", synth->x);
+ ws_send_message(b);
+ sprintf(b, "midi_active_n:%d", synth->midi_active_n);
+ ws_send_message(b);
+ sprintf(b, "octave:%d", synth->octave);
+ ws_send_message(b);
+ sprintf(b, "delay:%d", synth->delay);
+ ws_send_message(b);
+ sprintf(b, "deli:%d", synth->deli);
+ ws_send_message(b);
+ sprintf(b, "f_adsr_enabled:%d", synth->f_adsr_enabled);
+ ws_send_message(b);
+ sprintf(b, "filter:%d", synth->filter);
+ ws_send_message(b);
+ sprintf(b, "biquad:%d", synth->biquad);
+ ws_send_message(b);
+ sprintf(b, "clamp:%d", synth->clamp);
+ ws_send_message(b);
+ sprintf(b, "modi:%d", synth->modi);
+ ws_send_message(b);
+ sprintf(b, "geni:%d", synth->geni );
+ ws_send_message(b);
+ sprintf(b, "active:%d", synth->active);
+ ws_send_message(b);
+ sprintf(b, "sound_active:%d", synth->sound_active);
+ ws_send_message(b);
+ }
// Draw
//----------------------------------------------------------------------------------
+ BeginDrawing();
+ ClearBackground(BLACK);
+
if (synth->gui.screen == SCREEN_MAIN)
draw_main(synth);
else if (synth->gui.screen == SCREEN_AUDIOMIDISETUP)
- draw_audiomidisetup(synth, midi_devices);
+ draw_audiomidisetup(synth);
+
+ if (synth->gui.popup == POPUP_SAVE_PATCH)
+ draw_save_patch(synth);
+ else if (synth->gui.popup == POPUP_LOAD_PATCH)
+ draw_load_patch(synth);
+
+ EndDrawing();
//----------------------------------------------------------------------------------
current_time = Pa_GetStreamTime(synth->stream);
diff --git a/src/web.c b/src/web.c
index f3aa5f7..afd8c57 100644
--- a/src/web.c
+++ b/src/web.c
@@ -58,6 +58,38 @@ char *read_file_to_string(const char *filename) {
return buffer;
}
+char *get_from_template() {
+ int size = 1024^3 * 1000;
+ char * ret = (char *)malloc(sizeof(char) * size);
+
+ int pipefd[2];
+ if (pipe(pipefd) == -1) {
+ perror("pipe");
+ return NULL;
+ }
+
+ synth_t * synth = synthx;
+ #define OUT pipefd[1]
+ #define INT(x) dprintf(OUT, "%d", x);
+ #define FLOAT(x) dprintf(OUT, "%f", x);
+ #define STR(x) dprintf(OUT, "%s", x);
+ #define PERCENT dprintf(OUT, "%s", "%");
+ #include "index.html.h"
+ close(pipefd[1]);
+
+ // Read from the pipe into a buffer
+ ssize_t bytes_read = read(pipefd[0], ret, size - 1);
+ if (bytes_read == -1) {
+ perror("read");
+ return NULL;
+ }
+
+ // Null-terminate the ret
+ ret[bytes_read] = '\0';
+ close(pipefd[0]);
+ return ret;
+}
+
int
key_to_number(char key) {
switch (key) {
@@ -164,7 +196,7 @@ static int callback_ws(struct lws *wsi, enum lws_callback_reasons reason,
break;
}
case LWS_CALLBACK_SERVER_WRITEABLE: {
- printf("\nLWS_CALLBACK_SERVER_WRITEABLE\n\n");
+ printf("LWS_CALLBACK_SERVER_WRITEABLE\n");
/* size_t msg_len = strlen(message_buffer); */
/* unsigned char buffer[LWS_PRE + BUFFER_SIZE]; */
/* memcpy(&buffer[LWS_PRE], message_buffer, msg_len); */
@@ -172,10 +204,12 @@ static int callback_ws(struct lws *wsi, enum lws_callback_reasons reason,
break;
}
case LWS_CALLBACK_HTTP: {
+ html_content = get_from_template();
snprintf(tmp, sizeof(tmp), html_header, strlen(html_content));
strcpy(buf, tmp);
strcat(buf, html_content);
lws_write(wsi, (unsigned char *)buf, strlen(buf), LWS_WRITE_HTTP);
+ free(html_content);
break;
}
case LWS_CALLBACK_CLOSED: {
@@ -221,40 +255,10 @@ void *websocket_server_thread(void *arg) {
return NULL;
}
-char *get_from_template() {
- int size = 1024^3 * 1000;
- char * ret = (char *)malloc(sizeof(char) * size);
-
- int pipefd[2];
- if (pipe(pipefd) == -1) {
- perror("pipe");
- return NULL;
- }
-
- #define OUT pipefd[1]
- #define INT(x) dprintf(OUT, "%d", x);
- #define PERCENT dprintf(OUT, "%s", "%");
- #include "index.html.h"
- close(pipefd[1]);
-
- // Read from the pipe into a buffer
- ssize_t bytes_read = read(pipefd[0], ret, size - 1);
- if (bytes_read == -1) {
- perror("read");
- return NULL;
- }
-
- // Null-terminate the ret
- ret[bytes_read] = '\0';
- close(pipefd[0]);
- return ret;
-}
-
void
init_web(synth_t * synth)
{
//html_content = read_file_to_string("src/index.html");
- html_content = get_from_template();
synthx = synth;
lws_set_log_level(LLL_WARN, NULL);
@@ -274,14 +278,12 @@ free_web()
void ws_send_message(const char *message) {
if (client_wsi != NULL) {
- /* strcpy(message_buffer, message); */
- /* lws_callback_on_writable(client_wsi); */
size_t msg_len = strlen(message);
unsigned char buffer[LWS_PRE + BUFFER_SIZE];
memcpy(&buffer[LWS_PRE], message, msg_len);
lws_write(client_wsi, &buffer[LWS_PRE], msg_len, LWS_WRITE_TEXT);
- printf("[WS]: Sent <<%s>>\n", message);
-
- //lws_write(client_wsi, (unsigned char *)message, strlen(message), LWS_WRITE_TEXT);
+ // printf("[WS]: Sent <<%s>>\n", message);
+ // do I need this?
+ //lws_callback_on_writable(client_wsi); ???
}
}
diff --git a/tests/example_test.c b/tests/example_test.c
new file mode 100644
index 0000000..341f6af
--- /dev/null
+++ b/tests/example_test.c
@@ -0,0 +1,70 @@
+#include <check.h>
+#include "osc.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+START_TEST (test_name)
+{
+ //printf("Hello from test!\n");
+ //ck_assert_int_eq(0,109);
+ return;
+}
+END_TEST
+
+START_TEST (badtest_name)
+{
+ //printf("Hello from test!\n");
+ //ck_assert_int_eq(0,109);
+ return;
+}
+END_TEST
+
+
+Suite * osc_suite(void)
+{
+ Suite *s;
+ TCase *tc_osc;
+
+ s = suite_create("osc");
+
+ tc_osc = tcase_create("OSC");
+
+ tcase_add_test(tc_osc, test_name);
+ suite_add_tcase(s, tc_osc);
+
+ return s;
+}
+
+Suite * adsr_suite(void)
+{
+ Suite *s;
+ TCase *tc_adsr;
+
+ s = suite_create("adsr");
+
+ tc_adsr = tcase_create("ADSR");
+
+ tcase_add_test(tc_adsr, test_name);
+ tcase_add_test(tc_adsr, test_name);
+ tcase_add_test(tc_adsr, badtest_name);
+ tcase_add_test(tc_adsr, test_name);
+ suite_add_tcase(s, tc_adsr);
+
+ return s;
+}
+
+int main(void)
+{
+ int number_failed;
+ SRunner *sr;
+
+ sr = srunner_create(osc_suite());
+ srunner_add_suite(sr, adsr_suite());
+ srunner_add_suite(sr, adsr_suite());
+
+ srunner_set_tap(sr, "results.tap");
+ srunner_run_all(sr, CK_NORMAL);
+ number_failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+ return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/tmpl/index.html b/tmpl/index.html
index 23a0714..a6ae38b 100644
--- a/tmpl/index.html
+++ b/tmpl/index.html
@@ -4,10 +4,41 @@
<title>C SYNTH WEB!</title>
</head>
<body>
- <input id='slider' style='width: 100%; height: 200px;' type='range'
+ <input id='slider' style='width: 100%; height: 200px;' type='range'
min='1' max='22000' />
<button onclick='onButtonClick()'>Trigger</button>
<button id='but'>ws</button>
+
+ <div class="struct">
+ <h4>soundcard_t</h4>
+ <span class="str">$STR(synth->soundcard.name)$</span>
+ </div>
+
+ <div class="struct">
+ <h4>midi_device_t</h4>
+ <span class="str">$STR(synth->midi_device.name)$</span>
+ </div>
+
+ <div class="struct">
+ <h4>synth_t</h4>
+ <span id="wvt_pos" class="int">wvt_pos: $INT( synth->wvt_pos )$</span>
+ <span id="cci" class="int">cci: $INT( synth->cci )$</span>
+ <span id="autogain" class="int">autogain: $INT( synth->autogain )$</span>
+ <span id="x" class="float">x: $FLOAT( synth->x )$</span>
+ <span id="midi_active_n" class="int">midi_active_n: $INT( synth->midi_active_n )$</span>
+ <span id="octave" class="int">octave: $INT( synth->octave )$</span>
+ <span id="delay" class="int">delay: $INT( synth->delay )$</span>
+ <span id="deli" class="int">deli: $INT( synth->deli )$</span>
+ <span id="f_adsr_enabled" class="int">f_adsr_enabled: $INT( synth->f_adsr_enabled )$</span>
+ <span id="filter" class="int">filter: $INT( synth->filter )$</span>
+ <span id="biquad" class="int">biquad: $INT( synth->biquad )$</span>
+ <span id="clamp" class="int">clamp: $INT( synth->clamp )$</span>
+ <span id="modi" class="int">modi: $INT( synth->modi )$</span>
+ <span id="geni" class="int">geni: $INT( synth->geni )$</span>
+ <span id="active" class="int">active: $INT( synth->active )$</span>
+ <span id="sound_active" class="int">sound_active: $INT( synth->sound_active )$</span>
+ </div>
+
<script>
const ws = new WebSocket('ws://10.0.0.10:9967');
const slider = document.getElementById('slider');
@@ -16,8 +47,10 @@
but.onmousedown = function() { ws.send('note_on'); };
but.onmouseup = function() { ws.send('note_off'); }
ws.onmessage = function(event) {
- console.log('Message from server: ' + event.data);
- slider.value = parseInt(event.data);
+ //console.log('Message from server: ' + event.data);
+ const parts = event.data.split(":");
+ document.getElementById(parts[0]).innerText = parts[0] +": " + parts[1];
+ //slider.value = parseInt(event.data);
};
ws.onopen = function() {
console.log('Connected to WebSocket server');
@@ -55,14 +88,14 @@
document.addEventListener("keydown", (event) => sendKeyEvent("keydown", event));
document.addEventListener("keyup", (event) => sendKeyEvent("keyup", event));
- var fps = 60;
-
- function loop() {
- //document.getElementById("test").innerText = ;
-
- setTimeout(loop, 1000 / fps);
- }
- loop();
+ // var fps = 60;
+ // var count = 0;
+ // function loop() {
+ // //document.getElementById("test").innerText = ;
+ // document.getElementById("test").innerText = "Count: " + count++;
+ // setTimeout(loop, 1000 / fps);
+ // }
+ // loop();
</script>
</body>
</html>