From: kazu Date: Fri, 19 Apr 2024 09:16:42 +0000 (+0000) Subject: Added support for release time and natural decay time for when you either release... X-Git-Url: http://10.10.0.4:5575/?a=commitdiff_plain;h=5c5f4247c741f4ce143fada408c23a2fbbaa8b7b;p=ksynth.git Added support for release time and natural decay time for when you either release the key or the note's sample is about to reach the end, support for continuous controllers (Only CC10 - Pan and CC64 - Sustain for now) and made it so the voices are allocated when calling ksynth_new instead of when calling ksynth_note_on/ksynth_note_off. git-svn-id: file:///raid/svn-main/kazu-ksynth/trunk@24 7b47e76f-e598-2f43-bc14-414d160cc389 --- diff --git a/Makefile b/Makefile index 3cddea5..c8b3d85 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # $Id$ CC := cc -CFLAGS := -D_POSIX_C_SOURCE=199309L -std=c99 +CFLAGS := -D_POSIX_C_SOURCE=199309L -std=c99 -ffast-math ifeq ($(DEBUG),YES) CFLAGS += -g endif @@ -57,4 +57,4 @@ format: clang-format -i `find . -name "*.h" -or -name "*.c"` clean: - rm -f out/libksynth_*.so out/libksynth_*.a out/ksynth_*.dll $(OBJS) + rm -f out/libksynth.so out/libksynth.a out/ksynth_*.dll $(OBJS) diff --git a/src/chan.h b/src/chan.h new file mode 100644 index 0000000..bc1dc10 --- /dev/null +++ b/src/chan.h @@ -0,0 +1,17 @@ +#ifndef CHANS_H +#define CHANS_H +#ifdef __cplusplus +extern "C" { +#endif + +#include + + struct Chan { + float pan; + unsigned short sustain; + }; + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/src/ksynth.c b/src/ksynth.c index 4faa444..6bee50a 100644 --- a/src/ksynth.c +++ b/src/ksynth.c @@ -1,252 +1,388 @@ -/* $Id$ */ - #include "ksynth.h" -#include "sample.h" -#include "voice.h" -#include -#include -#include -#include -#include +#define MAX_POLYPHONY 1000000 +#define MIN_BUF 16 +#define MAX_BUF 65536 + +#define RELEASE_TIME 400 + +#if defined(_MSC_VER) && !defined(__clang__) +float compute_velocity_sse2(float logVel, float pow, float coeff) { + __m128 fresult = _mm_add_ps(_mm_pow_ps(_mm_set1_ps(logVel), _mm_set1_ps(pow)), _mm_set1_ps(coeff)); + + float result; + _mm_store_ss(&result, _mm_max_ps(_mm_min_ps(fresult, _mm_set1_ps(1.0f)), _mm_setzero_ps())); -#define MAX_POLYPHONY 100000 + return result; +} +#endif struct KSynth* ksynth_new(const char* sample_file_path, unsigned int sample_rate, unsigned char num_channel, unsigned long max_polyphony) { struct KSynth* ksynth_instance = malloc(sizeof(struct KSynth)); - if(ksynth_instance != NULL) { - ksynth_instance->samples = malloc(sizeof(struct Sample*) * 128); + if (ksynth_instance != NULL) { + memset(ksynth_instance, 0, sizeof(struct KSynth)); // Load samples from file FILE* f = fopen(sample_file_path, "rb"); - if(f == NULL) { + if (f == NULL) { fprintf(stderr, "[KSynth] Error: Failed to open sample file.\n"); free(ksynth_instance); return NULL; } - for(int i = 0; i < 128; i++) { - ksynth_instance->samples[i] = malloc(sizeof(struct Sample)); - ksynth_instance->samples[i]->sample = malloc(48000 * 10 * sizeof(float)); - ksynth_instance->samples[i]->length = 48000 * 10; - fread(ksynth_instance->samples[i]->sample, 48000 * 10 * sizeof(float), 1, f); + // HARDCODED? + int sfrate = 48000; + ksynth_instance->samples = malloc(sizeof(struct Sample*) * 128); + if (ksynth_instance->samples != NULL) { + for (int i = 0; i < 128; i++) { + ksynth_instance->samples[i] = malloc(sizeof(struct Sample)); + memset(ksynth_instance->samples[i], 0, sizeof(struct Sample)); + + ksynth_instance->samples[i]->sample = malloc(sfrate * 10 * sizeof(float)); + memset(ksynth_instance->samples[i]->sample, 0, sfrate * 10 * sizeof(float)); + + ksynth_instance->samples[i]->length = sfrate * 10; + fread(ksynth_instance->samples[i]->sample, sfrate * 10 * sizeof(float), 1, f); + + ksynth_instance->samples[i]->sample_rate = sfrate; + } } + fclose(f); // Load samples from file end ksynth_instance->sample_rate = sample_rate; - if(num_channel >= 2) { + if (num_channel >= 2) { ksynth_instance->num_channel = 2; - } else { + } + else { ksynth_instance->num_channel = num_channel; } ksynth_instance->polyphony = 0; - if(max_polyphony < 1) { + if (max_polyphony < 1) { ksynth_instance->max_polyphony = 1; - } else if(max_polyphony >= MAX_POLYPHONY) { + } + else if (max_polyphony >= MAX_POLYPHONY) { ksynth_instance->max_polyphony = MAX_POLYPHONY; - } else { + } + else { ksynth_instance->max_polyphony = max_polyphony; } - for(int i = 0; i < 16; i++) { + for (int i = 0; i < 16; i++) { + ksynth_instance->channels[i].pan = 0.0f; + ksynth_instance->channels[i].sustain = 0; ksynth_instance->polyphony_per_channel[i] = 0; } ksynth_instance->rendering_time = 0; ksynth_instance->voices = malloc(sizeof(struct Voice*) * max_polyphony); - } else { + memset(ksynth_instance->voices, 0, sizeof(struct Voice*) * max_polyphony); + + if (ksynth_instance->voices) { + for (unsigned long i = 0; i < max_polyphony; i++) { + struct Voice* newVoice = malloc(sizeof(struct Voice)); + memset(newVoice, 0, sizeof(struct Voice)); + + if (newVoice) { + newVoice->killed = 1; + + ksynth_instance->voices[i] = newVoice; + continue; + } + + return NULL; + } + } + } + else { fprintf(stderr, "[KSynth] Error: Failed to allocate memory for KSynth instance.\n"); } return ksynth_instance; } -void ksynth_note_on(struct KSynth* ksynth_instance, unsigned char channel, unsigned char note, unsigned char velocity) { - if(ksynth_instance == NULL || channel < 0 || channel > 16 || note < 0 || note > 127 || velocity < 0 || velocity > 127) { - fprintf(stderr, "[KSynth] Error: Invalid parameters for note on.\n"); +void ksynth_cc(struct KSynth* ksynth_instance, unsigned char channel, unsigned char param1, unsigned char param2) { + if (!ksynth_instance || channel > 15) { + fprintf(stderr, "[KSynth] Error: Invalid parameters for CC.\n"); return; } - if(channel == 9) return; - - if(ksynth_instance->polyphony >= ksynth_instance->max_polyphony) { - voice_free(ksynth_instance->voices[0]); - - for(int i = 0; i < ksynth_instance->polyphony - 1; ++i) { - ksynth_instance->voices[i] = ksynth_instance->voices[i + 1]; - } - - ksynth_instance->polyphony--; + if (channel == 9) return; + + switch (param1) { + case 10: + ksynth_instance->channels[channel].pan = ((param2 - 64) / 64.0f); + break; + case 64: + // Damper pedal, MUST respond to this at least + ksynth_instance->channels[channel].sustain = param2 > 63 ? 1 : 0; + break; + default: + break; } +} - struct Voice* newVoice = malloc(sizeof(struct Voice)); - if(newVoice == NULL) { - fprintf(stderr, "[KSynth] Error: Failed to allocate memory for new voice.\n"); +void ksynth_note_off(struct KSynth* ksynth_instance, unsigned char channel, unsigned char note) { + if (!ksynth_instance || channel > 15 || note > 127) { + fprintf(stderr, "[KSynth] Error: Invalid parameters for note off.\n"); return; } - newVoice->sample_rate = ksynth_instance->sample_rate; - newVoice->channel = channel; - newVoice->noteNumber = note; - newVoice->velocity = velocity; - newVoice->sample_position = 0; + if (channel == 9) return; - ksynth_instance->voices[ksynth_instance->polyphony++] = newVoice; - ksynth_instance->polyphony_per_channel[channel]++; + struct Voice* voice = 0; + for (unsigned long i = ksynth_instance->polyphony - 1; i < ksynth_instance->polyphony; --i) { + voice = ksynth_instance->voices[i]; + + if ((voice->channel == channel && note == voice->noteNumber) && + (!voice->killed && !voice->tokill)) { + voice->tokill = 1; + break; + } + } } -void ksynth_note_off(struct KSynth* ksynth_instance, unsigned char channel, unsigned char note) { - if(ksynth_instance == NULL || channel < 0 || channel > 16 || note < 0 || note > 127) { - fprintf(stderr, "[KSynth] Error: Invalid parameters for note off.\n"); +void ksynth_note_on(struct KSynth* ksynth_instance, unsigned char channel, unsigned char note, unsigned char velocity) { + if (!ksynth_instance || !ksynth_instance->voices || channel > 15 || note > 127 || velocity > 127) { + fprintf(stderr, "[KSynth] Error: Invalid parameters for note on.\n"); return; } - if(channel == 9) return; + if (velocity < 1) + ksynth_note_off(ksynth_instance, channel, note); + + if (channel == 9) return; + + struct Voice* voice = 0; + if (ksynth_instance->polyphony >= ksynth_instance->max_polyphony) { + unsigned char min = ksynth_instance->voices[0]->velocity; + unsigned long index = 0; - for(int i = 0; i < ksynth_instance->polyphony; ++i) { - if(ksynth_instance->voices[i]->channel == channel && note == ksynth_instance->voices[i]->noteNumber) { - voice_free(ksynth_instance->voices[i]); + for (unsigned long i = 0; i < ksynth_instance->polyphony - 1; ++i) { + voice = ksynth_instance->voices[i]; - for(int j = i; j < ksynth_instance->polyphony - 1; ++j) { - ksynth_instance->voices[j] = ksynth_instance->voices[j + 1]; + if (voice->velocity < min) { + min = voice->velocity; + index = i; } + } - ksynth_instance->polyphony--; - ksynth_instance->polyphony_per_channel[channel]--; + voice = ksynth_instance->voices[index]; - break; + for (unsigned long i = index; i < ksynth_instance->polyphony - 1; ++i) { + ksynth_instance->voices[i] = ksynth_instance->voices[i + 1]; + } + + ksynth_instance->voices[ksynth_instance->max_polyphony - 1] = voice; + } + else { + for (unsigned long i = 0; i < ksynth_instance->max_polyphony; ++i) { + struct Voice* temp = ksynth_instance->voices[i]; + + if (temp->killed) { + voice = temp; + break; + } } + + ksynth_instance->polyphony++; + ksynth_instance->polyphony_per_channel[channel]++; + } + + if (voice) { + voice_reset(voice); + + voice->killed = 0; + voice->channel = channel; + voice->noteNumber = note; + voice->velocity = velocity; } } void ksynth_note_off_all(struct KSynth* ksynth_instance) { - if(ksynth_instance != NULL) { - for(int i = 0; i < ksynth_instance->polyphony; ++i) { - voice_free(ksynth_instance->voices[i]); + if (ksynth_instance != NULL) { + for (unsigned long i = 0; i < ksynth_instance->max_polyphony; ++i) { + struct Voice* voice = ksynth_instance->voices[i]; + voice_reset(voice); } - ksynth_instance->polyphony = 0; - - for(int i = 0; i < 16; i++) { + for (unsigned short i = 0; i < 15; i++) { + ksynth_instance->channels[i].pan = 0.0f; + ksynth_instance->channels[i].sustain = 0; ksynth_instance->polyphony_per_channel[i] = 0; + } + + ksynth_instance->polyphony = 0; } } int ksynth_get_polyphony(struct KSynth* ksynth_instance) { - if(ksynth_instance != NULL) { - return ksynth_instance->polyphony; - } else { + if (!ksynth_instance) { fprintf(stderr, "[KSynth] Error: Invalid KSynth instance.\n"); return -1; } + + return ksynth_instance->polyphony; }; int ksynth_get_max_polyphony(struct KSynth* ksynth_instance) { - if(ksynth_instance != NULL) { - return ksynth_instance->max_polyphony; - } else { + if (!ksynth_instance) { fprintf(stderr, "[KSynth] Error: Invalid KSynth instance.\n"); return -1; } + + return ksynth_instance->max_polyphony; }; bool ksynth_set_max_polyphony(struct KSynth* ksynth_instance, unsigned long max_polyphony) { - fprintf(stderr, "[KSynth] Error: Setting max polyphony is currently not supported.\n"); - return false; - - /*if(ksynth_instance != NULL) { - if(max_polyphony < 1) { - max_polyphony = 1; - } else if(max_polyphony >= MAX_POLYPHONY) { - max_polyphony = MAX_POLYPHONY; - } + if (!ksynth_instance) + return false; - if(ksynth_instance->polyphony > max_polyphony) { - int num_voices_to_remove = ksynth_instance->polyphony - max_polyphony; - for(int i = 0; i < num_voices_to_remove; ++i) { - voice_free(ksynth_instance->voices[i]); - } + if (max_polyphony < 1) { + max_polyphony = 1; + } + else if (max_polyphony >= MAX_POLYPHONY) { + max_polyphony = MAX_POLYPHONY; + } - for(int i = 0; i < max_polyphony; ++i) { - ksynth_instance->voices[i] = ksynth_instance->voices[i + num_voices_to_remove]; - } - ksynth_instance->polyphony = max_polyphony; - } + ksynth_note_off_all(ksynth_instance); + + for (unsigned long i = 0; i < ksynth_instance->max_polyphony; i++) + free(ksynth_instance->voices[i]); - ksynth_instance->max_polyphony = max_polyphony; + free(ksynth_instance->voices); - return true; - } else { + struct Voice** new_voices = malloc(sizeof(struct Voice*) * max_polyphony); + if (!new_voices) { + fprintf(stderr, "[KSynth] Error: Failed to allocate memory for new voices.\n"); return false; - }*/ + } + + for (unsigned long i = 0; i < max_polyphony; i++) { + struct Voice* newVoice = malloc(sizeof(struct Voice)); + if (!newVoice) { + fprintf(stderr, "[KSynth] Error: Failed to allocate memory for new voice.\n"); + return true; + } + + newVoice->killed = 1; + newVoice->channel = 0; + } + + ksynth_instance->voices = new_voices; + ksynth_instance->max_polyphony = max_polyphony; + + return true; } -float* ksynth_generate_buffer(struct KSynth* ksynth_instance, unsigned short buffer_size) { - if(ksynth_instance == NULL || ksynth_instance->samples == NULL) { +void ksynth_fill_buffer(struct KSynth* ksynth_instance, float* buffer, unsigned int buffer_size) { + // I decided not to limit the buffer size in ksynth_fill_buffer, + // to make use of the new chunk system in OMv2, better strums:tm:! + if (ksynth_instance == NULL || ksynth_instance->samples == NULL) { fprintf(stderr, "[KSynth] Error: Invalid KSynth instance or no samples loaded.\n"); - return NULL; + return; } - if(buffer_size < 32) { - buffer_size = 32; - } else if(buffer_size >= 16384) { - buffer_size = 16384; + if (!buffer) { + fprintf(stderr, "[KSynth] Error: Target buffer is not valid.\n"); + return; } - int num_channels = ksynth_instance->num_channel; + // Initialize buffer with zeros + memset(buffer, 0, sizeof(float) * buffer_size); - float* buffer = malloc(buffer_size * num_channels * sizeof(float)); - if(buffer == NULL) { - fprintf(stderr, "[KSynth] Error: Failed to allocate memory for buffer.\n"); - return NULL; + if (!ksynth_instance->polyphony) { + ksynth_instance->rendering_time = 0; + return; } - // Initialize buffer with zeros - for(int i = 0; i < buffer_size * num_channels; ++i) { - buffer[i] = 0.0; - } + // post_decay is RELEASE_TIME of the MIDI stream, which you can take by doing (sample_rate / 1000) * RELEASE_TIME + unsigned int post_decay = (ksynth_instance->sample_rate / 1000) * RELEASE_TIME; + int num_channels = ksynth_instance->num_channel; - struct timespec rendering_time_start, rendering_time_end; + float velocity = 1.0f; + static struct timespec rendering_time_start, rendering_time_end; clock_gettime(CLOCK_MONOTONIC, &rendering_time_start); - for(int i = 0; i < ksynth_instance->polyphony; ++i) { + for (unsigned long i = 0; i < ksynth_instance->max_polyphony; i++) { struct Voice* voice = ksynth_instance->voices[i]; - int sample_position = voice->sample_position; - int sample_length = ksynth_instance->samples[voice->noteNumber]->length; - float logVel = (float)voice->velocity / 127.0f; - float velocity = fmin(pow(logVel, 2.5) + 0.03, 1.0); - - char killed = 0; - for(int j = 0; j < buffer_size; ++j) { - float sample = ksynth_instance->samples[voice->noteNumber]->sample[sample_position]; - - for(int c = 0; c < num_channels; ++c) { - buffer[j * num_channels + c] += sample * velocity; - } - - sample_position++; + if (!voice->killed) { + // Voice is alive, let's store the current position and the length of the sample + // for future reference + unsigned int sample_position = voice->sample_position; + unsigned int sample_length = ksynth_instance->samples[voice->noteNumber]->length; + + // CC 10 support + float pan = ((ksynth_instance->channels[voice->channel].pan + 1.0f) / 2.0f) * (M_PI / 2.0f); + float lpan = sinf(pan); + float rpan = cosf(pan); + + float decay_time = post_decay * (voice->noteNumber > 64 ? ((64 - (voice->noteNumber - 64)) / 128.0f) : 1.0f); + float nVel = 1.0f; + float logVel = (float)voice->velocity / 127.0f; + +#if defined(_MSC_VER) && !defined(__clang__) + velocity = compute_velocity_sse2(logVel, 2.5f, 0.03f); +#else + velocity = fminf(powf(logVel, 2.5) + 0.03, 1.0); +#endif + + for (unsigned int j = 0; j < buffer_size / num_channels; ++j) { + // This is used to calculate the natural decay of the note, + // which happens n samples before sample_length!! VERY IMPORTANT! + bool natural_decay = sample_length - sample_position < decay_time; + bool sample_done = sample_position >= sample_length; + bool fadeout_done = voice->curfalloff >= decay_time; + + // If we ran out of samples to play, or the fade out limit from voice->tokill + // has been reached, the voice is done and doesn't need to be played anymore. + if (sample_done || fadeout_done) + { + voice->killed = 1; + break; + } - // If sample_position exceeds sample_length, the voice has finished playing - if(ksynth_instance->voices[i] != NULL && sample_position >= sample_length) { - voice_free(voice); + // Get the sample byte you want to play + float sample = ksynth_instance->samples[voice->noteNumber]->sample[sample_position]; + + // Check if the voice hasn't been killed yet, if it has + // been marked as tokill, or if it is about to reach the end of the sample (natural_decay) + if (!voice->killed && (voice->tokill || natural_decay)) + { + // If the voice isn't sustained using CC 64 and it's time to kill it, or if we reached the natural_decay limit, + // start the curve falloff + if ((!ksynth_instance->channels[voice->channel].sustain && voice->tokill) || natural_decay) voice->curfalloff++; + + // As long as curfalloff is minor than decay_time (the amount of samples it takes for the + // sample to reach natural decay, which is "sample_length - (sample_rate / 4)"), keep + // calculating the velocity curve, else set velocity to 0.0f. + nVel = voice->curfalloff < decay_time ? (((float)decay_time - voice->curfalloff) / decay_time) : 0.0f; + } - // Shift remaining voices to fill the gap - for(int k = i; k < ksynth_instance->polyphony - 1; ++k) { - ksynth_instance->voices[k] = ksynth_instance->voices[k + 1]; + for (int c = 0; c < num_channels; ++c) { + // Fill up the buffer. + buffer[j * num_channels + c] += sample * (velocity * nVel) * (num_channels < 2 ? 1.0f : c ? lpan : rpan); } + // Move forward in sample by one. + sample_position++; + } + + // If voice is finally done being played, and hasn't been killed, store its position + if (!voice->killed) voice->sample_position = sample_position; + else { + // Otherwise, commit homicide and reset it ksynth_instance->polyphony--; ksynth_instance->polyphony_per_channel[voice->channel]--; - killed = 1; - break; + + voice_reset(voice); + continue; } } - - if(!killed) voice->sample_position = sample_position; } clock_gettime(CLOCK_MONOTONIC, &rendering_time_end); @@ -254,28 +390,48 @@ float* ksynth_generate_buffer(struct KSynth* ksynth_instance, unsigned short buf float buffer_generate_elapsed_time_ns = (rendering_time_end.tv_sec - rendering_time_start.tv_sec) * 1.0e9 + (rendering_time_end.tv_nsec - rendering_time_start.tv_nsec); float buffer_generate_elapsed_time_ms = buffer_generate_elapsed_time_ns / 1e6; - float rendering_time = buffer_generate_elapsed_time_ms / buffer_size; - ksynth_instance->rendering_time = rendering_time * 100.0f; + float rendering_time = (float)buffer_generate_elapsed_time_ms / buffer_size; + + ksynth_instance->rendering_time = rendering_time * 100.f; +} + +float* ksynth_generate_buffer(struct KSynth* ksynth_instance, unsigned int buffer_size) { + // buffer still limited here for compatibility purposes + if (!ksynth_instance || !ksynth_instance->samples) { + fprintf(stderr, "[KSynth] Error: Invalid KSynth instance or no samples loaded.\n"); + return NULL; + } + + if (buffer_size < MIN_BUF) { + buffer_size = MIN_BUF; + } + else if (buffer_size >= MAX_BUF) { + buffer_size = MAX_BUF; + } + + float* buffer = malloc(buffer_size * sizeof(float)); + + ksynth_fill_buffer(ksynth_instance, buffer, buffer_size); return buffer; } float ksynth_get_rendering_time(struct KSynth* ksynth_instance) { - if(ksynth_instance != NULL) { - return ksynth_instance->rendering_time; - } else { + if (!ksynth_instance) { fprintf(stderr, "[KSynth] Error: Invalid KSynth instance.\n"); return -1.0f; } + + return ksynth_instance->rendering_time; } int ksynth_get_polyphony_for_channel(struct KSynth* ksynth_instance, unsigned char channel) { - if(ksynth_instance == NULL) { + if (!ksynth_instance) { fprintf(stderr, "[KSynth] Error: Invalid KSynth instance.\n"); return -1; } - if(channel < 0 || channel > 16) { + if (channel < 0 || channel > 15) { fprintf(stderr, "[KSynth] Error: Invalid channel number (%d). Channel number must be between 0 and 15.\n", channel); return -1; } @@ -284,13 +440,13 @@ int ksynth_get_polyphony_for_channel(struct KSynth* ksynth_instance, unsigned ch } void ksynth_buffer_free(float* buffer) { - if(buffer != NULL) { + if (buffer != NULL) { free(buffer); } } void ksynth_free(struct KSynth* ksynth_instance) { - if(ksynth_instance != NULL) { + if (ksynth_instance != NULL) { free(ksynth_instance); } -} +} \ No newline at end of file diff --git a/src/ksynth.h b/src/ksynth.h index a4f1802..bbe976f 100644 --- a/src/ksynth.h +++ b/src/ksynth.h @@ -1,253 +1,329 @@ -/* $Id$ */ - -#ifndef KSYNTH_H +#ifndef KSYNTH_H #define KSYNTH_H #ifdef __cplusplus extern "C" { #endif +#include "sample.h" +#include "voice.h" +#include "chan.h" +#include +#include #include +#include +#include + +#ifdef KSYNTH_MSVC_TESTING +// HEEDOOPY BY BREE +#include "breewashere.h" +#include +#else +#include +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288 +#endif + + struct KSynth { + struct Sample** samples; + unsigned int sample_rate; + unsigned char num_channel; + unsigned long polyphony; + unsigned char polyphony_per_channel[16]; + unsigned long max_polyphony; + float rendering_time; +#ifdef ATOMIC_TEST + _Atomic(bool) lock; +#endif + struct Chan channels[16]; + struct Voice** voices; + }; + + /** + * @~english + * @brief Creates a new KSynth instance. + * + * This function creates a new KSynth instance with the specified parameters. + * The `sample_file_path` parameter specifies the path to the sample file which will be loaded and used for synthesis. + * The `sample_rate` parameter defines the audio sample rate in Hz. + * The `num_channel` parameter specifies the number of audio channels (1 for mono, 2 for stereo). + * The `max_polyphony` parameter sets the maximum number of simultaneous notes that can be played. + * Values exceeding 100,000 will be clamped to 100,000. + * + * @param sample_file_path Path to the sample file + * @param sample_rate Sample rate + * @param num_channel Number of channels (1-2) + * @param max_polyphony Maximum polyphony (1-100,000) + * @return A pointer to the newly created KSynth instance on success, NULL on failure. + * + * @~japanese + * @brief 新しいKSynthインスタンスを作成します。 + * + * この関数は、指定されたパラメータで新しいKSynthインスタンスを作成します。 + * `sample_file_path`パラメータは、合成に使用されるサンプルファイルへのパスを指定します。 + * `sample_rate`パラメータは、オーディオサンプルレート(Hz)を定義します。 + * `num_channel`パラメータは、オーディオチャンネルの数を指定します(モノラルの場合は1、ステレオの場合は2)。 + * `max_polyphony`パラメータは、同時に再生できる最大ノート数を設定します。 + * 100,000を超える値は100,000にクランプされます。 + * + * @param sample_file_path サンプルファイルへのパス + * @param sample_rate サンプルレート + * @param num_channel チャンネル数(1〜2) + * @param max_polyphony 最大ポリフォニー(1〜100,000) + * @return 成功した場合は新しいKSynthインスタンスへのポインタ、失敗した場合はNULL + */ + struct KSynth* ksynth_new(const char* sample_file_path, unsigned int sample_rate, unsigned char num_channel, unsigned long max_polyphony); + + /** + * @~english + * @brief Turns a note on for the specified MIDI channel and note. + * + * @param ksynth_instance Pointer to the KSynth instance + * @param channel MIDI channel + * @param note Note number + * @param velocity Velocity + * + * @~japanese + * @brief 指定されたMIDIチャンネルとノートのノートオンを行います。 + * + * @param ksynth_instance KSynthインスタンスへのポインタ + * @param channel MIDIチャンネル + * @param note ノート番号 + * @param velocity ベロシティ + */ + void ksynth_note_on(struct KSynth* ksynth_instance, unsigned char channel, unsigned char note, unsigned char velocity); + + /** + * @~english + * @brief Enables or disables a specific CC for a note or channel. + * + * It current only supports CC 64, also known as Damper Pedal/Sustain. + * + * @param ksynth_instance Pointer to the KSynth instance + * @param channel MIDI channel + * @param param1 CC ID + * @param param2 CC param + * + * @~japanese + * @brief ノートまたはチャンネルの特定の CC を有効または無効にします。 + * + * 現在、ダンパー ペダル/サスティンとも呼ばれる CC 64 のみをサポートしています。 + * + * i don't speak japanese, please fix + * + * @param ksynth_instance KSynthインスタンスへのポインタ + * @param channel MIDIチャンネル + * @param param1 CC ID + * @param param2 CCパラメータ + */ + void ksynth_cc(struct KSynth* ksynth_instance, unsigned char channel, unsigned char param1, unsigned char param2); + + /** + * @~english + * @brief Turns a note off for the specified MIDI channel and note. + * + * @param ksynth_instance Pointer to the KSynth instance + * @param channel MIDI channel + * @param note Note number + * + * @~japanese + * @brief 指定されたMIDIチャンネルとノートのノートオフを行います。 + * + * @param ksynth_instance KSynthインスタンスへのポインタ + * @param channel MIDIチャンネル + * @param note ノート番号 + */ + void ksynth_note_off(struct KSynth* ksynth_instance, unsigned char channel, unsigned char note); -struct KSynth { - struct Sample** samples; - unsigned int sample_rate; - unsigned char num_channel; - unsigned long polyphony; - unsigned char polyphony_per_channel[16]; - unsigned long max_polyphony; - float rendering_time; - struct Voice** voices; -}; - -/** - * @~english - * @brief Creates a new KSynth instance. - * - * This function creates a new KSynth instance with the specified parameters. - * The `sample_file_path` parameter specifies the path to the sample file which will be loaded and used for synthesis. - * The `sample_rate` parameter defines the audio sample rate in Hz. - * The `num_channel` parameter specifies the number of audio channels (1 for mono, 2 for stereo). - * The `max_polyphony` parameter sets the maximum number of simultaneous notes that can be played. - * Values exceeding 100,000 will be clamped to 100,000. - * - * @param sample_file_path Path to the sample file - * @param sample_rate Sample rate - * @param num_channel Number of channels (1-2) - * @param max_polyphony Maximum polyphony (1-100,000) - * @return A pointer to the newly created KSynth instance on success, NULL on failure. - * - * @~japanese - * @brief 新しいKSynthインスタンスを作成します。 - * - * この関数は、指定されたパラメータで新しいKSynthインスタンスを作成します。 - * `sample_file_path`パラメータは、合成に使用されるサンプルファイルへのパスを指定します。 - * `sample_rate`パラメータは、オーディオサンプルレート(Hz)を定義します。 - * `num_channel`パラメータは、オーディオチャンネルの数を指定します(モノラルの場合は1、ステレオの場合は2)。 - * `max_polyphony`パラメータは、同時に再生できる最大ノート数を設定します。 - * 100,000を超える値は100,000にクランプされます。 - * - * @param sample_file_path サンプルファイルへのパス - * @param sample_rate サンプルレート - * @param num_channel チャンネル数(1〜2) - * @param max_polyphony 最大ポリフォニー(1〜100,000) - * @return 成功した場合は新しいKSynthインスタンスへのポインタ、失敗した場合はNULL - */ -struct KSynth* ksynth_new(const char* sample_file_path, unsigned int sample_rate, unsigned char num_channel, unsigned long max_polyphony); - -/** - * @~english - * @brief Turns a note on for the specified MIDI channel and note. - * - * @param ksynth_instance Pointer to the KSynth instance - * @param channel MIDI channel - * @param note Note number - * @param velocity Velocity - * - * @~japanese - * @brief 指定されたMIDIチャンネルとノートのノートオンを行います。 - * - * @param ksynth_instance KSynthインスタンスへのポインタ - * @param channel MIDIチャンネル - * @param note ノート番号 - * @param velocity ベロシティ - */ -void ksynth_note_on(struct KSynth* ksynth_instance, unsigned char channel, unsigned char note, unsigned char velocity); - -/** - * @~english - * @brief Turns a note off for the specified MIDI channel and note. - * - * @param ksynth_instance Pointer to the KSynth instance - * @param channel MIDI channel - * @param note Note number - * - * @~japanese - * @brief 指定されたMIDIチャンネルとノートのノートオフを行います。 - * - * @param ksynth_instance KSynthインスタンスへのポインタ - * @param channel MIDIチャンネル - * @param note ノート番号 - */ -void ksynth_note_off(struct KSynth* ksynth_instance, unsigned char channel, unsigned char note); - -/** - * @~english - * @brief Turns off all notes. - * - * @param ksynth_instance Pointer to the KSynth instance - * - * @~japanese - * @brief すべてのノートをオフにします。 - * - * @param ksynth_instance KSynthインスタンスへのポインタ - */ -void ksynth_note_off_all(struct KSynth* ksynth_instance); - -/** - * @~english - * @brief Gets the current polyphony. - * - * @param ksynth_instance Pointer to the KSynth instance - * @return The current polyphony - * - * @~japanese - * @brief 現在のポリフォニーを取得します。 - * - * @param ksynth_instance KSynthインスタンスへのポインタ - * @return 現在のポリフォニー - */ -int ksynth_get_polyphony(struct KSynth* ksynth_instance); - -/** - * @~english - * @brief Gets the maximum polyphony. - * - * @param ksynth_instance Pointer to the KSynth instance - * @return The maximum polyphony - * - * @~japanese - * @brief 最大ポリフォニーを取得します。 - * - * @param ksynth_instance KSynthインスタンスへのポインタ - * @return 最大ポリフォニー - */ -int ksynth_get_max_polyphony(struct KSynth* ksynth_instance); - -/** - * @~english - * @brief Sets the maximum polyphony. - * @warning DO NOT CALL THIS FUNCTION. IT IS NOT WORKING NOW (IT IS RETURNINNG ALWAYS FALSE). - * - * @param ksynth_instance Pointer to the KSynth instance - * @param max_polyphony Maximum polyphony (Note: Values exceeding 100,000 will be clamped) - * @return Returns true on success, false on failure. - * - * @~japanese - * @brief 最大ポリフォニーを設定します。 - * @warning この関数を呼び出さないでください。動作しません(常にfalseを返します)。 - * - * @param ksynth_instance KSynthインスタンスへのポインタ - * @param max_polyphony 最大ポリフォニー(注:100,000を超える値はクランプされます) - * @return 成功した場合はtrue、失敗した場合はfalse - */ -bool ksynth_set_max_polyphony(struct KSynth* ksynth_instance, unsigned long max_polyphony); - -/** - * @~english - * @brief Generates a buffer with the specified size. - * - * This function generates audio data for a buffer of a given size. - * The size of the buffer determines the duration of audio that will be generated. - * Larger buffer sizes typically result in more audio data being generated per call, - * potentially reducing the frequency of function calls to generate audio. - * However, extremely large buffer sizes may increase memory usage and latency. - * - * @param ksynth_instance Pointer to the KSynth instance - * @param buffer_size Size of the buffer (Note: Values are typically between 32 and 16384) - * @return A pointer to the generated buffer on success, NULL on failure. - * - * @~japanese - * @brief 指定されたサイズのバッファを生成します。 - * - * この関数は、指定されたサイズのバッファ用のオーディオデータを生成します。 - * バッファのサイズは生成されるオーディオの期間を決定します。 - * より大きなバッファサイズは通常、呼び出しごとに生成されるオーディオデータの量を増やし、 - * オーディオを生成するための関数呼び出しの頻度を減らす可能性があります。 - * ただし、非常に大きなバッファサイズはメモリ使用量とレイテンシを増加させる可能性があります。 - * - * @param ksynth_instance KSynthインスタンスへのポインタ - * @param buffer_size バッファのサイズ(注:通常は32から16384の値です) - * @return 成功した場合は生成されたバッファへのポインタ、失敗した場合はNULL - */ -float* ksynth_generate_buffer(struct KSynth* ksynth_instance, unsigned short buffer_size); - -/** - * @~english - * @brief Gets the rendering time. - * - * The rendering time represents the time taken by the synthesis engine to generate audio data for the buffer. - * When the rendering time reaches 100%, it indicates that the synthesis process is taking as long as the buffer duration, - * and any longer processing time may result in buffer underrun, leading to audio glitches or gaps. - * - * @param ksynth_instance Pointer to the KSynth instance - * @return Rendering time as a percentage of the buffer duration (0 to 100%) - * - * @~japanese - * @brief レンダリング時間を取得します。 - * - * レンダリング時間は、合成エンジンがバッファのためのオーディオデータを生成するのにかかる時間を表します。 - * レンダリング時間が100%に達すると、合成プロセスがバッファの期間と同じくらいの時間を取ることを示し、 - * より長い処理時間はバッファのアンダーランを引き起こし、オーディオのグリッチやギャップを引き起こす可能性があります。 - * - * @param ksynth_instance KSynthインスタンスへのポインタ - * @return バッファの期間の割合としてのレンダリング時間(0から100%) - */ -float ksynth_get_rendering_time(struct KSynth* ksynth_instance); - -/** - * @~english - * @brief Gets the number of active polyphony for the specified MIDI channel. - * - * @param ksynth_instance Pointer to the KSynth instance - * @param channel MIDI channel - * @return Number of active polyphony for the specified MIDI channel - * - * @~japanese - * @brief 指定されたMIDIチャンネルのアクティブなポリフォニーの数を取得します。 - * - * @param ksynth_instance KSynthインスタンスへのポインタ - * @param channel MIDIチャンネル - * @return 指定されたMIDIチャンネルのアクティブなポリフォニーの数 - */ -int ksynth_get_polyphony_for_channel(struct KSynth* ksynth_instance, unsigned char channel); - -/** - * @~english - * @brief Frees the buffer. - * - * @param buffer Pointer to the buffer to be freed - * - * @~japanese - * @brief バッファを解放します。 - * - * @param buffer 解放するバッファへのポインタ - */ -void ksynth_buffer_free(float* buffer); - -/** - * @~english - * @brief Frees the KSynth instance. - * - * @param ksynth_instance Pointer to the KSynth instance to be freed - * - * @~japanese - * @brief KSynthインスタンスを解放します。 - * - * @param ksynth_instance 解放するKSynthインスタンスへのポインタ - */ -void ksynth_free(struct KSynth* ksynth_instance); + /** + * @~english + * @brief Turns off all notes. + * + * @param ksynth_instance Pointer to the KSynth instance + * + * @~japanese + * @brief すべてのノートをオフにします。 + * + * @param ksynth_instance KSynthインスタンスへのポインタ + */ + void ksynth_note_off_all(struct KSynth* ksynth_instance); + + /** + * @~english + * @brief Gets the current polyphony. + * + * @param ksynth_instance Pointer to the KSynth instance + * @return The current polyphony + * + * @~japanese + * @brief 現在のポリフォニーを取得します。 + * + * @param ksynth_instance KSynthインスタンスへのポインタ + * @return 現在のポリフォニー + */ + int ksynth_get_polyphony(struct KSynth* ksynth_instance); + + /** + * @~english + * @brief Gets the maximum polyphony. + * + * @param ksynth_instance Pointer to the KSynth instance + * @return The maximum polyphony + * + * @~japanese + * @brief 最大ポリフォニーを取得します。 + * + * @param ksynth_instance KSynthインスタンスへのポインタ + * @return 最大ポリフォニー + */ + int ksynth_get_max_polyphony(struct KSynth* ksynth_instance); + + /** + * @~english + * @brief Sets the maximum polyphony. + * + * @param ksynth_instance Pointer to the KSynth instance + * @param max_polyphony Maximum polyphony (Note: Values exceeding 100,000 will be clamped) + * @return Returns true on success, false on failure. + * + * @~japanese + * @brief 最大ポリフォニーを設定します。 + * @warning この関数を呼び出さないでください。動作しません(常にfalseを返します)。 + * + * @param ksynth_instance KSynthインスタンスへのポインタ + * @param max_polyphony 最大ポリフォニー(注:100,000を超える値はクランプされます) + * @return 成功した場合はtrue、失敗した場合はfalse + */ + bool ksynth_set_max_polyphony(struct KSynth* ksynth_instance, unsigned long max_polyphony); + + /** + * @~english + * @brief Generates a buffer with the specified size. + * + * This function generates audio data for a buffer of a given size. + * The size of the buffer determines the duration of audio that will be generated. + * Smaller buffer sizes allow for more granularity in the audio, making strummed notes + * sound better. + * Larger buffer sizes typically result in more audio data being generated per call, + * potentially reducing the frequency of function calls to generate audio. + * However, extremely large buffer sizes may increase memory usage and latency, and make + * the audio sound choppy as a result. + * + * @param ksynth_instance Pointer to the KSynth instance + * @param buffer Pointer to the buffer you want to fill up with data + * @param buffer_size Size of the buffer + * + * @~japanese + * @brief 指定されたサイズのバッファを生成します。 + * + * この関数は、指定されたサイズのバッファーのオーディオ データを生成します。 + * バッファのサイズによって、生成されるオーディオの長さが決まります。 + * バッファ サイズを小さくすると、オーディオの粒度が向上し、かき鳴らされたノートの音が良くなります。 + * 通常、バッファ サイズが大きいと、呼び出しごとに生成される音声データが多くなり、音声を生成するための関数呼び出しの頻度が減少する可能性があります。 + * ただし、バッファ サイズが非常に大きいと、メモリ使用量と遅延が増加し、結果として音声が途切れ途切れになる可能性があります。 + * + * i don't speak japanese, please fix + * + * @param ksynth_instance KSynthインスタンスへのポインタ + * @param buffer AAAA + * @param buffer_size バッファのサイズ + */ + void ksynth_fill_buffer(struct KSynth* ksynth_instance, float* buffer, unsigned int buffer_size); + + /** + * @~english + * @brief Generates a buffer with the specified size. + * + * This function generates audio data for a buffer of a given size. + * It internally points to ksynth_fill_buffer, but instead of having the user manage the buffer, + * this function prepares it for them and gives them a pointer as a return value. + * + * @param ksynth_instance Pointer to the KSynth instance + * @param buffer_size Size of the buffer (Note: Values are typically between 32 and 16384) + * @return A pointer to the generated buffer on success, NULL on failure. + * + * @~japanese + * @brief 指定されたサイズのバッファを生成します。 + * + * この関数は、指定されたサイズのバッファーのオーディオ データを生成します。 + * この関数は内部的に ksynth_fill_buffer を指しますが、ユーザーにバッファーを管理させる代わりに、この関数はユーザーのためにバッファーを準備し、戻り値としてポインターを与えます。 + * + * i don't speak japanese, please fix + * + * @param ksynth_instance KSynthインスタンスへのポインタ + * @param buffer_size バッファのサイズ(注:通常は32から16384の値です) + * @return 成功した場合は生成されたバッファへのポインタ、失敗した場合はNULL + */ + float* ksynth_generate_buffer(struct KSynth* ksynth_instance, unsigned int buffer_size); + + /** + * @~english + * @brief Gets the rendering time. + * + * The rendering time represents the time taken by the synthesis engine to generate audio data for the buffer. + * When the rendering time reaches 100%, it indicates that the synthesis process is taking as long as the buffer duration, + * and any longer processing time may result in buffer underrun, leading to audio glitches or gaps. + * + * @param ksynth_instance Pointer to the KSynth instance + * @return Rendering time as a percentage of the buffer duration (0 to 100%) + * + * @~japanese + * @brief レンダリング時間を取得します。 + * + * レンダリング時間は、合成エンジンがバッファのためのオーディオデータを生成するのにかかる時間を表します。 + * レンダリング時間が100%に達すると、合成プロセスがバッファの期間と同じくらいの時間を取ることを示し、 + * より長い処理時間はバッファのアンダーランを引き起こし、オーディオのグリッチやギャップを引き起こす可能性があります。 + * + * @param ksynth_instance KSynthインスタンスへのポインタ + * @return バッファの期間の割合としてのレンダリング時間(0から100%) + */ + float ksynth_get_rendering_time(struct KSynth* ksynth_instance); + + /** + * @~english + * @brief Gets the number of active polyphony for the specified MIDI channel. + * + * @param ksynth_instance Pointer to the KSynth instance + * @param channel MIDI channel + * @return Number of active polyphony for the specified MIDI channel + * + * @~japanese + * @brief 指定されたMIDIチャンネルのアクティブなポリフォニーの数を取得します。 + * + * @param ksynth_instance KSynthインスタンスへのポインタ + * @param channel MIDIチャンネル + * @return 指定されたMIDIチャンネルのアクティブなポリフォニーの数 + */ + int ksynth_get_polyphony_for_channel(struct KSynth* ksynth_instance, unsigned char channel); + + /** + * @~english + * @brief Frees the buffer. + * + * @param buffer Pointer to the buffer to be freed + * + * @~japanese + * @brief バッファを解放します。 + * + * @param buffer 解放するバッファへのポインタ + */ + void ksynth_buffer_free(float* buffer); + + /** + * @~english + * @brief Frees the KSynth instance. + * + * @param ksynth_instance Pointer to the KSynth instance to be freed + * + * @~japanese + * @brief KSynthインスタンスを解放します。 + * + * @param ksynth_instance 解放するKSynthインスタンスへのポインタ + */ + void ksynth_free(struct KSynth* ksynth_instance); #ifdef __cplusplus } #endif -#endif +#endif \ No newline at end of file diff --git a/src/sample.c b/src/sample.c index 3be4349..35e7119 100644 --- a/src/sample.c +++ b/src/sample.c @@ -1,11 +1,11 @@ -/* $Id$ */ - -#include "sample.h" -#include -#include - -void sample_free(struct Sample* sample) { - if(sample != NULL) { - free(sample); - } -} +/* $Id$ */ + +#include "sample.h" +#include +#include + +void sample_free(struct Sample* sample) { + if (sample != NULL) { + free(sample); + } +} \ No newline at end of file diff --git a/src/sample.h b/src/sample.h index 0726769..7642ad3 100644 --- a/src/sample.h +++ b/src/sample.h @@ -1,21 +1,20 @@ -/* $Id$ */ - -#ifndef SAMPLE_H -#define SAMPLE_H -#ifdef __cplusplus -extern "C" { -#endif - -#include - -struct Sample { - float* sample; - unsigned int length; -}; - -void sample_free(struct Sample* sample); - -#ifdef __cplusplus -} -#endif -#endif +#ifndef SAMPLE_H +#define SAMPLE_H +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct Sample { + float* sample; + unsigned int sample_rate; + unsigned int length; +}; + +void sample_free(struct Sample* sample); + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/src/voice.c b/src/voice.c index 3a5a5c9..6553c17 100644 --- a/src/voice.c +++ b/src/voice.c @@ -1,10 +1,22 @@ -/* $Id$ */ - -#include "voice.h" -#include - -void voice_free(struct Voice* voice) { - if(voice != NULL) { - free(voice); - } -} +/* $Id$ */ + +#include "voice.h" +#include + +void voice_free(struct Voice* voice) { + if (voice != NULL) { + free(voice); + } +} + +void voice_reset(struct Voice* voice) { + voice->killed = 1; + + voice->channel = 0; + voice->curfalloff = 0; + voice->noteNumber = 0; + voice->sample_position = 0; + + voice->tokill = 0; + voice->velocity = 0; +} \ No newline at end of file diff --git a/src/voice.h b/src/voice.h index 88ede9e..9d7bf53 100644 --- a/src/voice.h +++ b/src/voice.h @@ -1,24 +1,25 @@ -/* $Id$ */ - -#ifndef VOICE_H -#define VOICE_H -#ifdef __cplusplus -extern "C" { -#endif - -#include - -struct Voice { - unsigned int sample_rate; - unsigned char channel; - unsigned char noteNumber; - unsigned char velocity; - unsigned int sample_position; -}; - -void voice_free(struct Voice* voice); - -#ifdef __cplusplus -} -#endif -#endif +#ifndef VOICE_H +#define VOICE_H +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct Voice { + unsigned char killed; + unsigned char tokill; + unsigned char channel; + unsigned char noteNumber; + unsigned char velocity; + unsigned int curfalloff; + unsigned int sample_position; +}; + +void voice_free(struct Voice* voice); +void voice_reset(struct Voice* voice); + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file