From 15a56278620a66fd6916d8179c4200e761682997 Mon Sep 17 00:00:00 2001 From: m1maker Date: Sun, 29 Dec 2024 19:01:47 +0300 Subject: [PATCH] Add audio utilities (audio.as). --- Release/Include/audio.as | 158 +++++++++++++++++++++++++++++++++++++++ Release/Tests/audio.ngt | 26 +++++++ Release/Tests/pcm.ngt | 12 ++- SRC/ngt.cpp | 19 ++++- SRC/ngt.h | 5 ++ SRC/ngtreg.cpp | 5 ++ 6 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 Release/Include/audio.as create mode 100644 Release/Tests/audio.ngt diff --git a/Release/Include/audio.as b/Release/Include/audio.as new file mode 100644 index 0000000..781feff --- /dev/null +++ b/Release/Include/audio.as @@ -0,0 +1,158 @@ +#include "hip.as" // clamp + +string pack_audio(array@ data) +{ + string result; + for (uint i = 0; i < data.length(); ++i) + { + result += float_to_bytes(data[i]); + } + return result; +} + +array@ unpack_audio(const string &in data) +{ + array result; + + uint numFloats = data.length() / sizeof(0.0f); + result.resize(numFloats); + + for (uint i = 0; i < numFloats; ++i) + { + string floatBytes = data.substr(i * sizeof(0.0f), sizeof(0.0f)); + result[i] = bytes_to_float(floatBytes); + } + + return result; +} + + +array@ mix_audio(const array@ audio1, const array@ audio2, bool avoid_clip = true) +{ + uint maxLength = audio1.length() > audio2.length() ? audio1.length() : audio2.length(); + array mixedAudio; + mixedAudio.resize(maxLength); + + for (uint i = 0; i < maxLength; ++i) + { + float sample1 = (i < audio1.length()) ? audio1[i] : 0.0f; + float sample2 = (i < audio2.length()) ? audio2[i] : 0.0f; + + mixedAudio[i] = sample1 + sample2; + if (avoid_clip) + { + if (mixedAudio[i] > 1.0f) mixedAudio[i] = 1.0f; + else if (mixedAudio[i] < -1.0f) mixedAudio[i] = -1.0f; + } + } + return mixedAudio; +} + + +array@ generate_sine(int freq, int channels, int sample_rate, int samples) { + array @waveform = array(samples * channels); + float increment = (2.0 * 3.14159265 * freq) / sample_rate; + + for (int i = 0; i < samples; ++i) { + float sample = sin(increment * i); + for (int ch = 0; ch < channels; ++ch) { + waveform[i * channels + ch] = sample; + } + } + + return waveform; +} + + +array@ generate_square(int freq, int channels, int sample_rate, int samples) { + array @waveform = array(samples * channels); + float period = sample_rate / freq; + + for (int i = 0; i < samples; ++i) { + float sample = (i % period < period / 2) ? 1.0 : -1.0; + for (int ch = 0; ch < channels; ++ch) { + waveform[i * channels + ch] = sample; + } + } + + return waveform; +} + + +array@ generate_sawtooth(int freq, int channels, int sample_rate, int samples) { + array @waveform = array(samples * channels); + float increment = (2.0 / sample_rate) * freq; + + for (int i = 0; i < samples; ++i) { + float sample = (2.0 * (i * increment - floor(0.5 + i * increment))) - 1.0; + for (int ch = 0; ch < channels; ++ch) { + waveform[i * channels + ch] = sample; + } + } + + return waveform; +} + + +array@ generate_triangle(int freq, int channels, int sample_rate, int samples) { + array @waveform = array(samples * channels); + float increment = (2.0 / sample_rate) * freq; + + for (int i = 0; i < samples; ++i) { + float sample = 2.0 * abs(2.0 * ((i * increment) - floor((i * increment) + 0.5))) - 1.0; + for (int ch = 0; ch < channels; ++ch) { + waveform[i * channels + ch] = sample; + } + } + + return waveform; +} + +array@ generate_noise(int channels, int samples) { + array @waveform = array(samples * channels); + for (int i = 0; i < samples; ++i) { + float sample = random(0, 1); + for (int ch = 0; ch < channels; ++ch) { + waveform[i * channels + ch] = sample; + } + } + return waveform; +} + +array@ generate_silence(int channels, int samples) { + array @waveform = array(samples * channels); + for (int i = 0; i < samples * channels; ++i) { + waveform[i] = 0.0f; // Silence + } + return waveform; +} + +array@ apply_envelope(const array@ audio, float attack, float decay, float sustain, float release, int sample_rate) { + array @envelopedAudio = array(audio.length()); + int totalSamples = audio.length(); + int attackSamples = int(sample_rate * attack); + int decaySamples = int(sample_rate * decay); + int releaseSamples = int(sample_rate * release); + + // Attack phase + for (int i = 0; i < attackSamples && i < totalSamples; ++i) { + envelopedAudio[i] = audio[i] * (i / float(attackSamples)); + } + + // Decay phase + for (int i = 0; i < decaySamples && (attackSamples + i) < totalSamples; ++i) { + envelopedAudio[attackSamples + i] = audio[attackSamples + i] * (1.0f - (i / float(decaySamples))) + sustain * (i / float(decaySamples)); + } + + // Sustain phase + for (int i = 0; i < (totalSamples - attackSamples - decaySamples) && (attackSamples + decaySamples + i) < totalSamples; ++i) { + envelopedAudio[attackSamples + decaySamples + i] = audio[attackSamples + decaySamples + i] * sustain; + } + + // Release phase + for (int i = 0; i < releaseSamples && (totalSamples - releaseSamples + i) < totalSamples; ++i) { + envelopedAudio[totalSamples - releaseSamples + i] = audio[totalSamples - releaseSamples + i] * (1.0f - (i / float(releaseSamples))); + } + + return envelopedAudio; +} diff --git a/Release/Tests/audio.ngt b/Release/Tests/audio.ngt new file mode 100644 index 0000000..f942d7a --- /dev/null +++ b/Release/Tests/audio.ngt @@ -0,0 +1,26 @@ +#include "../include/audio.as" +void main() { + int sampleRate = 44100; + int samples = 44100; // 1 second of audio + + // Generate a sine wave + array@ sineWave = generate_sine(440, 2, sampleRate, samples); + + // Generate noise + array@ noiseWave = generate_noise(2, samples); + + // Mix the sine wave and noise + array@ mixedAudio = mix_audio(sineWave, noiseWave); + + // Apply an envelope to the mixed audio + array@ finalAudio = apply_envelope(mixedAudio, 0.1f, 0.1f, 0.7f, 0.2f, sampleRate); + + // Pack the final audio data for output + string packedData = pack_audio(finalAudio); + + sound s; + s.load_pcm(packedData, samples, 2, sampleRate, 32); + s.play_wait(); + s.close(); +} + diff --git a/Release/Tests/pcm.ngt b/Release/Tests/pcm.ngt index 4db5138..795e97e 100644 --- a/Release/Tests/pcm.ngt +++ b/Release/Tests/pcm.ngt @@ -2,13 +2,17 @@ void main(){ sound s; tts_voice v; size_t size; -string pcm = v.speak_to_memory("Test", size); +string pcm = v.speak_to_memory("S", size); show_window("Test"); +v.speak("Are you ready?"); +wait(1500); +v.speak_interrupt("Let's go"); +wait(500); thread_func@ func = function(dictionary@ args){ tts_voice@ v = cast(args[0]); while (!quit_requested){ +v.speak(random_bool() ? "Bla" : "Meh"); wait(1000); -v.speak("Bla"); } }; dictionary args; @@ -20,12 +24,12 @@ s.load_pcm(pcm, size/2, 1, 16000, 16); bool result = random_bool(); if (result){ s.pitch = random(90, 100); -s.set_fx("reverb"); +s.set_fx(random_bool() ? "reverb" : "delay"); s.set_reverb_parameters(1.0f, 0.5f, 1.0f, 0, 0); } s.hrtf = true; s.set_position(0, 0, 0, random(-10, 10), random(-10, 10), 00); -s.play(); +s.play_looped(); wait(result ? 500 : 250); s.close(); } diff --git a/SRC/ngt.cpp b/SRC/ngt.cpp index c5cd625..e75bb77 100644 --- a/SRC/ngt.cpp +++ b/SRC/ngt.cpp @@ -649,6 +649,23 @@ CScriptArray* keys_repeat() { } return array; } +// Sam Tupy NVGT's functions +std::string float_to_bytes(float f) { + return std::string((char*)&f, 4); +} +float bytes_to_float(const std::string& s) { + if (s.size() != 4) return 0; + return *((float*)&s[0]); +} +std::string double_to_bytes(double d) { + return std::string((char*)&d, 8); +} +double bytes_to_double(const std::string& s) { + if (s.size() != 8) return 0; + return *((double*)&s[0]); +} + + string number_to_words(uint64_t num, bool include_and) { vector buf(1024); @@ -746,7 +763,7 @@ return""; } return result; #endif -} + } bool key_pressed(int key_code) { if (keys[key_code].isDown == true and keys[key_code].isPressed == false) diff --git a/SRC/ngt.h b/SRC/ngt.h index c8beb0a..c3e6aec 100644 --- a/SRC/ngt.h +++ b/SRC/ngt.h @@ -147,6 +147,11 @@ CScriptArray* keys_pressed(); CScriptArray* keys_released(); CScriptArray* keys_down(); CScriptArray* keys_repeat(); +std::string float_to_bytes(float f); +float bytes_to_float(const std::string& s); +std::string double_to_bytes(double d); +double bytes_to_double(const std::string& s); + string string_encrypt(const string& str, string encryption_key); string string_decrypt(const string& str, string encryption_key); string url_decode(const string& url); diff --git a/SRC/ngtreg.cpp b/SRC/ngtreg.cpp index a7e539d..ddd7f16 100644 --- a/SRC/ngtreg.cpp +++ b/SRC/ngtreg.cpp @@ -403,6 +403,11 @@ void RegisterFunctions(asIScriptEngine* engine) engine->RegisterGlobalFunction("string url_get(const string &in url)", asFUNCTION(url_get), asCALL_CDECL); engine->RegisterGlobalFunction("string url_post(const string &in url, const string &in parameters)", asFUNCTION(url_post), asCALL_CDECL); AS_END(engine); + engine->RegisterGlobalFunction(_O("string float_to_bytes(float number)"), asFUNCTION(float_to_bytes), asCALL_CDECL); + engine->RegisterGlobalFunction(_O("float bytes_to_float(const string&in data)"), asFUNCTION(bytes_to_float), asCALL_CDECL); + engine->RegisterGlobalFunction(_O("string double_to_bytes(double number)"), asFUNCTION(double_to_bytes), asCALL_CDECL); + engine->RegisterGlobalFunction(_O("double bytes_to_double(const string&in data)"), asFUNCTION(bytes_to_double), asCALL_CDECL); + engine->RegisterGlobalFunction("string ascii_to_character(int ascii)", asFUNCTION(ascii_to_character), asCALL_CDECL); engine->RegisterGlobalFunction("int character_to_ascii(const string &in character)", asFUNCTION(character_to_ascii), asCALL_CDECL); engine->RegisterGlobalFunction("string hex_to_string(const string&in the_hexadecimal_sequence)", asFUNCTION(hex_to_string), asCALL_CDECL);