summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/osc.c51
-rw-r--r--src/osc_sound.c4
-rw-r--r--src/synth_engine_v2.c83
-rw-r--r--src/synth_gui.c52
4 files changed, 142 insertions, 48 deletions
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_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, &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);
}
}
@@ -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;
}