Browse Source

added note system, note to hz, semitones to hz, example song

Iver 1 month ago
parent
commit
746b61c583
8 changed files with 306 additions and 131 deletions
  1. 7 4
      Makefile
  2. 20 11
      src/headers/MD_functions.h
  3. 17 12
      src/headers/MD_types.h
  4. 12 0
      src/headers/YZ_functions.h
  5. 12 0
      src/headers/YZ_types.h
  6. 1 0
      src/headers/mandarin.h
  7. 59 0
      src/launch program/main.c
  8. 178 104
      src/main/main.c

+ 7 - 4
Makefile

@@ -1,16 +1,19 @@
 COMPILER=gcc
 FLAGS_ALL=-g -Wall -Wextra -Wno-unused-variable -Wno-unused-parameter
-FLAGS_EXAMPLE=-Lbuilds/ -lNAMEHERE -lLIBNAMEHERE -Wl,-rpath=builds/ -lm
+FLAGS_EXAMPLE=-Lbuilds/ -lmandarin -lyuzuparse -lorangetree -Wl,-rpath=builds/ -lm
 FLAGS_LIB=-fPIC -shared -lc -lm 
 
-main.bin: libmandarin.so libyuzuparse.so
+main.bin: libmandarin.so libyuzuparse.so liborangetree.so
 	$(COMPILER) $(FLAGS_ALL) src/launch\ program/main.c -o builds/main.bin $(FLAGS_EXAMPLE) 
 
 libmandarin.so:
-	$(COMPILER) $(FLAGS_ALL) src/main/main.c -o builds/librasteriver.so $(FLAGS_LIB) 
+	$(COMPILER) $(FLAGS_ALL) src/main/main.c -o builds/libmandarin.so $(FLAGS_LIB) 
 
 libyuzuparse.so:
-	cp src/libraries/libLIBNAMEHERE.so builds/
+	cp src/dependencies/libyuzuparse.so builds/
+
+liborangetree.so:
+	cp src/dependencies/liborangetree.so builds/
 
 clean:
 	rm builds/*

+ 20 - 11
src/headers/MD_functions.h

@@ -3,20 +3,29 @@
 
 #include "MD_types.h"
 
-// play an audio stream one time though
-pa_callback_data* MD_play_stream(YZ_audio_stream* audio_stream);
+// plays a tone defined by a function of time
+// the tone generator function must be a function of time that returns a delta that will be applied to the speaker position
+pa_tone_callback_data* MD_enqueue_tone(float (*tone_generator)(float, float), float sample_rate, float duration, float value, double start_time);
 
-// play an audio stream with optional effects. You can change speed, turn on looping, or set the starting second of the stream.
-pa_callback_data* MD_play_stream_dynamic(YZ_audio_stream* audio_stream, double* speed_multiplier, unsigned char* should_loop_audio, double start_second, void (*end_function)(void*), void* custom_pointer_for_end_function);
+float MD_saw_wave_generator(float time, float value);
 
-// Initializes the audio player. Playing a stream when the player is uninitializes automatically runs this function.
-void MD_init_player();
+float MD_sine_wave_generator(float time, float value);
 
-// Kills the player and frees memory
-void MD_kill_player();
+float MD_square_wave_generator(float time, float value);
 
-// plays a tone defined by a function of time
-// the tone generator function must be a function of time that returns a delta that will be applied to the speaker position
-pa_tone_callback_data* MD_play_tone(float (*tone_generator)(float, float), float sample_rate, float duration, float value);
+float MD_triangle_wave_generator(float time, float value);
+
+float MD_lsynth_wave_generator(float time, float value);
+
+MD_queue* MD_get_queue();
+
+double MD_seconds_since_epoch();
+
+double MD_semitones_to_hz(double reference_note, double semitones);
+
+double MD_note_to_hz(double reference_note, char* note, uint32_t octave);
+
+// ticks scheduler
+void MD_tick();
 
 #endif

+ 17 - 12
src/headers/MD_types.h

@@ -2,18 +2,8 @@
 #define MD_TYPES_H
 
 #include <stdint.h>
-
-typedef struct {
-    float current_sample;
-    float* pcm_data;
-    uint32_t channel_count;
-    uint32_t sample_count;
-    double* pitch_multiplier;
-    double* speed_multiplier;
-    unsigned char* should_loop_audio;
-    void(*end_function)(void*);
-    void* custom_pointer_for_end_function;
-} pa_callback_data;
+#include "yuzuparse.h"
+#include "orangetree.h"
 
 typedef struct {
     float left_tone;
@@ -28,4 +18,19 @@ typedef struct {
     float value; // a value that is passed into the tone generator. could be HZ for example
 } pa_tone_callback_data;
 
+typedef struct {
+    void* OT_next;
+    void* OT_prev;
+    pa_tone_callback_data* tone_data;
+    pa_callback_data* sound_data;
+    unsigned char is_started;
+    double sample_rate;
+    double start_time; // seconds since epoch until sound will start
+} MD_sound;
+
+typedef struct {
+    unsigned char is_empty;
+    MD_sound* sounds;
+} MD_queue;
+
 #endif

+ 12 - 0
src/headers/YZ_functions.h

@@ -6,4 +6,16 @@
 // loads an audio file into a playable format
 YZ_audio_stream* YZ_load_audio_file(char* filename, unsigned char debug_mode);
 
+// play an audio stream one time though
+pa_callback_data* YZ_play_stream(YZ_audio_stream* audio_stream);
+
+// play an audio stream with optional effects. You can change speed, turn on looping, or set the starting second of the stream.
+pa_callback_data* YZ_play_stream_dynamic(YZ_audio_stream* audio_stream, double* speed_multiplier, unsigned char* should_loop_audio, double start_second, void (*end_function)(void*), void* custom_pointer_for_end_function);
+
+// Initializes the audio player. Playing a stream when the player is uninitializes automatically runs this function.
+void YZ_init_player();
+
+// Kills the player and frees memory
+void YZ_kill_player();
+
 #endif

+ 12 - 0
src/headers/YZ_types.h

@@ -10,4 +10,16 @@ typedef struct {
     uint32_t sample_count;
 } YZ_audio_stream;
 
+typedef struct {
+    float current_sample;
+    float* pcm_data;
+    uint32_t channel_count;
+    uint32_t sample_count;
+    double* pitch_multiplier;
+    double* speed_multiplier;
+    unsigned char* should_loop_audio;
+    void(*end_function)(void*);
+    void* custom_pointer_for_end_function;
+} pa_callback_data;
+
 #endif

+ 1 - 0
src/headers/mandarin.h

@@ -4,6 +4,7 @@
 #include "MD_functions.h"
 #include "MD_types.h"
 
+#include <sys/time.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <string.h>

+ 59 - 0
src/launch program/main.c

@@ -1,3 +1,62 @@
+#include "../headers/mandarin.h"
+
+
 int main(){
+    MD_queue* queue = MD_get_queue();
+
+    double count = 0;
+
+    while (1){
+        MD_tick();
+
+        if (queue->is_empty){
+            double start_time = MD_seconds_since_epoch();
+    
+            double t = start_time;
+            const float beat = 0.2;
+            double note = 0;
+
+            // Helper function to add a note and advance the counter
+            #define NOTE(pitch, octave, duration) \
+                MD_enqueue_tone(MD_lsynth_wave_generator, 44100, beat * (duration), \
+                                MD_note_to_hz(440, (pitch), (octave)), t + beat * note); \
+                note += (duration);
+
+            #define REST(duration) note += (duration);
+
+            NOTE("e", 4, 2)
+            NOTE("e", 4, 2)
+            NOTE("a", 5, 1)
+            NOTE("b", 5, 1)
+            REST(1)
+            NOTE("c", 5, 5)
+            REST(2)
+            NOTE("c", 5, 2)
+            NOTE("c", 5, 4)
+            NOTE("b", 5, 1)
+            NOTE("a", 5, 1)
+            REST(1)
+            NOTE("f", 4, 5)
+            REST(2)
+            NOTE("f", 4, 2)
+            NOTE("c", 5, 4)
+            NOTE("b", 5, 1)
+            NOTE("a", 5, 1)
+            REST(1)
+            NOTE("a", 5, 3)
+            NOTE("g", 4, 4)
+            NOTE("g", 4, 2)
+            NOTE("a", 5, 3)
+            NOTE("g", 4, 1)
+            REST(1)
+            NOTE("f", 4, 1)
+            REST(1)
+            NOTE("e", 4, 9)
+
+            #undef NOTE
+            #undef REST
+        }
+    }
+
     return 0;
 }

+ 178 - 104
src/main/main.c

@@ -1,55 +1,61 @@
 #include "../headers/mandarin.h"
 #include "../headers/yuzuparse.h"
 
-int portaudio_callback(const void* input, void* output, unsigned long frame_count, const PaStreamCallbackTimeInfo* pa_time_info, PaStreamCallbackFlags pa_status_flags, void* audio_data){
-    pa_callback_data* data = (pa_callback_data*)audio_data;
-    
-    float* out = (float*)output;
+#define PI2 6.28318530718
 
-    for(uint32_t i = 0; i < frame_count; i++){
-        for (uint32_t j = 0; j < data->channel_count; j++) {
-            *out++ = data->pcm_data[(int)(data->current_sample) * data->channel_count + j];
-        }
+MD_queue queue;
 
-        data->current_sample += *data->speed_multiplier;
+float MD_lsynth_wave_generator(float time, float value){
+    // play a VALUE hz note
     
-        if (*data->should_loop_audio){
-            if (data->current_sample >= data->sample_count){
-                data->current_sample = data->sample_count - data->current_sample;
-            } else if (data->current_sample < 0){
-                data->current_sample = data->sample_count + data->current_sample;
-            }
-        }
-
-        if (data->current_sample >= data->sample_count || data->current_sample < 0){
-            goto END;
-        }
-    }
-
-    if (data->current_sample >= data->sample_count){
-        goto END;
-    }
+    return ((2.0 * fabs(2.0 * fmodf(time * value - 0.25, 1.0) - 1.0) - 1.0) + (fmodf(time * value, 1) - fmodf(time * value + 0.5, 1)) + (sin(time * value * 3.141564 * 2)) + (fmodf(time * value * 2 + 1.0, 2.0) - 1.0)) / 4;
+}
 
-    return 0;
+float MD_saw_wave_generator(float time, float value){
+    // play a VALUE hz note
+    
+    return fmodf(time * value * 2 + 1.0, 2.0) - 1.0;
+}
 
-    END:
-        if (data->end_function) data->end_function(data->custom_pointer_for_end_function);
+float MD_sine_wave_generator(float time, float value){
+    // play a VALUE hz note
+    
+    return sin(time * value * PI2);
+}
 
-        free(audio_data);
+float MD_square_wave_generator(float time, float value){
+    // play a VALUE hz note
+    
+    return fmodf(time * value, 1) - fmodf(time * value + 0.5, 1);
+}
 
-        return 1;    
+float MD_triangle_wave_generator(float time, float value){
+    // play a VALUE hz note
+    
+    return 2.0 * fabs(2.0 * fmodf(time * value - 0.25, 1.0) - 1.0) - 1.0;
 }
 
+float tone_fade_out_duration = 0.05;
+float tone_fade_in_duration = 0.05;
+
 int tone_callback(const void* input, void* output, unsigned long frames_per_buffer, const PaStreamCallbackTimeInfo* time_info, PaStreamFlags status_flags, void* user_data){
     pa_tone_callback_data* data = (pa_tone_callback_data*)user_data;
     float *out = (float*)output;
     unsigned long i;
 
     for(i = 0; i < frames_per_buffer; i++){
-        *out++ = data->left_tone;
-        *out++ = data->right_tone;
+        double cur_second = (double)data->current_sample / (double)data->sample_rate;
+
+        // fade in
+        float a = (cur_second) / (tone_fade_in_duration);
+        // fade out
+        float b = (data->duration - cur_second) / (tone_fade_out_duration);
         
-        float delta = data->tone_generator((double)data->current_sample / (double)data->sample_rate, data->value);
+        float fade = fmax(fmin(
+            pow(fmin(a, 1.0), 2) * pow(fmin(b, 1.0), 2)
+        , 1), 0.0);
+
+        float delta = data->tone_generator(cur_second, data->value) * fade;
 
         data->left_tone = delta;
         if(data->left_tone >= 1.0) data->left_tone = 1.0;
@@ -59,6 +65,9 @@ int tone_callback(const void* input, void* output, unsigned long frames_per_buff
         if(data->right_tone >= 1.0) data->right_tone = 1.0;
         if(data->right_tone <= -1.0) data->right_tone = -1.0;
     
+        *out++ = data->left_tone;
+        *out++ = data->right_tone;
+
         data->current_sample++;
     }
 
@@ -71,103 +80,168 @@ int tone_callback(const void* input, void* output, unsigned long frames_per_buff
 
 unsigned char player_is_initialized = 0;
 
-void MD_init_player(){
-    if (player_is_initialized){
-        printf("cannot init, already started");
-    
-        return;
-    }
-    
-    PaError error;
+pa_tone_callback_data* MD_play_tone(pa_tone_callback_data* callback_data){
+    PaStream* stream;
 
-    // block PortAudio's standard output and standard error stream
-    freopen("/dev/null", "w", stdout); 
-    freopen("/dev/null", "w", stderr); 
+    PaError error = Pa_OpenDefaultStream(&stream, 0, 2, paFloat32, callback_data->sample_rate, paFramesPerBufferUnspecified, tone_callback, callback_data);
+    if(error != paNoError) { printf("PortAudio error: %d\n", error); exit(1);}
 
-    error = Pa_Initialize();
- 
-    // unblock PortAudio's standard output and standard error stream
-    freopen("/dev/tty", "w", stdout);
-    freopen("/dev/tty", "w", stderr);
+    Pa_StartStream(stream);
 
-    if(error != paNoError) { printf("PortAudio error: %d\n", error); exit(1);}
-    
-    player_is_initialized = 1;
+    return callback_data;
 }
 
-void MD_kill_player(){
-    if (!player_is_initialized){
-        printf("cannot kill, player isn't initilized");
-
-        return;
+pa_tone_callback_data* MD_enqueue_tone(float (*tone_generator)(float, float), float sample_rate, float duration, float value, double start_time){
+    if (!player_is_initialized) {
+        YZ_init_player();
+        player_is_initialized = 1;
     }
 
-    PaError error;
+    pa_tone_callback_data* callback_data = malloc(sizeof(pa_tone_callback_data));
 
-    error = Pa_Terminate();
-    if(error != paNoError) { printf("PortAudio error: %d\n", error); exit(1);}
-}
+    callback_data->tone_generator = tone_generator;
+    callback_data->current_sample = 0;
+    callback_data->duration = duration;
+    callback_data->left_amplitude = 1;
+    callback_data->right_amplitude = 1;
+    callback_data->sample_rate = sample_rate;
+    callback_data->sample_count = duration * sample_rate;
+    callback_data->value = value;
 
-pa_callback_data* play_stream(YZ_audio_stream* audio_stream, double* pitch_multiplier, double* speed_multiplier, unsigned char* should_loop_audio, double starting_second, void (*end_func)(void* custom_pointer_for_end_function), void* custom_pointer_for_end_function){
-    PaError error;
-    
-    PaStream *stream;
+    MD_sound* sound = malloc(sizeof(MD_sound));
 
-    pa_callback_data* callback_data = malloc(sizeof(pa_callback_data));
+    sound->tone_data = callback_data;
+    sound->OT_next = NULL;
+    sound->OT_prev = NULL;
+    sound->sound_data = NULL;
+    sound->is_started = 0;
+    sound->start_time = start_time;
+    sound->sample_rate = sample_rate;
 
-    callback_data->channel_count = audio_stream->channel_count;
-    callback_data->current_sample = starting_second >= 0 ? audio_stream->sample_rate * starting_second : audio_stream->sample_count;
-    callback_data->pcm_data = audio_stream->pcm_data;
-    callback_data->sample_count = audio_stream->sample_count;
-    callback_data->pitch_multiplier = pitch_multiplier;
-    callback_data->speed_multiplier = speed_multiplier;
-    callback_data->should_loop_audio = should_loop_audio;
-    callback_data->end_function = end_func;
-    callback_data->custom_pointer_for_end_function = custom_pointer_for_end_function;
+    OT_append(queue.sounds, sound);
 
-    if (!player_is_initialized) MD_init_player();
+    queue.sounds = sound;
 
-    error = Pa_OpenDefaultStream(&stream, 0, audio_stream->channel_count, paFloat32, audio_stream->sample_rate, paFramesPerBufferUnspecified, portaudio_callback, callback_data);
-    if(error != paNoError) { printf("PortAudio error: %d\n", error); exit(1);}
+    return callback_data;
+}
 
-    Pa_StartStream(stream);
+double MD_seconds_since_epoch(){
+    struct timeval tv;
 
-    return callback_data;
+    gettimeofday(&tv, NULL);
+
+    return (double)(tv.tv_sec) + (double)(tv.tv_usec) / (double)1000000.0;
 }
 
-// play an audio stream
-pa_callback_data* MD_play_stream(YZ_audio_stream* audio_stream){
-    double value = 1;
-    unsigned char antivalue = 0;
+void iterator(void* operand, void* ptr){
+    MD_sound* sound = (MD_sound*)operand;
+
+    if (sound->is_started){ // if sound is already started
+        // erase sound if it's done
+        if ((sound->sound_data && sound->sound_data->current_sample >= sound->sound_data->sample_count) || 
+            (sound->tone_data && sound->tone_data->current_sample >= sound->tone_data->sample_count)){
+                if (OT_drop(sound) == -1){
+                    queue.sounds = NULL;
+                } 
+            }
+    } else { // if sound is not started
+        // check if its time to start it
+        double cur_time = MD_seconds_since_epoch();
+
+        if (sound->start_time <= cur_time){
+            double time_offset = sound->start_time >= 0 ? cur_time - sound->start_time : 0;
+
+            if (sound->sound_data) {
+                sound->sound_data->current_sample = time_offset / sound->sample_rate;
+            
+                // YZ_play_stream_dynamic();
+            }
+
+            if (sound->tone_data) {
+                sound->tone_data->current_sample = time_offset / sound->sample_rate;
+
+                MD_play_tone(sound->tone_data);
+            }
 
-    return play_stream(audio_stream, &value, &value, &antivalue, (double)antivalue, NULL, NULL);
+            sound->is_started = 1;
+        }
+    }
 }
 
-// play an audio stream and modify pitch and speed dynamically. Pitch and speed changes work in real time due to it being pointers to the values
-pa_callback_data* MD_play_stream_dynamic(YZ_audio_stream* audio_stream, double* speed_multiplier, unsigned char* should_loop_audio, double starting_second, void (*end_func)(void*), void* custom_pointer_for_end_function){
-    return play_stream(audio_stream, NULL, speed_multiplier, should_loop_audio, starting_second, end_func, custom_pointer_for_end_function);
+double MD_semitones_to_hz(double reference_note, double semitones){
+    return reference_note * pow(2, semitones / 12.0);
 }
 
-pa_tone_callback_data* MD_play_tone(float (*tone_generator)(float, float), float sample_rate, float duration, float value){
-    if (!player_is_initialized) MD_init_player();
+double MD_note_to_hz(double reference_note, char* note, uint32_t octave){
+    double semitones = 0;
+
+    char natural = note[0];
+    char modifier = note[1];
+
+    switch(natural){
+        case ('a'):
+        case ('A'):
+            semitones = 0;
+            break;
+            
+        case ('b'):
+        case ('B'):
+            semitones = 2;
+            break;
+
+        case ('c'):
+        case ('C'):
+            semitones = 3;
+            break;
+
+        case ('d'):
+        case ('D'):
+            semitones = 5;
+            break;
+
+        case ('e'):
+        case ('E'):
+            semitones = 7;
+            break;
+
+        case ('f'):
+        case ('F'):
+            semitones = 8;
+            break;
+
+        case ('g'):
+        case ('G'):
+            semitones = 10;
+            break;
+    }
 
-    pa_tone_callback_data* callback_data = malloc(sizeof(pa_tone_callback_data));
+    switch(modifier){
+        case('#'):
+        case('s'):
+            semitones++;
+            break;
 
-    callback_data->tone_generator = tone_generator;
-    callback_data->current_sample = 0;
-    callback_data->duration = duration;
-    callback_data->left_amplitude = 1;
-    callback_data->right_amplitude = 1;
-    callback_data->sample_rate = sample_rate;
-    callback_data->sample_count = duration * sample_rate;
-    callback_data->value = value;
+        case('b'):
+        case('f'):
+            semitones--;
+            break;
+    }
 
-    PaStream* stream;
+    return MD_semitones_to_hz(reference_note, semitones + (octave - 5.0) * 12.0);
+}
 
-    PaError error = Pa_OpenDefaultStream(&stream, 0, 2, paFloat32, sample_rate, paFramesPerBufferUnspecified, tone_callback, callback_data);
-    if(error != paNoError) { printf("PortAudio error: %d\n", error); exit(1);}
+void MD_tick(){
+    if(queue.sounds){
+        queue.is_empty = 0;
 
-    Pa_StartStream(stream);
+        OT_iterate(queue.sounds, NULL, iterator);
+    } else {
+        queue.is_empty = 1;
+    }
+}
 
-    return callback_data;
+MD_queue* MD_get_queue(){
+    queue.is_empty = 1;
+
+    return &queue;
 }