|
@@ -1,55 +1,61 @@
|
|
|
#include "../headers/mandarin.h"
|
|
#include "../headers/mandarin.h"
|
|
|
#include "../headers/yuzuparse.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){
|
|
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;
|
|
pa_tone_callback_data* data = (pa_tone_callback_data*)user_data;
|
|
|
float *out = (float*)output;
|
|
float *out = (float*)output;
|
|
|
unsigned long i;
|
|
unsigned long i;
|
|
|
|
|
|
|
|
for(i = 0; i < frames_per_buffer; 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;
|
|
data->left_tone = delta;
|
|
|
if(data->left_tone >= 1.0) data->left_tone = 1.0;
|
|
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;
|
|
|
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++;
|
|
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;
|
|
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;
|
|
|
}
|
|
}
|