diff options
-rw-r--r-- | b.c | 37 | ||||
-rw-r--r-- | src/gui.h | 8 | ||||
-rw-r--r-- | src/osc.c | 51 | ||||
-rw-r--r-- | src/osc_sound.c | 4 | ||||
-rw-r--r-- | src/synth_engine.h | 1 | ||||
-rw-r--r-- | src/synth_engine_v2.c | 116 | ||||
-rw-r--r-- | src/synth_gui.c | 280 | ||||
-rw-r--r-- | src/web.c | 74 | ||||
-rw-r--r-- | tests/example_test.c | 70 | ||||
-rw-r--r-- | tmpl/index.html | 55 |
10 files changed, 561 insertions, 135 deletions
@@ -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; } @@ -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; @@ -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, ¬e_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, ¬e_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); @@ -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> |