Compare commits
5 Commits
7eb6a1755d
...
feature/os
| Author | SHA1 | Date | |
|---|---|---|---|
|
f88cba1843
|
|||
|
ce6a4d9ae5
|
|||
|
2918cac022
|
|||
|
2e4dc2c179
|
|||
| aaec53cfea |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,5 @@
|
||||
.DS_Store
|
||||
/Debug/
|
||||
*.wav
|
||||
*.dSYM
|
||||
*.dSYM
|
||||
/lib
|
||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -3,7 +3,11 @@
|
||||
"readability/casting"
|
||||
],
|
||||
"files.associations": {
|
||||
"algorithm": "c"
|
||||
"algorithm": "c",
|
||||
"__bit_reference": "c",
|
||||
"bitset": "c",
|
||||
"chrono": "c",
|
||||
"unordered_map": "c"
|
||||
},
|
||||
"FSharp.suggestGitignore": false,
|
||||
}
|
||||
38
docs/Resources.md
Normal file
38
docs/Resources.md
Normal file
@@ -0,0 +1,38 @@
|
||||
http://basicsynth.com/index.php?page=basic
|
||||
|
||||
Signal Generator
|
||||
The most straightforward method of sound generation in software is to evaluate a periodic function for each sample time. A periodic function is any function that repeats at a constant interval, called the period. Consider the circle in the figure below. Starting at the 3:00 position and then sweeping around the circle counter-clockwise, we make a complete cycle in 2π radians and then the movement repeats. Thus the period is 2π radians. If we plot the points on the circumference over time we produce the waveform as shown below.
|
||||
|
||||
|
||||
|
||||
For audio signals, the period is the time it takes for the waveform to repeat and is thus the inverse of the frequency. In other words, a frequency of 100Hz repeats every 1/100 second. We need to generate an amplitude value for every sample time, thus the number of samples in one period is equal to the time of the period divided by the time of one sample. Since the time of one sample is the inverse of the sample rate, and the period is the inverse of the frequency, the number of samples is also the sample rate divided by the frequency: ((1/f) / (1/fs)) = (fs/f). Since our period is also equal to 2π radians, the phase increment for one sample time (φ) is 2π divided by the number of samples in one period:
|
||||
|
||||
|
||||
|
||||
where the frequency of the signal is f and the sample rate fs. The amplitude for any given sample is the y value of the phase at that point in time multiplied by the radius of the circle. In other words, the amplitude is the sine of the phase angle and we can also derive the phase increment from the sine function.
|
||||
|
||||
Signal Generation Equation
|
||||
|
||||
The value sn is the nth sample, An the peak amplitude (volume) at sample n, and θn the phase at sample n. To calculate θn for any sample n, we can multiply the phase increment for one sample time (φ) by the sample number. To calculate φ we need to determine the radians for one sample time at a given frequency. As there are 2π radians per cycle, we multiply the frequency by 2π to get the radians per second. The phase increment for one sample time is then the radians per second multiplied by the time for one sample. Substiting for θn in the original equation yields:
|
||||
|
||||
Signal Generation Equation
|
||||
|
||||
We can implement this as a program loop.
|
||||
|
||||
totalSamples = duration * sampleRate;
|
||||
for (n = 0; n < totalSamples; n++)
|
||||
sample[n] = sin((twoPI/sampleRate) * frequency * n);
|
||||
Since 2π/fs is constant through the loop, we can calculate it once. We can also replace the multiplication of the phase with a repeated addition.
|
||||
|
||||
phaseIncr = (twoPI/sampleRate) * frequency;
|
||||
phase = 0;
|
||||
totalSamples = duration * sampleRate;
|
||||
for (n = 0; n < totalSamples; n++) {
|
||||
sample[n] = sin(phase);
|
||||
phase += phaseIncr;
|
||||
if (phase >= twoPI)
|
||||
phase -= twoPI;
|
||||
}
|
||||
|
||||
|
||||
We can replace the sin function with any periodic function that returns an amplitude for a given phase angle. Thus, this small piece of code can be used to produce a very wide range of sounds. Functionally, it is the software equivalent of an oscillator, the basic building block of almost all synthesizers.
|
||||
280
main.c
280
main.c
@@ -11,11 +11,28 @@
|
||||
#include "export.h"
|
||||
|
||||
#include "raylib.h"
|
||||
#define RAYGUI_IMPLEMENTATION
|
||||
#include "raygui.h"
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Synth
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
typedef struct OscillatorUI {
|
||||
float volume;
|
||||
float freq;//todo: remove or change to pitch shift
|
||||
OscillatorType waveshape;
|
||||
bool is_dropdown_open;
|
||||
Rectangle shape_dropdown_rect;
|
||||
} OscillatorUI;
|
||||
|
||||
typedef struct Synth {
|
||||
OscillatorArray oscillators;
|
||||
OscillatorUI* ui_oscillators;
|
||||
Note current_note;
|
||||
SynthSound* out_signal;
|
||||
} Synth;
|
||||
|
||||
static int get_semitone_shift_internal(char* root_note, char* target_note) {
|
||||
char* pitch_classes[12] =
|
||||
{ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
|
||||
@@ -68,38 +85,42 @@ int get_semitone_shift(char* target_note) {
|
||||
return get_semitone_shift_internal("A4", target_note);
|
||||
}
|
||||
|
||||
SynthSound note(int semitone, float beats) {
|
||||
float hz = get_hz_by_semitone(semitone);
|
||||
float duration = beats * BEAT_DURATION;
|
||||
|
||||
OscillatorParameter first = {
|
||||
static OscillatorArray init_osc_array() {
|
||||
Oscillator first = {
|
||||
.osc = Square,
|
||||
.freq = hz
|
||||
.freq = 440.f,
|
||||
.volume = VOLUME
|
||||
};
|
||||
|
||||
OscillatorParameter second = {
|
||||
.osc = Saw,
|
||||
.freq = hz + 0.5
|
||||
};
|
||||
Oscillator* oscArray = malloc(sizeof(Oscillator*) * 1);
|
||||
assert(oscArray);
|
||||
|
||||
oscArray[0] = first;
|
||||
|
||||
OscillatorParameter third = {
|
||||
.osc = Saw,
|
||||
.freq = hz - 1.f
|
||||
};
|
||||
|
||||
OscillatorParameter oscArray[] = { first/*, second, third */};
|
||||
OscillatorParameterList parameters = {
|
||||
OscillatorArray oscillators = {
|
||||
.array = oscArray,
|
||||
.count = 1
|
||||
};
|
||||
|
||||
return freq(duration, parameters);
|
||||
return oscillators;
|
||||
}
|
||||
|
||||
SynthSound get_note_sound(Note input) {
|
||||
SynthSound note(Synth* synth, int semitone, float beats) {
|
||||
float hz = get_hz_by_semitone(semitone);
|
||||
float duration = beats * BEAT_DURATION;
|
||||
|
||||
// will change after oscillator starts to be more autonomous
|
||||
for (size_t i = 0; i < synth->oscillators.count; i++) {
|
||||
osc_set_freq(&synth->oscillators.array[i], hz);
|
||||
}
|
||||
|
||||
return freq(duration, synth->oscillators);
|
||||
}
|
||||
|
||||
SynthSound get_note_sound(Synth* synth, Note input) {
|
||||
float length = 1.f / input.length;
|
||||
int semitone_shift = get_semitone_shift(input.name);
|
||||
return note(semitone_shift, length);
|
||||
return note(synth, semitone_shift, length);
|
||||
}
|
||||
//-------------------------------------------------------
|
||||
|
||||
@@ -137,27 +158,202 @@ size_t detect_note_pressed(Note* note) {
|
||||
return is_pressed;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// GUI
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
void note_on(Synth *synth, Note *note) {
|
||||
|
||||
}
|
||||
|
||||
void DrawUi(Synth *synth) {
|
||||
const int panel_x_start = 0;
|
||||
const int panel_y_start = 0;
|
||||
const int panel_width = OSCILLATOR_PANEL_WIDTH;
|
||||
const int panel_height = WINDOW_HEIGHT;
|
||||
|
||||
bool is_shape_dropdown_open = false;
|
||||
int shape_index = 0;
|
||||
|
||||
|
||||
GuiPanel((Rectangle){
|
||||
panel_x_start,
|
||||
panel_y_start,
|
||||
panel_width,
|
||||
panel_height
|
||||
},
|
||||
"");
|
||||
|
||||
bool click_add_oscillator = GuiButton((Rectangle){
|
||||
panel_x_start + 10,
|
||||
panel_y_start + 10,
|
||||
panel_width - 20,
|
||||
25
|
||||
}, "Add Oscillator");
|
||||
if (click_add_oscillator)
|
||||
{
|
||||
// synth->ui_oscillator_count += 1;
|
||||
// // Set defaults:
|
||||
// UiOscillator *ui_osc = synth->ui_oscillator + (synth->ui_oscillator_count - 1);
|
||||
// ui_osc->shape = WaveShape_SINE;
|
||||
// ui_osc->freq = BASE_NOTE_FREQ;
|
||||
// ui_osc->amplitude_ratio = 0.1f;
|
||||
// ui_osc->shape_parameter_0 = 0.5f;
|
||||
}
|
||||
|
||||
// Draw Oscillators
|
||||
float panel_y_offset = 0;
|
||||
//synth->ui_oscillator_count = 1
|
||||
for (int ui_osc_i = 0; ui_osc_i < synth->oscillators.count; ui_osc_i++)
|
||||
{
|
||||
OscillatorUI* ui_osc = &synth->ui_oscillators[ui_osc_i];
|
||||
assert(ui_osc);
|
||||
|
||||
Oscillator* osc = &synth->oscillators.array[ui_osc_i];
|
||||
assert(osc);
|
||||
|
||||
const bool has_shape_param = (ui_osc->waveshape == Square);
|
||||
|
||||
// Draw Oscillator Panel
|
||||
const int osc_panel_width = panel_width - 20;
|
||||
const int osc_panel_height = has_shape_param ? 130 : 100;
|
||||
const int osc_panel_x = panel_x_start + 10;
|
||||
const int osc_panel_y = panel_y_start + 50 + panel_y_offset;
|
||||
panel_y_offset += osc_panel_height + 5;
|
||||
GuiPanel((Rectangle){
|
||||
osc_panel_x,
|
||||
osc_panel_y,
|
||||
osc_panel_width,
|
||||
osc_panel_height
|
||||
},
|
||||
"");
|
||||
|
||||
const float slider_padding = 50.f;
|
||||
const float el_spacing = 5.f;
|
||||
Rectangle el_rect = {
|
||||
.x = osc_panel_x + slider_padding + 30,
|
||||
.y = osc_panel_y + 10,
|
||||
.width = osc_panel_width - (slider_padding * 2),
|
||||
.height = 25
|
||||
};
|
||||
|
||||
// Volume slider
|
||||
float decibels = (20.f * log10f(osc->volume));
|
||||
char amp_slider_label[32];
|
||||
sprintf(amp_slider_label, "%.1f dB", decibels);
|
||||
decibels = GuiSlider(el_rect,
|
||||
amp_slider_label,
|
||||
"",
|
||||
decibels,
|
||||
-60.0f,
|
||||
0.0f
|
||||
);
|
||||
ui_osc->volume = powf(10.f, decibels * (1.f/20.f));
|
||||
osc->volume = ui_osc->volume;
|
||||
|
||||
el_rect.y += el_rect.height + el_spacing;
|
||||
|
||||
// Defer shape drop-down box.
|
||||
ui_osc->shape_dropdown_rect = el_rect;
|
||||
el_rect.y += el_rect.height + el_spacing;
|
||||
/*
|
||||
Rectangle delete_button_rect = el_rect;
|
||||
delete_button_rect.x = osc_panel_x + 5;
|
||||
delete_button_rect.y -= el_rect.height + el_spacing;
|
||||
delete_button_rect.width = 30;
|
||||
bool is_delete_button_pressed = GuiButton(delete_button_rect, "X");
|
||||
if (is_delete_button_pressed)
|
||||
{
|
||||
memmove(
|
||||
synth->ui_oscillator + ui_osc_i,
|
||||
synth->ui_oscillator + ui_osc_i + 1,
|
||||
(synth->ui_oscillator_count - ui_osc_i) * sizeof(UiOscillator)
|
||||
);
|
||||
synth->ui_oscillator_count -= 1;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// DRAW OSCILLATOR SHAPE INPUTS
|
||||
for (int ui_osc_i = 0; ui_osc_i < synth->oscillators.count; ui_osc_i += 1)
|
||||
{
|
||||
OscillatorUI* ui_osc = &synth->ui_oscillators[ui_osc_i];
|
||||
assert(ui_osc);
|
||||
|
||||
Oscillator* osc = &synth->oscillators.array[ui_osc_i];
|
||||
assert(osc);
|
||||
|
||||
// Shape select
|
||||
int shape_index = (int)(ui_osc->waveshape);
|
||||
bool is_dropdown_click = GuiDropdownBox(ui_osc->shape_dropdown_rect,
|
||||
WAVE_SHAPE_OPTIONS,
|
||||
&shape_index,
|
||||
ui_osc->is_dropdown_open
|
||||
);
|
||||
|
||||
if (is_dropdown_click)
|
||||
{
|
||||
ui_osc->is_dropdown_open = !ui_osc->is_dropdown_open;
|
||||
ui_osc->waveshape = (OscillatorType)(shape_index);
|
||||
// APPLY STATE TO REAL OSC
|
||||
osc->osc = (OscillatorType)(shape_index);
|
||||
}
|
||||
if (ui_osc->is_dropdown_open) break;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawSignal(Synth* synth) {
|
||||
GuiGrid((Rectangle){0, 0, WINDOW_WIDTH, WINDOW_HEIGHT}, "", WINDOW_HEIGHT / 8, 2);
|
||||
Vector2* signal_points = malloc(sizeof(Vector2) * synth->out_signal->sample_count);
|
||||
const float screen_vertical_midpoint = (WINDOW_HEIGHT/2);
|
||||
for (int point_idx = 0; point_idx < synth->out_signal->sample_count; point_idx++)
|
||||
{
|
||||
signal_points[point_idx].x = (float)point_idx + OSCILLATOR_PANEL_WIDTH;
|
||||
signal_points[point_idx].y = screen_vertical_midpoint + (int)(synth->out_signal->samples[point_idx] * 300);
|
||||
}
|
||||
DrawLineStrip(signal_points, synth->out_signal->sample_count, RED);
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Main
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "SeeSynth - v0.1");
|
||||
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "SeeSynth - v0.2");
|
||||
SetTargetFPS(60);
|
||||
|
||||
Note current_note = {
|
||||
//todo: move that variables to Synth declaration
|
||||
Note g_current_note = {
|
||||
.length = 1,
|
||||
.name = malloc(sizeof(char) * 3)
|
||||
};
|
||||
|
||||
SynthSound sound = {
|
||||
SynthSound g_sound = {
|
||||
.sample_count = 0
|
||||
};
|
||||
|
||||
Synth synth = {
|
||||
.current_note = g_current_note,
|
||||
.out_signal = &g_sound,
|
||||
.oscillators = init_osc_array()
|
||||
};
|
||||
//todo: move somewhere in initialization
|
||||
synth.ui_oscillators = malloc(sizeof(OscillatorUI) * synth.oscillators.count);
|
||||
for (size_t i = 0; i < synth.oscillators.count; i++)
|
||||
{
|
||||
OscillatorUI* ui = &synth.ui_oscillators[i];
|
||||
assert(ui);
|
||||
|
||||
ui->freq = synth.oscillators.array[i].freq;
|
||||
ui->waveshape = synth.oscillators.array[i].osc;
|
||||
ui->volume = synth.oscillators.array[i].volume;
|
||||
}
|
||||
|
||||
int sound_played_count = 0;
|
||||
float temp_buffer[STREAM_BUFFER_SIZE];
|
||||
RingBuffer ring_buffer = ring_buffer_init(STREAM_BUFFER_SIZE);
|
||||
|
||||
|
||||
InitAudioDevice();
|
||||
SetMasterVolume(SYNTH_VOLUME);
|
||||
SetAudioStreamBufferSizeDefault(STREAM_BUFFER_SIZE);
|
||||
@@ -172,25 +368,28 @@ int main(int argc, char **argv) {
|
||||
// Update Audio states
|
||||
//----------------------------------------------------------------------------------
|
||||
// Fill ring buffer from current sound
|
||||
SynthSound* sound = synth.out_signal;
|
||||
assert(sound);
|
||||
|
||||
size_t size_for_buffer = 0;
|
||||
if (!ring_buffer.is_full && sound.sample_count != sound_played_count) {
|
||||
write_log("[INFO] IsFull:%d Samples:%zu Played:%zu\n",
|
||||
if (!ring_buffer.is_full && sound->sample_count != sound_played_count) {
|
||||
write_log("[INFO] IsFull:%d Samples:%zu Played:%d\n",
|
||||
ring_buffer.is_full,
|
||||
sound.sample_count,
|
||||
sound->sample_count,
|
||||
sound_played_count);
|
||||
|
||||
// how many samples need write
|
||||
size_t size_to_fill = 0;
|
||||
|
||||
if ((sound.sample_count - sound_played_count) > ring_buffer.size) {
|
||||
if ((sound->sample_count - sound_played_count) > ring_buffer.size) {
|
||||
size_to_fill = ring_buffer.size;
|
||||
} else {
|
||||
size_to_fill = sound.sample_count - sound_played_count;
|
||||
size_to_fill = sound->sample_count - sound_played_count;
|
||||
}
|
||||
|
||||
write_log("[INFO] SizeToFill:%zu\n", size_to_fill);
|
||||
for (size_t i = 0; i < size_to_fill; i++) {
|
||||
temp_buffer[i] = sound.samples[i];
|
||||
temp_buffer[i] = sound->samples[i];
|
||||
}
|
||||
|
||||
ring_buffer_write(&ring_buffer, temp_buffer, size_to_fill);
|
||||
@@ -207,7 +406,7 @@ int main(int argc, char **argv) {
|
||||
// can try the SetAudioStreamCallback
|
||||
UpdateAudioStream(synth_stream, temp_buffer, size_to_read);
|
||||
// can overwrite the ring buffer to avoid that
|
||||
if (sound.sample_count == sound_played_count) {
|
||||
if (sound->sample_count == sound_played_count) {
|
||||
ring_buffer_reset(&ring_buffer);
|
||||
}
|
||||
}
|
||||
@@ -215,10 +414,11 @@ int main(int argc, char **argv) {
|
||||
|
||||
// Update On Input
|
||||
//----------------------------------------------------------------------------------
|
||||
if (detect_note_pressed(¤t_note)) {
|
||||
sound = get_note_sound(current_note);
|
||||
Note* current_note = &synth.current_note;
|
||||
if (detect_note_pressed(current_note)) {
|
||||
*sound = get_note_sound(&synth, *current_note);
|
||||
sound_played_count = 0;
|
||||
write_log("Note played: %s\n", current_note.name);
|
||||
write_log("Note played: %s\n", current_note->name);
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
@@ -227,8 +427,10 @@ int main(int argc, char **argv) {
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(RAYWHITE);
|
||||
DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY);
|
||||
DrawFPS(0,0);
|
||||
DrawUi(&synth);
|
||||
DrawSignal(&synth);
|
||||
//DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY);
|
||||
//DrawFPS(0,0);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
@@ -240,13 +442,17 @@ int main(int argc, char **argv) {
|
||||
|
||||
NoteArray note_array = parse_notes(buf, strlen(buf));
|
||||
SynthSound* sounds = malloc(sizeof(SynthSound) * note_array.count);
|
||||
assert(sounds);
|
||||
|
||||
for (size_t i = 0; i < note_array.count; i++) {
|
||||
Note note = note_array.notes[i];
|
||||
sounds[i] = get_note_sound(note);
|
||||
sounds[i] = get_note_sound(&synth, note);
|
||||
}
|
||||
|
||||
SynthSound song = concat_sounds(sounds, note_array.count);
|
||||
uint16_t* song_pcm = malloc(sizeof(uint16_t) * song.sample_count);
|
||||
assert(song_pcm);
|
||||
|
||||
for (size_t i = 0; i < song.sample_count; i++) {
|
||||
song_pcm[i] = toInt16Sample(song.samples[i]);
|
||||
}
|
||||
|
||||
148
oscillator.c
148
oscillator.c
@@ -3,6 +3,8 @@
|
||||
#include "math.h"
|
||||
#include "stdlib.h"
|
||||
|
||||
#define TWO_PI 2*SYNTH_PI
|
||||
|
||||
static SynthSound get_init_samples(float duration) {
|
||||
size_t sample_count = (size_t)(duration * SAMPLE_RATE);
|
||||
float* samples = malloc(sizeof(float) * sample_count);
|
||||
@@ -23,42 +25,98 @@ static float pos(float hz, float x) {
|
||||
return fmodf(hz * x / SAMPLE_RATE, 1);
|
||||
}
|
||||
|
||||
static float sineosc(float hz, float x) {
|
||||
return sinf(x * (2.f * SYNTH_PI * hz / SAMPLE_RATE));
|
||||
static void sine_osc_phase_incr(Oscillator* osc) {
|
||||
osc->phase += osc->phase_dt;
|
||||
if (osc->phase >= TWO_PI)
|
||||
osc->phase -= TWO_PI;
|
||||
}
|
||||
|
||||
static void saw_osc_phase_incr(Oscillator* osc) {
|
||||
osc->phase += osc->phase_dt;
|
||||
if (osc->phase >= 1.0f)
|
||||
osc->phase -= 1.0f;
|
||||
}
|
||||
|
||||
static float calc_saw_phase_delta(float freq) {
|
||||
return freq / SAMPLE_RATE;
|
||||
}
|
||||
|
||||
static float calc_sine_phase_delta(float freq) {
|
||||
return (TWO_PI * freq) / SAMPLE_RATE;
|
||||
}
|
||||
|
||||
static float sineosc(Oscillator* osc) {
|
||||
float result = sinf(osc->phase);
|
||||
sine_osc_phase_incr(osc);
|
||||
return result;
|
||||
}
|
||||
|
||||
static float sign(float v) {
|
||||
return (v > 0.0) ? 1.f : -1.f;
|
||||
}
|
||||
|
||||
static float squareosc(float hz, float x) {
|
||||
return sign(sineosc(hz, x));
|
||||
static float squareosc(Oscillator* osc) {
|
||||
return sign(sineosc(osc));
|
||||
}
|
||||
|
||||
static float triangleosc(float hz, float x) {
|
||||
return 1.f - fabsf(pos(hz, x) - 0.5f) * 4.f;
|
||||
static float triangleosc(Oscillator* osc) {
|
||||
float result = 1.f - fabsf(osc->phase - 0.5f) * 4.f;
|
||||
saw_osc_phase_incr(osc);
|
||||
return result;
|
||||
}
|
||||
|
||||
static float sawosc(float hz, float x) {
|
||||
return pos(hz, x) * 2.f - 1.f;
|
||||
static float sawosc(Oscillator* osc) {
|
||||
float result = osc->phase * 2.f - 1.f;
|
||||
saw_osc_phase_incr(osc);
|
||||
return result;
|
||||
}
|
||||
|
||||
void osc_set_freq(Oscillator* osc, float freq) {
|
||||
osc->freq = freq;
|
||||
osc->phase = 0;
|
||||
switch (osc->osc)
|
||||
{
|
||||
case Sine:
|
||||
osc->phase_dt = calc_sine_phase_delta(freq);
|
||||
break;
|
||||
case Square:
|
||||
osc->phase_dt = calc_sine_phase_delta(freq);
|
||||
break;
|
||||
case Triangle:
|
||||
osc->phase_dt = calc_saw_phase_delta(freq);
|
||||
break;
|
||||
case Saw:
|
||||
osc->phase_dt = calc_saw_phase_delta(freq);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void osc_reset(Oscillator* osc) {
|
||||
osc->volume = 0;
|
||||
osc->phase = 0;
|
||||
osc->phase_dt = 0;
|
||||
}
|
||||
|
||||
float multiosc(OscillatorGenerationParameter param) {
|
||||
float osc_sample = 0.f;
|
||||
for (size_t i = 0; i < param.oscillators.count; i++) {
|
||||
OscillatorParameter osc = param.oscillators.array[i];
|
||||
switch (osc.osc) {
|
||||
Oscillator* osc = ¶m.oscillators.array[i];
|
||||
assert(osc);
|
||||
|
||||
switch (osc->osc) {
|
||||
case Sine:
|
||||
osc_sample += sineosc(osc.freq, param.sample);
|
||||
osc_sample += sineosc(osc) * osc->volume;
|
||||
break;
|
||||
case Triangle:
|
||||
osc_sample += triangleosc(osc.freq, param.sample);
|
||||
osc_sample += triangleosc(osc) * osc->volume;
|
||||
break;
|
||||
case Square:
|
||||
osc_sample += squareosc(osc.freq, param.sample);
|
||||
osc_sample += squareosc(osc) * osc->volume;
|
||||
break;
|
||||
case Saw:
|
||||
osc_sample += sawosc(osc.freq, param.sample);
|
||||
osc_sample += sawosc(osc) * osc->volume;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -66,64 +124,20 @@ float multiosc(OscillatorGenerationParameter param) {
|
||||
return osc_sample;
|
||||
}
|
||||
|
||||
SynthSound freq(float duration, OscillatorParameterList osc) {
|
||||
SynthSound samples = get_init_samples(duration);
|
||||
// SynthSound attack = get_attack_samples();
|
||||
|
||||
float* output = malloc(sizeof(float) * samples.sample_count);
|
||||
for (int i = 0; i < samples.sample_count; i++) {
|
||||
float sample = samples.samples[i];
|
||||
SynthSound freq(float duration, OscillatorArray osc) {
|
||||
size_t sample_count = (size_t)(duration * SAMPLE_RATE);
|
||||
|
||||
float* output = malloc(sizeof(float) * sample_count);
|
||||
for (size_t i = 0; i < sample_count; i++) {
|
||||
OscillatorGenerationParameter param = {
|
||||
.oscillators = osc,
|
||||
.sample = sample
|
||||
.oscillators = osc
|
||||
};
|
||||
output[i] = multiosc(param) * VOLUME;
|
||||
output[i] = multiosc(param);
|
||||
}
|
||||
|
||||
// create attack and release
|
||||
/*
|
||||
let adsrLength = Seq.length output
|
||||
let attackArray = attack |> Seq.take adsrLength
|
||||
let release = Seq.rev attackArray
|
||||
*/
|
||||
/*
|
||||
todo: I will change the ADSR approach to an explicit ADSR module(with it's own state)
|
||||
size_t adsr_length = samples.sample_count;
|
||||
float *attackArray = NULL, *releaseArray = NULL;
|
||||
|
||||
if (adsr_length > 0) {
|
||||
//todo: calloc
|
||||
attackArray = malloc(sizeof(float) * adsr_length);
|
||||
size_t attack_length = attack.sample_count < adsr_length
|
||||
? attack.sample_count
|
||||
: adsr_length;
|
||||
|
||||
memcpy(attackArray, attack.samples, attack_length);
|
||||
//todo: calloc
|
||||
releaseArray = malloc(sizeof(float) * adsr_length);
|
||||
memcpy(releaseArray, attackArray, attack_length);
|
||||
reverse_array(releaseArray, 0, adsr_length);
|
||||
}
|
||||
*/
|
||||
|
||||
// if (samples.sample_count > 1024) {
|
||||
// samples.sample_count = 1024;
|
||||
// }
|
||||
// //todo: move to somewhere
|
||||
// for (size_t i = 0; i < 1024; i++) {
|
||||
// synth_sound.samples[i] = 0.0f;
|
||||
// }
|
||||
|
||||
// for (size_t i = 0; i < samples.sample_count; i++) {
|
||||
// synth_sound.samples[i] = output[i];
|
||||
// }
|
||||
// synth_sound.sample_count = samples.sample_count;
|
||||
|
||||
|
||||
// return zipped array
|
||||
|
||||
SynthSound res = {
|
||||
.samples = output,
|
||||
.sample_count = samples.sample_count
|
||||
.sample_count = sample_count
|
||||
};
|
||||
|
||||
return res;
|
||||
|
||||
22
oscillator.h
22
oscillator.h
@@ -2,7 +2,7 @@
|
||||
#define OSCILLATOR_H
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#define WAVE_SHAPE_OPTIONS "Sine;Triangle;Sawtooth;Square"
|
||||
typedef enum {
|
||||
Sine,
|
||||
Triangle,
|
||||
@@ -10,23 +10,27 @@ typedef enum {
|
||||
Square
|
||||
} OscillatorType;
|
||||
|
||||
typedef struct OscillatorParameter {
|
||||
typedef struct Oscillator {
|
||||
OscillatorType osc;
|
||||
float freq;
|
||||
} OscillatorParameter;
|
||||
float volume;
|
||||
float phase;
|
||||
float phase_dt;
|
||||
} Oscillator;
|
||||
|
||||
typedef struct OscillatorParameterList {
|
||||
OscillatorParameter* array;
|
||||
typedef struct OscillatorArray {
|
||||
Oscillator* array;
|
||||
size_t count;
|
||||
} OscillatorParameterList;
|
||||
} OscillatorArray;
|
||||
|
||||
typedef struct OscillatorGenerationParameter {
|
||||
OscillatorParameterList oscillators;
|
||||
float sample;
|
||||
OscillatorArray oscillators;
|
||||
} OscillatorGenerationParameter;
|
||||
|
||||
void osc_set_freq(Oscillator* osc, float freq);
|
||||
void osc_reset(Oscillator* osc);
|
||||
float multiosc(OscillatorGenerationParameter param);
|
||||
SynthSound freq(float duration, OscillatorParameterList osc);
|
||||
SynthSound freq(float duration, OscillatorArray osc);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
|
||||
#define WINDOW_WIDTH 640
|
||||
#define WINDOW_HEIGHT 480
|
||||
#define OSCILLATOR_PANEL_WIDTH 200
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user