diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/osc.c | 51 | ||||
-rw-r--r-- | src/osc_sound.c | 4 | ||||
-rw-r--r-- | src/synth_engine_v2.c | 83 | ||||
-rw-r--r-- | src/synth_gui.c | 52 |
4 files changed, 142 insertions, 48 deletions
@@ -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_v2.c b/src/synth_engine_v2.c index 8740fe0..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) { @@ -209,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 { @@ -222,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) { @@ -263,11 +273,9 @@ 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 @@ -313,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) { @@ -346,8 +373,6 @@ get_frame(void *outputBuffer, synth_t *synth, int frame) add_to_delay(synth, sample); - - //sample = clamp(sample, -1, 1); *out++ = sample; @@ -397,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, @@ -406,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; @@ -443,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; @@ -558,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; diff --git a/src/synth_gui.c b/src/synth_gui.c index da90f52..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); } } @@ -603,11 +593,6 @@ 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); @@ -951,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; } |