Compare commits
7 Commits
feature/os
...
v0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
2e4dc2c179
|
|||
| aaec53cfea | |||
| 7eb6a1755d | |||
| 97c743100a | |||
| 320a3cc8e0 | |||
|
a93278f705
|
|||
|
46ac7c9bba
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@
|
||||
.DS_Store
|
||||
/Debug/
|
||||
*.wav
|
||||
*.dSYM
|
||||
/lib
|
||||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@@ -3,6 +3,11 @@
|
||||
"readability/casting"
|
||||
],
|
||||
"files.associations": {
|
||||
"algorithm": "c"
|
||||
}
|
||||
"algorithm": "c",
|
||||
"__bit_reference": "c",
|
||||
"bitset": "c",
|
||||
"chrono": "c",
|
||||
"unordered_map": "c"
|
||||
},
|
||||
"FSharp.suggestGitignore": false,
|
||||
}
|
||||
36
.vscode/tasks.json
vendored
Normal file
36
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"type": "cppbuild",
|
||||
"label": "C/C++: clang сборка активного файла",
|
||||
"command": "/usr/bin/clang",
|
||||
"args": [
|
||||
"-fcolor-diagnostics",
|
||||
"-fansi-escape-codes",
|
||||
"-g",
|
||||
"${file}",
|
||||
"${fileDirname}/utils.c",
|
||||
"${fileDirname}/ring_buffer.c",
|
||||
"${fileDirname}/oscillator.c",
|
||||
"${fileDirname}/parser.c",
|
||||
"${fileDirname}/export.c",
|
||||
"-lm",
|
||||
"-lraylib",
|
||||
"-o",
|
||||
"${fileDirname}/bin/${fileBasenameNoExtension}"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${fileDirname}"
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$gcc"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"detail": "Задача создана отладчиком."
|
||||
}
|
||||
],
|
||||
"version": "2.0.0"
|
||||
}
|
||||
2
build.sh
2
build.sh
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
CC="${CXX:-cc}"
|
||||
$CC -Wall -std=c11 ./main.c ./parser.c -lm -o ./bin/main
|
||||
$CC -Wall -std=c11 ./main.c ./utils.c ./ring_buffer.c ./oscillator.c ./parser.c ./export.c -lm -lraylib -o ./bin/main
|
||||
|
||||
60
docs/ADSR.md
Normal file
60
docs/ADSR.md
Normal file
@@ -0,0 +1,60 @@
|
||||
Certainly! Here's an example of generating an ADSR (Attack, Decay, Sustain, Release) envelope in Python:
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Parameters
|
||||
sample_rate = 44100 # Sample rate in Hz
|
||||
duration = 5 # Duration of the envelope in seconds
|
||||
|
||||
# Time values
|
||||
num_samples = int(duration * sample_rate)
|
||||
time = np.arange(num_samples) / sample_rate
|
||||
|
||||
# ADSR parameters
|
||||
attack_time = 0.5 # Attack time in seconds
|
||||
decay_time = 0.3 # Decay time in seconds
|
||||
sustain_level = 0.6 # Sustain level (0 to 1)
|
||||
release_time = 1.0 # Release time in seconds
|
||||
|
||||
# Generate the ADSR envelope
|
||||
envelope = np.zeros(num_samples)
|
||||
attack_samples = int(attack_time * sample_rate)
|
||||
decay_samples = int(decay_time * sample_rate)
|
||||
release_samples = int(release_time * sample_rate)
|
||||
|
||||
# Attack phase
|
||||
envelope[:attack_samples] = np.linspace(0, 1, num=attack_samples)
|
||||
|
||||
# Decay phase
|
||||
decay_slope = (1 - sustain_level) / decay_samples
|
||||
envelope[attack_samples:attack_samples + decay_samples] = np.linspace(1, sustain_level, num=decay_samples) - decay_slope * np.arange(decay_samples)
|
||||
|
||||
# Sustain phase
|
||||
envelope[attack_samples + decay_samples:-release_samples] = sustain_level
|
||||
|
||||
# Release phase
|
||||
release_slope = sustain_level / release_samples
|
||||
envelope[-release_samples:] = sustain_level - release_slope * np.arange(release_samples)
|
||||
|
||||
# Normalize the envelope
|
||||
envelope /= np.max(envelope)
|
||||
|
||||
# Plot the envelope
|
||||
plt.plot(time, envelope)
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Amplitude')
|
||||
plt.title('ADSR Envelope')
|
||||
plt.show()
|
||||
```
|
||||
|
||||
In this example, we specify the sample rate and duration of the envelope. We define the ADSR parameters: attack time, decay time, sustain level, and release time.
|
||||
|
||||
We create an array to store the envelope values and initialize it with zeros. We calculate the number of samples for each phase based on the sample rate and duration.
|
||||
|
||||
We then calculate the envelope values for each phase. The attack phase increases linearly from 0 to 1. The decay phase decreases linearly from 1 to the sustain level. The sustain phase maintains a constant value equal to the sustain level. The release phase decreases linearly from the sustain level to 0.
|
||||
|
||||
After generating the envelope, we normalize it to ensure the maximum value is 1. Finally, we plot the envelope using Matplotlib.
|
||||
|
||||
You can modify the ADSR parameters to create different envelope shapes or experiment with adding modulation to create more dynamic and expressive sounds.
|
||||
39
docs/PhaseAccumulation.md
Normal file
39
docs/PhaseAccumulation.md
Normal file
@@ -0,0 +1,39 @@
|
||||
Certainly! Here's a simple example of phase accumulation in Python code:
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Parameters
|
||||
sample_rate = 44100 # Sample rate in Hz
|
||||
frequency = 440 # Frequency of the oscillator in Hz
|
||||
duration = 1 # Duration of the generated waveform in seconds
|
||||
|
||||
# Calculate the phase increment per sample
|
||||
phase_increment = 2 * np.pi * frequency / sample_rate
|
||||
|
||||
# Initialize phase and time arrays
|
||||
num_samples = int(duration * sample_rate)
|
||||
phase = np.zeros(num_samples)
|
||||
time = np.arange(num_samples) / sample_rate
|
||||
|
||||
# Perform phase accumulation
|
||||
for i in range(1, num_samples):
|
||||
phase[i] = phase[i - 1] + phase_increment
|
||||
|
||||
# Generate the waveform (sine wave) based on the accumulated phase
|
||||
waveform = np.sin(phase)
|
||||
|
||||
# Plot the waveform
|
||||
plt.plot(time, waveform)
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Amplitude')
|
||||
plt.title('Phase Accumulation Example - Sine Wave')
|
||||
plt.show()
|
||||
```
|
||||
|
||||
In this example, we specify the sample rate, frequency, and duration of the waveform. We calculate the phase increment per sample based on the desired frequency. Then, we initialize arrays to store the phase values and time values. By iterating through each sample, we perform phase accumulation by adding the phase increment to the previous phase value.
|
||||
|
||||
Finally, we generate a sine wave by taking the sine of the accumulated phase values. The resulting waveform is plotted using Matplotlib.
|
||||
|
||||
This code demonstrates the basic concept of phase accumulation, where the phase of an oscillator is incremented over time to generate a periodic waveform. You can modify the parameters, try different waveforms, or experiment with phase modulation to create more complex sounds.
|
||||
74
export.c
Normal file
74
export.c
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "export.h"
|
||||
|
||||
#include "stdio.h"
|
||||
#include "string.h"
|
||||
#include "settings.h"
|
||||
|
||||
uint16_t toInt16Sample(float sample) {
|
||||
return (uint16_t)(sample * 32767.f);
|
||||
}
|
||||
|
||||
static void write_file(char* filename, void* data, int size) {
|
||||
FILE* fp = fopen(filename, "wb"); // open file for writing in binary mode
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "Cannot open file: %s\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fwrite(data, size, 1, fp); // write data to file
|
||||
|
||||
fclose(fp); // close file
|
||||
}
|
||||
|
||||
void pack(uint16_t* d, size_t length) {
|
||||
size_t dataLength = length * 2;
|
||||
|
||||
int bytesPerSample = 2;
|
||||
int byteRate = SAMPLE_RATE * bytesPerSample;
|
||||
|
||||
size_t fileSize = 36 + dataLength;
|
||||
|
||||
uint8_t* buffer = (uint8_t*)malloc(fileSize);
|
||||
|
||||
int i = 0;
|
||||
|
||||
// RIFF header
|
||||
memcpy(buffer + i, "RIFF", 4);
|
||||
i += 4;
|
||||
memcpy(buffer + i, &fileSize, 4);
|
||||
i += 4;
|
||||
memcpy(buffer + i, "WAVE", 4);
|
||||
i += 4;
|
||||
|
||||
// fmt subchunk
|
||||
memcpy(buffer + i, "fmt ", 4);
|
||||
i += 4;
|
||||
int fmtSize = 16;
|
||||
memcpy(buffer + i, &fmtSize, 4);
|
||||
i += 4;
|
||||
uint16_t audioFormat = 1;
|
||||
memcpy(buffer + i, &audioFormat, 2);
|
||||
i += 2;
|
||||
uint16_t numChannels = 1;
|
||||
memcpy(buffer + i, &numChannels, 2);
|
||||
i += 2;
|
||||
int sampleRate = (int)SAMPLE_RATE;
|
||||
memcpy(buffer + i, &sampleRate, 4);
|
||||
i += 4;
|
||||
memcpy(buffer + i, &byteRate, 4);
|
||||
i += 4;
|
||||
memcpy(buffer + i, &bytesPerSample, 2);
|
||||
i += 2;
|
||||
int bitsPerSample = bytesPerSample * 8;
|
||||
memcpy(buffer + i, &bitsPerSample, 2);
|
||||
i += 2;
|
||||
|
||||
// data subchunk
|
||||
memcpy(buffer + i, "data", 4);
|
||||
i += 4;
|
||||
memcpy(buffer + i, &dataLength, 4);
|
||||
i += 4;
|
||||
memcpy(buffer + i, d, dataLength);
|
||||
|
||||
write_file("output.wav", buffer, fileSize);
|
||||
}
|
||||
9
export.h
Normal file
9
export.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef EXPORT_H
|
||||
#define EXPORT_H
|
||||
|
||||
#include "stdlib.h"
|
||||
|
||||
uint16_t toInt16Sample(float sample);
|
||||
void pack(uint16_t* d, size_t length);
|
||||
|
||||
#endif
|
||||
643
main.c
643
main.c
@@ -2,219 +2,38 @@
|
||||
#include "stdio.h"
|
||||
#include "string.h"
|
||||
#include "math.h"
|
||||
|
||||
#include "parser.h"
|
||||
#include "utils.h"
|
||||
#include "ring_buffer.h"
|
||||
#include "settings.h"
|
||||
#include "oscillator.h"
|
||||
#include "export.h"
|
||||
|
||||
#define SAMPLE_RATE 48000.f
|
||||
#define BPM 120.f
|
||||
#define BEAT_DURATION 60.f/BPM
|
||||
#define PITCH_STANDARD 440.f
|
||||
#define VOLUME 0.5f
|
||||
#define ATTACK_MS 100.f
|
||||
|
||||
#define PI 3.1415926535f
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// General Sound
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
typedef struct Sound {
|
||||
float* samples;
|
||||
size_t sample_count;
|
||||
} Sound;
|
||||
|
||||
// frees the original sounds
|
||||
Sound concat_sounds(Sound* sounds, size_t count) {
|
||||
size_t total_count = 0;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
total_count += sounds[i].sample_count;
|
||||
}
|
||||
// array to hold the result
|
||||
float* total = malloc(total_count * sizeof(float));
|
||||
|
||||
size_t current_count = 0;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
memcpy(total + current_count,
|
||||
sounds[i].samples,
|
||||
sounds[i].sample_count * sizeof(float));
|
||||
current_count += sounds[i].sample_count;
|
||||
|
||||
free(sounds[i].samples);
|
||||
}
|
||||
|
||||
Sound result = {
|
||||
.samples = total,
|
||||
.sample_count = total_count
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Oscillator
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
typedef enum {
|
||||
Sine,
|
||||
Triangle,
|
||||
Saw,
|
||||
Square
|
||||
} OscillatorType;
|
||||
|
||||
typedef struct OscillatorParameter {
|
||||
OscillatorType osc;
|
||||
float freq;
|
||||
} OscillatorParameter;
|
||||
|
||||
typedef struct OscillatorParameterList {
|
||||
OscillatorParameter* array;
|
||||
size_t count;
|
||||
} OscillatorParameterList;
|
||||
|
||||
typedef struct OscillatorGenerationParameter {
|
||||
OscillatorParameterList oscillators;
|
||||
float sample;
|
||||
} OscillatorGenerationParameter;
|
||||
|
||||
static Sound get_init_samples(float duration) {
|
||||
size_t sample_count = (size_t)(duration * SAMPLE_RATE);
|
||||
float* samples = malloc(sizeof(float) * sample_count);
|
||||
|
||||
for (double i = 0.0; i < duration * SAMPLE_RATE; i++) {
|
||||
samples[(int)i] = i;
|
||||
}
|
||||
|
||||
Sound res = {
|
||||
.samples = samples,
|
||||
.sample_count = sample_count
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static float pos(float hz, float x) {
|
||||
return fmodf(hz * x / SAMPLE_RATE, 1);
|
||||
}
|
||||
|
||||
float sineosc(float hz, float x) {
|
||||
return sinf(x * (2.f * PI * hz / SAMPLE_RATE));
|
||||
}
|
||||
|
||||
static float sign(float v) {
|
||||
return (v > 0.0) ? 1.f : -1.f;
|
||||
}
|
||||
|
||||
float squareosc(float hz, float x) {
|
||||
return sign(sineosc(hz, x));
|
||||
}
|
||||
|
||||
float triangleosc(float hz, float x) {
|
||||
return 1.f - fabsf(pos(hz, x) - 0.5f) * 4.f;
|
||||
}
|
||||
|
||||
float sawosc(float hz, float x) {
|
||||
return pos(hz, x) * 2.f - 1.f;
|
||||
}
|
||||
|
||||
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) {
|
||||
case Sine:
|
||||
osc_sample += sineosc(osc.freq, param.sample);
|
||||
break;
|
||||
case Triangle:
|
||||
osc_sample += triangleosc(osc.freq, param.sample);
|
||||
break;
|
||||
case Square:
|
||||
osc_sample += squareosc(osc.freq, param.sample);
|
||||
break;
|
||||
case Saw:
|
||||
osc_sample += sawosc(osc.freq, param.sample);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return osc_sample;
|
||||
}
|
||||
|
||||
static Sound freq(float duration, OscillatorParameterList osc) {
|
||||
Sound samples = get_init_samples(duration);
|
||||
// Sound 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];
|
||||
OscillatorGenerationParameter param = {
|
||||
.oscillators = osc,
|
||||
.sample = sample
|
||||
};
|
||||
output[i] = multiosc(param) * VOLUME;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
*/
|
||||
|
||||
// return zipped array
|
||||
Sound res = {
|
||||
.samples = output,
|
||||
.sample_count = samples.sample_count
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
static Sound get_attack_samples() {
|
||||
float attack_time = 0.001 * ATTACK_MS;
|
||||
size_t sample_count = (size_t)(attack_time * SAMPLE_RATE);
|
||||
float* attack = malloc(sizeof(float) * sample_count);
|
||||
float samples_to_rise = SAMPLE_RATE * attack_time;
|
||||
float rising_delta = 1.0 / samples_to_rise;
|
||||
float i = 0.0;
|
||||
|
||||
for (int j = 0; j < sample_count; j++) {
|
||||
i += rising_delta;
|
||||
attack[j] = fmin(i, 1.0);
|
||||
}
|
||||
|
||||
Sound res = {
|
||||
.samples = attack,
|
||||
.sample_count = sample_count
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
*/
|
||||
#include "raylib.h"
|
||||
#define RAYGUI_IMPLEMENTATION
|
||||
#include "raygui.h"
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Synth
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
static size_t get_semitone_shift_internal(char* root_note, char* target_note) {
|
||||
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" };
|
||||
|
||||
@@ -258,139 +77,359 @@ static size_t get_semitone_shift_internal(char* root_note, char* target_note) {
|
||||
(target_pitch_class - root_pitch_class);
|
||||
}
|
||||
|
||||
static float get_hz_by_semitone(size_t semitone) {
|
||||
static float get_hz_by_semitone(int semitone) {
|
||||
return PITCH_STANDARD * powf(powf(2.f, (1.f / 12.f)), semitone);
|
||||
}
|
||||
|
||||
size_t get_semitone_shift(char* target_note) {
|
||||
int get_semitone_shift(char* target_note) {
|
||||
return get_semitone_shift_internal("A4", target_note);
|
||||
}
|
||||
|
||||
Sound note(size_t semitone, float beats) {
|
||||
static OscillatorArray init_osc_array() {
|
||||
Oscillator first = {
|
||||
.osc = Square,
|
||||
.freq = 440.f,
|
||||
.volume = VOLUME
|
||||
};
|
||||
|
||||
Oscillator* oscArray = malloc(sizeof(Oscillator*) * 1);
|
||||
oscArray[0] = first;
|
||||
|
||||
OscillatorArray oscillators = {
|
||||
.array = oscArray,
|
||||
.count = 1
|
||||
};
|
||||
|
||||
return oscillators;
|
||||
}
|
||||
|
||||
SynthSound note(Synth* synth, int semitone, float beats) {
|
||||
float hz = get_hz_by_semitone(semitone);
|
||||
float duration = beats * BEAT_DURATION;
|
||||
|
||||
OscillatorParameter first = {
|
||||
.osc = Saw,
|
||||
.freq = hz/4.f
|
||||
};
|
||||
|
||||
OscillatorParameter second = {
|
||||
.osc = Saw,
|
||||
.freq = hz + 0.5
|
||||
};
|
||||
|
||||
OscillatorParameter third = {
|
||||
.osc = Saw,
|
||||
.freq = hz - 1.f
|
||||
};
|
||||
|
||||
OscillatorParameter oscArray[] = { first, second, third };
|
||||
OscillatorParameterList parameters = {
|
||||
.array = oscArray,
|
||||
.count = 3
|
||||
};
|
||||
|
||||
return freq(duration, parameters);
|
||||
// will change after oscillator starts to be more autonomous
|
||||
for (size_t i = 0; i < synth->oscillators.count; i++) {
|
||||
synth->oscillators.array[i].freq = hz;
|
||||
}
|
||||
|
||||
Sound get_note_sound(Note input) {
|
||||
return freq(duration, synth->oscillators);
|
||||
}
|
||||
|
||||
SynthSound get_note_sound(Synth* synth, Note input) {
|
||||
float length = 1.f / input.length;
|
||||
size_t semitone_shift = get_semitone_shift(input.name);
|
||||
return note(semitone_shift, length);
|
||||
int semitone_shift = get_semitone_shift(input.name);
|
||||
return note(synth, semitone_shift, length);
|
||||
}
|
||||
//-------------------------------------------------------
|
||||
|
||||
size_t detect_note_pressed(Note* note) {
|
||||
size_t is_pressed = 0;
|
||||
note->length = 8;
|
||||
if (IsKeyPressed(KEY_A)) {
|
||||
strcpy(note->name, "A4");
|
||||
is_pressed = 1;
|
||||
}
|
||||
if (IsKeyPressed(KEY_B)) {
|
||||
strcpy(note->name, "B4");
|
||||
is_pressed = 1;
|
||||
}
|
||||
if (IsKeyPressed(KEY_C)) {
|
||||
strcpy(note->name, "C4");
|
||||
is_pressed = 1;
|
||||
}
|
||||
if (IsKeyPressed(KEY_D)) {
|
||||
strcpy(note->name, "D4");
|
||||
is_pressed = 1;
|
||||
}
|
||||
if (IsKeyPressed(KEY_E)) {
|
||||
strcpy(note->name, "E4");
|
||||
is_pressed = 1;
|
||||
}
|
||||
if (IsKeyPressed(KEY_F)) {
|
||||
strcpy(note->name, "F4");
|
||||
is_pressed = 1;
|
||||
}
|
||||
if (IsKeyPressed(KEY_G)) {
|
||||
strcpy(note->name, "G4");
|
||||
is_pressed = 1;
|
||||
}
|
||||
return is_pressed;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Wav File
|
||||
// GUI
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
static uint16_t toInt16Sample(float sample) {
|
||||
return (uint16_t)(sample * 32767.f);
|
||||
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;
|
||||
}
|
||||
|
||||
static void write_file(char* filename, void* data, int size) {
|
||||
FILE* fp = fopen(filename, "wb"); // open file for writing in binary mode
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "Cannot open file: %s\n", filename);
|
||||
exit(1);
|
||||
// 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];
|
||||
Oscillator* osc = &synth->oscillators.array[ui_osc_i];
|
||||
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;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
fwrite(data, size, 1, fp); // write data to file
|
||||
// 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];
|
||||
Oscillator* osc = &synth->oscillators.array[ui_osc_i];
|
||||
// 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
|
||||
);
|
||||
|
||||
fclose(fp); // close file
|
||||
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 pack(uint16_t* d, size_t length) {
|
||||
size_t dataLength = length * 2;
|
||||
|
||||
int bytesPerSample = 2;
|
||||
int byteRate = SAMPLE_RATE * bytesPerSample;
|
||||
|
||||
size_t fileSize = 36 + dataLength;
|
||||
|
||||
uint8_t* buffer = (uint8_t*)malloc(fileSize);
|
||||
|
||||
int i = 0;
|
||||
|
||||
// RIFF header
|
||||
memcpy(buffer + i, "RIFF", 4);
|
||||
i += 4;
|
||||
memcpy(buffer + i, &fileSize, 4);
|
||||
i += 4;
|
||||
memcpy(buffer + i, "WAVE", 4);
|
||||
i += 4;
|
||||
|
||||
// fmt subchunk
|
||||
memcpy(buffer + i, "fmt ", 4);
|
||||
i += 4;
|
||||
int fmtSize = 16;
|
||||
memcpy(buffer + i, &fmtSize, 4);
|
||||
i += 4;
|
||||
uint16_t audioFormat = 1;
|
||||
memcpy(buffer + i, &audioFormat, 2);
|
||||
i += 2;
|
||||
uint16_t numChannels = 1;
|
||||
memcpy(buffer + i, &numChannels, 2);
|
||||
i += 2;
|
||||
int sampleRate = (int)SAMPLE_RATE;
|
||||
memcpy(buffer + i, &sampleRate, 4);
|
||||
i += 4;
|
||||
memcpy(buffer + i, &byteRate, 4);
|
||||
i += 4;
|
||||
memcpy(buffer + i, &bytesPerSample, 2);
|
||||
i += 2;
|
||||
int bitsPerSample = bytesPerSample * 8;
|
||||
memcpy(buffer + i, &bitsPerSample, 2);
|
||||
i += 2;
|
||||
|
||||
// data subchunk
|
||||
memcpy(buffer + i, "data", 4);
|
||||
i += 4;
|
||||
memcpy(buffer + i, &dataLength, 4);
|
||||
i += 4;
|
||||
memcpy(buffer + i, d, dataLength);
|
||||
|
||||
write_file("output.wav", buffer, fileSize);
|
||||
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.2");
|
||||
SetTargetFPS(60);
|
||||
|
||||
//todo: move that variables to Synth declaration
|
||||
Note g_current_note = {
|
||||
.length = 1,
|
||||
.name = malloc(sizeof(char) * 3)
|
||||
};
|
||||
|
||||
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];
|
||||
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);
|
||||
AudioStream synth_stream = LoadAudioStream(SAMPLE_RATE, sizeof(float) * 8, 1);
|
||||
SetAudioStreamVolume(synth_stream, 0.5f);
|
||||
|
||||
PlayAudioStream(synth_stream);
|
||||
|
||||
// Main game loop
|
||||
while (!WindowShouldClose()) // Detect window close button or ESC key
|
||||
{
|
||||
// Update Audio states
|
||||
//----------------------------------------------------------------------------------
|
||||
// Fill ring buffer from current sound
|
||||
SynthSound* sound = synth.out_signal;
|
||||
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",
|
||||
ring_buffer.is_full,
|
||||
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) {
|
||||
size_to_fill = ring_buffer.size;
|
||||
} else {
|
||||
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];
|
||||
}
|
||||
|
||||
ring_buffer_write(&ring_buffer, temp_buffer, size_to_fill);
|
||||
sound_played_count += size_to_fill;
|
||||
}
|
||||
|
||||
// Play ring-buffered audio
|
||||
if (IsAudioStreamProcessed(synth_stream) && !ring_buffer.is_empty) {
|
||||
size_t size_to_read = ring_buffer_size(&ring_buffer);
|
||||
|
||||
write_log("Samples to play:%zu \n", size_to_read);
|
||||
//todo: try to start reading directly from ring buffer, avoiding temp_buffer
|
||||
ring_buffer_read(&ring_buffer, temp_buffer, size_to_read);
|
||||
// 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) {
|
||||
ring_buffer_reset(&ring_buffer);
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Update On Input
|
||||
//----------------------------------------------------------------------------------
|
||||
Note current_note = synth.current_note;
|
||||
if (detect_note_pressed(¤t_note)) {
|
||||
*sound = get_note_sound(&synth, current_note);
|
||||
sound_played_count = 0;
|
||||
write_log("Note played: %s\n", current_note.name);
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(RAYWHITE);
|
||||
DrawUi(&synth);
|
||||
DrawSignal(&synth);
|
||||
//DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY);
|
||||
//DrawFPS(0,0);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
char* input = "A4-4 A4-4 A4-4 A4-4 A4-2 A4-4 A4-4 A4-4 A4-4 A4-4 A4-2 D5-4 D5-4 D5-4 D5-4 D5-4 D5-4 D5-2 C5-4 C5-4 C5-4 C5-4 C5-4 C5-4 C5-2 G4-2 ";
|
||||
char* buf = malloc(strlen(input) + 1);
|
||||
strcpy(buf, input);
|
||||
|
||||
NoteArray note_array = parse_notes(buf, strlen(buf));
|
||||
Sound* sounds = malloc(sizeof(Sound) * note_array.count);
|
||||
SynthSound* sounds = malloc(sizeof(SynthSound) * note_array.count);
|
||||
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);
|
||||
}
|
||||
|
||||
Sound song = concat_sounds(sounds, note_array.count);
|
||||
SynthSound song = concat_sounds(sounds, note_array.count);
|
||||
uint16_t* song_pcm = malloc(sizeof(uint16_t) * song.sample_count);
|
||||
for (size_t i = 0; i < song.sample_count; i++) {
|
||||
song_pcm[i] = toInt16Sample(song.samples[i]);
|
||||
@@ -398,5 +437,13 @@ int main(int argc, char **argv) {
|
||||
|
||||
pack(song_pcm, song.sample_count);
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
StopAudioStream(synth_stream);
|
||||
UnloadAudioStream(synth_stream);
|
||||
CloseAudioDevice();
|
||||
CloseWindow(); // Close window and OpenGL context
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
153
oscillator.c
Normal file
153
oscillator.c
Normal file
@@ -0,0 +1,153 @@
|
||||
#include "oscillator.h"
|
||||
#include "settings.h"
|
||||
#include "math.h"
|
||||
#include "stdlib.h"
|
||||
|
||||
static SynthSound get_init_samples(float duration) {
|
||||
size_t sample_count = (size_t)(duration * SAMPLE_RATE);
|
||||
float* samples = malloc(sizeof(float) * sample_count);
|
||||
|
||||
for (double i = 0.0; i < duration * SAMPLE_RATE; i++) {
|
||||
samples[(int)i] = i;
|
||||
}
|
||||
|
||||
SynthSound res = {
|
||||
.samples = samples,
|
||||
.sample_count = sample_count
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
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 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 triangleosc(float hz, float x) {
|
||||
return 1.f - fabsf(pos(hz, x) - 0.5f) * 4.f;
|
||||
}
|
||||
|
||||
static float sawosc(float hz, float x) {
|
||||
return pos(hz, x) * 2.f - 1.f;
|
||||
}
|
||||
|
||||
float multiosc(OscillatorGenerationParameter param) {
|
||||
float osc_sample = 0.f;
|
||||
for (size_t i = 0; i < param.oscillators.count; i++) {
|
||||
Oscillator osc = param.oscillators.array[i];
|
||||
switch (osc.osc) {
|
||||
case Sine:
|
||||
osc_sample += sineosc(osc.freq, param.sample) * osc.volume;
|
||||
break;
|
||||
case Triangle:
|
||||
osc_sample += triangleosc(osc.freq, param.sample) * osc.volume;
|
||||
break;
|
||||
case Square:
|
||||
osc_sample += squareosc(osc.freq, param.sample) * osc.volume;
|
||||
break;
|
||||
case Saw:
|
||||
osc_sample += sawosc(osc.freq, param.sample) * osc.volume;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return osc_sample;
|
||||
}
|
||||
|
||||
SynthSound freq(float duration, OscillatorArray 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];
|
||||
OscillatorGenerationParameter param = {
|
||||
.oscillators = osc,
|
||||
.sample = sample
|
||||
};
|
||||
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
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
static SynthSound get_attack_samples() {
|
||||
float attack_time = 0.001 * ATTACK_MS;
|
||||
size_t sample_count = (size_t)(attack_time * SAMPLE_RATE);
|
||||
float* attack = malloc(sizeof(float) * sample_count);
|
||||
float samples_to_rise = SAMPLE_RATE * attack_time;
|
||||
float rising_delta = 1.0 / samples_to_rise;
|
||||
float i = 0.0;
|
||||
|
||||
for (int j = 0; j < sample_count; j++) {
|
||||
i += rising_delta;
|
||||
attack[j] = fmin(i, 1.0);
|
||||
}
|
||||
|
||||
SynthSound res = {
|
||||
.samples = attack,
|
||||
.sample_count = sample_count
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
*/
|
||||
34
oscillator.h
Normal file
34
oscillator.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef OSCILLATOR_H
|
||||
#define OSCILLATOR_H
|
||||
|
||||
#include "utils.h"
|
||||
#define WAVE_SHAPE_OPTIONS "Sine;Triangle;Sawtooth;Square"
|
||||
typedef enum {
|
||||
Sine,
|
||||
Triangle,
|
||||
Saw,
|
||||
Square
|
||||
} OscillatorType;
|
||||
|
||||
typedef struct Oscillator {
|
||||
OscillatorType osc;
|
||||
float freq;
|
||||
float volume;
|
||||
} Oscillator;
|
||||
|
||||
typedef struct OscillatorArray {
|
||||
Oscillator* array;
|
||||
size_t count;
|
||||
} OscillatorArray;
|
||||
|
||||
typedef struct OscillatorGenerationParameter {
|
||||
OscillatorArray oscillators;
|
||||
float sample;
|
||||
} OscillatorGenerationParameter;
|
||||
|
||||
float multiosc(OscillatorGenerationParameter param);
|
||||
SynthSound freq(float duration, OscillatorArray osc);
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
99
ring_buffer.c
Normal file
99
ring_buffer.c
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "ring_buffer.h"
|
||||
#include "utils.h"
|
||||
|
||||
RingBuffer ring_buffer_init(size_t buffer_size) {
|
||||
RingBuffer buffer = {
|
||||
.items = calloc(buffer_size, sizeof(float)),
|
||||
.head = 0,
|
||||
.tail = 0,
|
||||
.is_full = 0,
|
||||
.is_empty = 1,
|
||||
.size = buffer_size
|
||||
};
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void ring_buffer_reset(RingBuffer* me) {
|
||||
me->head = 0;
|
||||
me->tail = 0;
|
||||
me->is_full = 0;
|
||||
}
|
||||
|
||||
// +
|
||||
static void advance_pointer(RingBuffer* me) {
|
||||
if(me->is_full) {
|
||||
me->tail++;
|
||||
if (me->tail == me->size) {
|
||||
me->tail = 0;
|
||||
}
|
||||
}
|
||||
me->head++;
|
||||
if (me->head == me->size) {
|
||||
me->head = 0;
|
||||
}
|
||||
size_t is_full = me->head == me->tail ? 1 : 0;
|
||||
me->is_full = is_full;
|
||||
}
|
||||
|
||||
// -
|
||||
static void retreat_pointer(RingBuffer* me) {
|
||||
me->is_full = 0;
|
||||
me->tail++;
|
||||
if (me->tail == me->size) {
|
||||
me->tail = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ring_buffer_write(RingBuffer* buffer, float* data, size_t count) {
|
||||
if (buffer->is_full || buffer->head + count > buffer->size) {
|
||||
write_log("[WARN] Trying to overfill the ring buffer: \n\tIsFull:%d\n\tHead:%zu\n\tCount:%zu\n\t",
|
||||
buffer->is_full,
|
||||
buffer->head,
|
||||
count);
|
||||
return;
|
||||
}
|
||||
buffer->is_empty = 0;
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
buffer->items[buffer->head] = data[i];
|
||||
advance_pointer(buffer);
|
||||
}
|
||||
//me->is_empty = is_full && (me->head == me->tail);
|
||||
}
|
||||
|
||||
int ring_buffer_read(RingBuffer* buffer, float* output, size_t count) {
|
||||
if (buffer->is_empty) {
|
||||
write_log("[WARN] Trying to read empty buffer");
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
output[i] = buffer->items[buffer->tail];
|
||||
retreat_pointer(buffer);
|
||||
}
|
||||
buffer->is_empty = !buffer->is_full && (buffer->head == buffer->tail);
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t ring_buffer_size(RingBuffer* buffer) {
|
||||
size_t size = buffer->size;
|
||||
if(!buffer->is_full) {
|
||||
if(buffer->head >= buffer->tail) {
|
||||
size = (buffer->head - buffer->tail);
|
||||
}
|
||||
else {
|
||||
size = (buffer->size + buffer->head - buffer->tail);
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void ring_buffer_print(RingBuffer* me) {
|
||||
write_log("[INFO] The ring buffer: \n\tIsFull:%d\n\tIsEmpty:%d\n\tHead:%zu\n\tTail:%zu\n\t",
|
||||
me->is_full,
|
||||
me->is_empty,
|
||||
me->head,
|
||||
me->tail);
|
||||
}
|
||||
22
ring_buffer.h
Normal file
22
ring_buffer.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef RING_BUFFER_H
|
||||
#define RING_BUFFER_H
|
||||
|
||||
#include "stdlib.h"
|
||||
|
||||
typedef struct RingBuffer {
|
||||
float* items;
|
||||
size_t head;
|
||||
size_t tail;
|
||||
int is_full;
|
||||
int is_empty;
|
||||
size_t size;
|
||||
} RingBuffer;
|
||||
|
||||
RingBuffer ring_buffer_init(size_t buffer_size);
|
||||
void ring_buffer_reset(RingBuffer* me);
|
||||
void ring_buffer_write(RingBuffer* buffer, float* data, size_t count);
|
||||
int ring_buffer_read(RingBuffer* buffer, float* output, size_t count);
|
||||
size_t ring_buffer_size(RingBuffer* buffer);
|
||||
void ring_buffer_print(RingBuffer* me);
|
||||
|
||||
#endif
|
||||
19
settings.h
Normal file
19
settings.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef SETTINGS_H
|
||||
#define SETTINGS_H
|
||||
|
||||
#define SAMPLE_RATE 48000.f
|
||||
#define BPM 120.f
|
||||
#define BEAT_DURATION 60.f/BPM
|
||||
#define PITCH_STANDARD 440.f
|
||||
#define VOLUME 0.5f
|
||||
#define ATTACK_MS 100.f
|
||||
#define STREAM_BUFFER_SIZE 4096
|
||||
|
||||
#define SYNTH_PI 3.1415926535f
|
||||
#define SYNTH_VOLUME 0.5f
|
||||
|
||||
#define WINDOW_WIDTH 640
|
||||
#define WINDOW_HEIGHT 480
|
||||
#define OSCILLATOR_PANEL_WIDTH 200
|
||||
|
||||
#endif
|
||||
30
utils.c
Normal file
30
utils.c
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "utils.h"
|
||||
#include "stdlib.h"
|
||||
#include "string.h"
|
||||
|
||||
// frees the original sounds
|
||||
SynthSound concat_sounds(SynthSound* sounds, size_t count) {
|
||||
size_t total_count = 0;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
total_count += sounds[i].sample_count;
|
||||
}
|
||||
// array to hold the result
|
||||
float* total = malloc(total_count * sizeof(float));
|
||||
|
||||
size_t current_count = 0;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
memcpy(total + current_count,
|
||||
sounds[i].samples,
|
||||
sounds[i].sample_count * sizeof(float));
|
||||
current_count += sounds[i].sample_count;
|
||||
|
||||
free(sounds[i].samples);
|
||||
}
|
||||
|
||||
SynthSound result = {
|
||||
.samples = total,
|
||||
.sample_count = total_count
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
22
utils.h
Normal file
22
utils.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include "stdio.h"
|
||||
|
||||
#define write_log(format,args...) do { \
|
||||
printf(format, ## args); \
|
||||
} while(0)
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// General SynthSound
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
typedef struct SynthSound {
|
||||
float* samples;
|
||||
size_t sample_count;
|
||||
} SynthSound;
|
||||
|
||||
// frees the original sounds
|
||||
SynthSound concat_sounds(SynthSound* sounds, size_t count);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user