Compare commits
3 Commits
90e2c72a89
...
3a0eb52fb1
| Author | SHA1 | Date | |
|---|---|---|---|
|
3a0eb52fb1
|
|||
|
a93278f705
|
|||
|
46ac7c9bba
|
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 ./parser.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.
|
||||
71
main.c
71
main.c
@@ -4,6 +4,8 @@
|
||||
#include "math.h"
|
||||
#include "parser.h"
|
||||
|
||||
#include "raylib.h"
|
||||
|
||||
#define SAMPLE_RATE 48000.f
|
||||
#define BPM 120.f
|
||||
#define BEAT_DURATION 60.f/BPM
|
||||
@@ -11,19 +13,19 @@
|
||||
#define VOLUME 0.5f
|
||||
#define ATTACK_MS 100.f
|
||||
|
||||
#define PI 3.1415926535f
|
||||
#define SYNTH_PI 3.1415926535f
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// General Sound
|
||||
// General SynthSound
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
typedef struct Sound {
|
||||
typedef struct SynthSound {
|
||||
float* samples;
|
||||
size_t sample_count;
|
||||
} Sound;
|
||||
} SynthSound;
|
||||
|
||||
// frees the original sounds
|
||||
Sound concat_sounds(Sound* sounds, size_t count) {
|
||||
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;
|
||||
@@ -41,7 +43,7 @@ Sound concat_sounds(Sound* sounds, size_t count) {
|
||||
free(sounds[i].samples);
|
||||
}
|
||||
|
||||
Sound result = {
|
||||
SynthSound result = {
|
||||
.samples = total,
|
||||
.sample_count = total_count
|
||||
};
|
||||
@@ -75,7 +77,7 @@ typedef struct OscillatorGenerationParameter {
|
||||
float sample;
|
||||
} OscillatorGenerationParameter;
|
||||
|
||||
static Sound get_init_samples(float duration) {
|
||||
static SynthSound get_init_samples(float duration) {
|
||||
size_t sample_count = (size_t)(duration * SAMPLE_RATE);
|
||||
float* samples = malloc(sizeof(float) * sample_count);
|
||||
|
||||
@@ -83,7 +85,7 @@ static Sound get_init_samples(float duration) {
|
||||
samples[(int)i] = i;
|
||||
}
|
||||
|
||||
Sound res = {
|
||||
SynthSound res = {
|
||||
.samples = samples,
|
||||
.sample_count = sample_count
|
||||
};
|
||||
@@ -96,7 +98,7 @@ static float pos(float hz, float x) {
|
||||
}
|
||||
|
||||
float sineosc(float hz, float x) {
|
||||
return sinf(x * (2.f * PI * hz / SAMPLE_RATE));
|
||||
return sinf(x * (2.f * SYNTH_PI * hz / SAMPLE_RATE));
|
||||
}
|
||||
|
||||
static float sign(float v) {
|
||||
@@ -138,9 +140,9 @@ float multiosc(OscillatorGenerationParameter param) {
|
||||
return osc_sample;
|
||||
}
|
||||
|
||||
static Sound freq(float duration, OscillatorParameterList osc) {
|
||||
Sound samples = get_init_samples(duration);
|
||||
// Sound attack = get_attack_samples();
|
||||
static 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++) {
|
||||
@@ -179,7 +181,7 @@ static Sound freq(float duration, OscillatorParameterList osc) {
|
||||
*/
|
||||
|
||||
// return zipped array
|
||||
Sound res = {
|
||||
SynthSound res = {
|
||||
.samples = output,
|
||||
.sample_count = samples.sample_count
|
||||
};
|
||||
@@ -188,7 +190,7 @@ static Sound freq(float duration, OscillatorParameterList osc) {
|
||||
}
|
||||
|
||||
/*
|
||||
static Sound get_attack_samples() {
|
||||
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);
|
||||
@@ -201,7 +203,7 @@ static Sound get_attack_samples() {
|
||||
attack[j] = fmin(i, 1.0);
|
||||
}
|
||||
|
||||
Sound res = {
|
||||
SynthSound res = {
|
||||
.samples = attack,
|
||||
.sample_count = sample_count
|
||||
};
|
||||
@@ -266,7 +268,7 @@ size_t get_semitone_shift(char* target_note) {
|
||||
return get_semitone_shift_internal("A4", target_note);
|
||||
}
|
||||
|
||||
Sound note(size_t semitone, float beats) {
|
||||
SynthSound note(size_t semitone, float beats) {
|
||||
float hz = get_hz_by_semitone(semitone);
|
||||
float duration = beats * BEAT_DURATION;
|
||||
|
||||
@@ -294,7 +296,7 @@ Sound note(size_t semitone, float beats) {
|
||||
return freq(duration, parameters);
|
||||
}
|
||||
|
||||
Sound get_note_sound(Note input) {
|
||||
SynthSound get_note_sound(Note input) {
|
||||
float length = 1.f / input.length;
|
||||
size_t semitone_shift = get_semitone_shift(input.name);
|
||||
return note(semitone_shift, length);
|
||||
@@ -379,18 +381,44 @@ void pack(uint16_t* d, size_t length) {
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const size_t width = 1280;
|
||||
const size_t height = 720;
|
||||
|
||||
InitWindow(width, height, "SeeSynth - v0.1");
|
||||
//SetTargetFPS(60);
|
||||
|
||||
// Main game loop
|
||||
while (!WindowShouldClose()) // Detect window close button or ESC key
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// TODO: Update your variables here
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(RAYWHITE);
|
||||
|
||||
DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 +426,10 @@ int main(int argc, char **argv) {
|
||||
|
||||
pack(song_pcm, song.sample_count);
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
CloseWindow(); // Close window and OpenGL context
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user