diff --git a/modules/renderer/audio/sounds.ts b/modules/renderer/audio/sounds.ts
index 0d2b3e0..994a7f8 100644
--- a/modules/renderer/audio/sounds.ts
+++ b/modules/renderer/audio/sounds.ts
@@ -1,31 +1,31 @@
-window["require_setup"](module);
-//
-///
-
-import {audio as naudio} from "teaclient_connection";
-import * as paths from "path";
-
-namespace audio.sounds {
- export async function play_sound(file: sound.SoundFile) : Promise {
- await new Promise((resolve, reject) => {
- let pathname = paths.dirname(location.pathname);
- if(pathname[0] === '/' && pathname[2] === ':') //e.g.: /C:/test...
- pathname = pathname.substr(1);
- const path = paths.join(pathname, file.path);
-
- console.log(path);
- naudio.sounds.playback_sound({
- callback: (result, message) => {
- if(result == naudio.sounds.PlaybackResult.SUCCEEDED)
- resolve();
- else
- reject(naudio.sounds.PlaybackResult[result].toLowerCase() + ": " + message);
- },
- file: path,
- volume: file.volume
- });
- });
- }
-}
-
+window["require_setup"](module);
+//
+///
+
+import {audio as naudio} from "teaclient_connection";
+import * as paths from "path";
+
+namespace audio.sounds {
+ export async function play_sound(file: sound.SoundFile) : Promise {
+ await new Promise((resolve, reject) => {
+ let pathname = paths.dirname(location.pathname);
+ if(pathname[0] === '/' && pathname[2] === ':') //e.g.: /C:/test...
+ pathname = pathname.substr(1);
+ const path = paths.join(pathname, file.path);
+
+ console.log(path);
+ naudio.sounds.playback_sound({
+ callback: (result, message) => {
+ if(result == naudio.sounds.PlaybackResult.SUCCEEDED)
+ resolve();
+ else
+ reject(naudio.sounds.PlaybackResult[result].toLowerCase() + ": " + message);
+ },
+ file: path,
+ volume: file.volume
+ });
+ });
+ }
+}
+
Object.assign(window["audio"] || (window["audio"] = {} as any), audio);
\ No newline at end of file
diff --git a/native/serverconnection/src/audio/file/wav.cpp b/native/serverconnection/src/audio/file/wav.cpp
index 4c5b5db..4db294c 100644
--- a/native/serverconnection/src/audio/file/wav.cpp
+++ b/native/serverconnection/src/audio/file/wav.cpp
@@ -1,180 +1,180 @@
-//
-// Created by WolverinDEV on 18/03/2020.
-//
-
-#include "./wav.h"
-#include "../../logger.h"
-
-using namespace tc::audio::file;
-
-struct WAVFileHeader {
- /* RIFF Chunk Descriptor */
- uint8_t RIFF[4]; // RIFF Header Magic header
- uint32_t ChunkSize; // RIFF Chunk Size
- uint8_t WAVE[4]; // WAVE Header
-
- /* "fmt" sub-chunk */
- uint8_t fmt[4]; // FMT header
- uint32_t Subchunk1Size; // Size of the fmt chunk
- uint16_t AudioFormat; // Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM
- uint16_t NumOfChan; // Number of channels 1=Mono 2=Sterio
- uint32_t SamplesPerSec; // Sampling Frequency in Hz
- uint32_t bytesPerSec; // bytes per second
- uint16_t blockAlign; // 2=16-bit mono, 4=16-bit stereo
- uint16_t bitsPerSample; // Number of bits per sample
-};
-static_assert(sizeof(WAVFileHeader) == 0x24);
-
-struct WAFFileChunk {
- uint8_t id[4];
- uint32_t size;
-};
-static_assert(sizeof(WAFFileChunk) == 0x08);
-
-WAVReader::WAVReader(std::string file) : file_path_{std::move(file)} {}
-WAVReader::~WAVReader() {
- this->close_file();
-}
-
-FileOpenResult WAVReader::open_file(std::string& error) {
- this->is_.open(this->file_path_, std::ifstream::in | std::ifstream::binary);
- if(!this->is_) {
- error = tr("failed to open file");
- return FileOpenResult::OPEN_RESULT_ERROR;
- }
-
- WAVFileHeader header{};
- if(!this->is_.read((char*) &header, sizeof(header))) {
- error = tr("failed to read wav header");
- return FileOpenResult::OPEN_RESULT_ERROR;
- }
-
- if(memcmp(header.RIFF, "RIFF", 4) != 0) {
- error = tr("invalid RIFF header");
- return FileOpenResult::OPEN_RESULT_ERROR;
- }
-
- if(memcmp(header.WAVE, "WAVE", 4) != 0) {
- error = tr("invalid WAVE header");
- return FileOpenResult::OPEN_RESULT_ERROR;
- }
-
- if(memcmp(header.fmt, "fmt ", 4) != 0) {
- error = tr("invalid WAVE header");
- return FileOpenResult::OPEN_RESULT_ERROR;
- }
-
- if(header.AudioFormat != 1) {
- error = tr("Only PCM has been supported. WAV file does not contains PCM data.");
- return FileOpenResult::OPEN_RESULT_FORMAT_UNSUPPORTED;
- }
-
- if(header.bytesPerSec != (header.NumOfChan * header.SamplesPerSec * header.bitsPerSample) / 8) {
- error = tr("inconsistent WAV header");
- return FileOpenResult::OPEN_RESULT_INVALID_FORMAT;
- }
-
- if(header.bitsPerSample != 8 && header.bitsPerSample != 16 && header.bitsPerSample != 24) {
- error = tr("unsupported bitrate");
- return FileOpenResult::OPEN_RESULT_FORMAT_UNSUPPORTED;
- }
-
- if(header.NumOfChan != 2 && header.NumOfChan != 1) {
- error = tr("unsupported channel count");
- return FileOpenResult::OPEN_RESULT_FORMAT_UNSUPPORTED;
- }
-
- WAFFileChunk chunk{};
- while(true) {
- if(!this->is_.read((char*) &chunk, sizeof(chunk))) {
- error = tr("failed to read chunks until data chunk");
- return FileOpenResult::OPEN_RESULT_ERROR;
- }
-
- if(memcmp(chunk.id, "data ", 4) == 0)
- break;
-
- this->is_.seekg(chunk.size, std::ifstream::cur);
- }
-
- this->current_sample_offset_ = 0;
- this->bytes_per_sample = header.bitsPerSample / 8;
- this->total_samples_ = chunk.size / this->bytes_per_sample / header.NumOfChan;
- this->sample_rate_ = header.SamplesPerSec;
- this->channels_ = header.NumOfChan;
- return FileOpenResult::OPEN_RESULT_SUCCESS;
-}
-
-void WAVReader::close_file() {
- this->total_samples_ = 0;
- this->bytes_per_sample = 0;
- this->sample_rate_ = 0;
- this->channels_ = 0;
- this->is_.close();
-}
-
-float _8bit_float_convert(const uint8_t* buffer) {
- int16_t value = buffer[0] & 0xFFU;
- return (float) (value - 127) * (1.0f / 127.0f);
-}
-
-float _16bit_float_convert(const uint8_t* buffer) {
- int16_t value = *reinterpret_cast(buffer);
- return (float) value / 32767.f;
-}
-
-float _24bit_float_convert(const uint8_t* buffer) {
-#if 0
- int32_t value = (*reinterpret_cast(buffer) & 0xFFFFFFU) << 8;
- return (float) (value - 1073741824) / 1073741824.f; //2147483648 / 2
-#endif
-#if 1
- int32_t value = ((uint32_t) buffer[2] << 16U) | ((uint32_t) buffer[1] << 8U) | ((uint32_t) buffer[0] << 0U);
- if (value & 0x800000) // if the 24th bit is set, this is a negative number in 24-bit world
- value = value | ~0xFFFFFF; // so make sure sign is extended to the 32 bit float
- auto result = (float) value / (float) 8388608.f; //8388608
- return result;
-#endif
-}
-
-static std::array pcm_to_float_converters{
- _8bit_float_convert,
- _16bit_float_convert,
- _24bit_float_convert
-};
-
-ReadResult WAVReader::read(void *buffer, size_t* samples) {
- auto fbuffer = (float*) buffer;
-
- const auto max_sample_count = this->total_samples_ - this->current_sample_offset_;
- const auto max_samples = std::min(*samples, max_sample_count);
- if(max_samples == 0) {
- if(max_sample_count == 0) return ReadResult::READ_RESULT_EOF;
-
- return ReadResult::READ_RESULT_SUCCESS;
- }
-
- constexpr size_t sbuffer_size{1536}; /* must be dividable by 24, 165 and 8 bit! As well by two channels so 6, 4 and 2 byte to avoid to mess up one frame */
- uint8_t sbuffer[sbuffer_size];
-
- size_t samples_read{0};
- auto fconverter = pcm_to_float_converters[(this->bytes_per_sample - 1) & 0x3U];
- while(samples_read < max_samples) {
- const auto block_byte_length{std::min(sbuffer_size, (max_samples - samples_read) * this->bytes_per_sample * this->channels_)};
- if(!this->is_.read((char*) sbuffer, block_byte_length))
- return ReadResult::READ_RESULT_UNRECOVERABLE_ERROR;
-
- uint8_t* sbufferptr = sbuffer;
- uint8_t* sbuferendptr = sbuffer + block_byte_length;
- while(sbufferptr != sbuferendptr) {
- *fbuffer++ = fconverter(sbufferptr);
- sbufferptr += this->bytes_per_sample;
- }
-
- samples_read += block_byte_length / (this->bytes_per_sample * this->channels_);
- }
-
- *samples = samples_read;
- this->current_sample_offset_ += samples_read;
- return ReadResult::READ_RESULT_SUCCESS;
+//
+// Created by WolverinDEV on 18/03/2020.
+//
+
+#include "./wav.h"
+#include "../../logger.h"
+
+using namespace tc::audio::file;
+
+struct WAVFileHeader {
+ /* RIFF Chunk Descriptor */
+ uint8_t RIFF[4]; // RIFF Header Magic header
+ uint32_t ChunkSize; // RIFF Chunk Size
+ uint8_t WAVE[4]; // WAVE Header
+
+ /* "fmt" sub-chunk */
+ uint8_t fmt[4]; // FMT header
+ uint32_t Subchunk1Size; // Size of the fmt chunk
+ uint16_t AudioFormat; // Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM
+ uint16_t NumOfChan; // Number of channels 1=Mono 2=Sterio
+ uint32_t SamplesPerSec; // Sampling Frequency in Hz
+ uint32_t bytesPerSec; // bytes per second
+ uint16_t blockAlign; // 2=16-bit mono, 4=16-bit stereo
+ uint16_t bitsPerSample; // Number of bits per sample
+};
+static_assert(sizeof(WAVFileHeader) == 0x24);
+
+struct WAFFileChunk {
+ uint8_t id[4];
+ uint32_t size;
+};
+static_assert(sizeof(WAFFileChunk) == 0x08);
+
+WAVReader::WAVReader(std::string file) : file_path_{std::move(file)} {}
+WAVReader::~WAVReader() {
+ this->close_file();
+}
+
+FileOpenResult WAVReader::open_file(std::string& error) {
+ this->is_.open(this->file_path_, std::ifstream::in | std::ifstream::binary);
+ if(!this->is_) {
+ error = tr("failed to open file");
+ return FileOpenResult::OPEN_RESULT_ERROR;
+ }
+
+ WAVFileHeader header{};
+ if(!this->is_.read((char*) &header, sizeof(header))) {
+ error = tr("failed to read wav header");
+ return FileOpenResult::OPEN_RESULT_ERROR;
+ }
+
+ if(memcmp(header.RIFF, "RIFF", 4) != 0) {
+ error = tr("invalid RIFF header");
+ return FileOpenResult::OPEN_RESULT_ERROR;
+ }
+
+ if(memcmp(header.WAVE, "WAVE", 4) != 0) {
+ error = tr("invalid WAVE header");
+ return FileOpenResult::OPEN_RESULT_ERROR;
+ }
+
+ if(memcmp(header.fmt, "fmt ", 4) != 0) {
+ error = tr("invalid WAVE header");
+ return FileOpenResult::OPEN_RESULT_ERROR;
+ }
+
+ if(header.AudioFormat != 1) {
+ error = tr("Only PCM has been supported. WAV file does not contains PCM data.");
+ return FileOpenResult::OPEN_RESULT_FORMAT_UNSUPPORTED;
+ }
+
+ if(header.bytesPerSec != (header.NumOfChan * header.SamplesPerSec * header.bitsPerSample) / 8) {
+ error = tr("inconsistent WAV header");
+ return FileOpenResult::OPEN_RESULT_INVALID_FORMAT;
+ }
+
+ if(header.bitsPerSample != 8 && header.bitsPerSample != 16 && header.bitsPerSample != 24) {
+ error = tr("unsupported bitrate");
+ return FileOpenResult::OPEN_RESULT_FORMAT_UNSUPPORTED;
+ }
+
+ if(header.NumOfChan != 2 && header.NumOfChan != 1) {
+ error = tr("unsupported channel count");
+ return FileOpenResult::OPEN_RESULT_FORMAT_UNSUPPORTED;
+ }
+
+ WAFFileChunk chunk{};
+ while(true) {
+ if(!this->is_.read((char*) &chunk, sizeof(chunk))) {
+ error = tr("failed to read chunks until data chunk");
+ return FileOpenResult::OPEN_RESULT_ERROR;
+ }
+
+ if(memcmp(chunk.id, "data ", 4) == 0)
+ break;
+
+ this->is_.seekg(chunk.size, std::ifstream::cur);
+ }
+
+ this->current_sample_offset_ = 0;
+ this->bytes_per_sample = header.bitsPerSample / 8;
+ this->total_samples_ = chunk.size / this->bytes_per_sample / header.NumOfChan;
+ this->sample_rate_ = header.SamplesPerSec;
+ this->channels_ = header.NumOfChan;
+ return FileOpenResult::OPEN_RESULT_SUCCESS;
+}
+
+void WAVReader::close_file() {
+ this->total_samples_ = 0;
+ this->bytes_per_sample = 0;
+ this->sample_rate_ = 0;
+ this->channels_ = 0;
+ this->is_.close();
+}
+
+float _8bit_float_convert(const uint8_t* buffer) {
+ int16_t value = buffer[0] & 0xFFU;
+ return (float) (value - 127) * (1.0f / 127.0f);
+}
+
+float _16bit_float_convert(const uint8_t* buffer) {
+ int16_t value = *reinterpret_cast(buffer);
+ return (float) value / 32767.f;
+}
+
+float _24bit_float_convert(const uint8_t* buffer) {
+#if 0
+ int32_t value = (*reinterpret_cast(buffer) & 0xFFFFFFU) << 8;
+ return (float) (value - 1073741824) / 1073741824.f; //2147483648 / 2
+#endif
+#if 1
+ int32_t value = ((uint32_t) buffer[2] << 16U) | ((uint32_t) buffer[1] << 8U) | ((uint32_t) buffer[0] << 0U);
+ if (value & 0x800000) // if the 24th bit is set, this is a negative number in 24-bit world
+ value = value | ~0xFFFFFF; // so make sure sign is extended to the 32 bit float
+ auto result = (float) value / (float) 8388608.f; //8388608
+ return result;
+#endif
+}
+
+static std::array pcm_to_float_converters{
+ _8bit_float_convert,
+ _16bit_float_convert,
+ _24bit_float_convert
+};
+
+ReadResult WAVReader::read(void *buffer, size_t* samples) {
+ auto fbuffer = (float*) buffer;
+
+ const auto max_sample_count = this->total_samples_ - this->current_sample_offset_;
+ const auto max_samples = std::min(*samples, max_sample_count);
+ if(max_samples == 0) {
+ if(max_sample_count == 0) return ReadResult::READ_RESULT_EOF;
+
+ return ReadResult::READ_RESULT_SUCCESS;
+ }
+
+ constexpr size_t sbuffer_size{1536}; /* must be dividable by 24, 165 and 8 bit! As well by two channels so 6, 4 and 2 byte to avoid to mess up one frame */
+ uint8_t sbuffer[sbuffer_size];
+
+ size_t samples_read{0};
+ auto fconverter = pcm_to_float_converters[(this->bytes_per_sample - 1) & 0x3U];
+ while(samples_read < max_samples) {
+ const auto block_byte_length{std::min(sbuffer_size, (max_samples - samples_read) * this->bytes_per_sample * this->channels_)};
+ if(!this->is_.read((char*) sbuffer, block_byte_length))
+ return ReadResult::READ_RESULT_UNRECOVERABLE_ERROR;
+
+ uint8_t* sbufferptr = sbuffer;
+ uint8_t* sbuferendptr = sbuffer + block_byte_length;
+ while(sbufferptr != sbuferendptr) {
+ *fbuffer++ = fconverter(sbufferptr);
+ sbufferptr += this->bytes_per_sample;
+ }
+
+ samples_read += block_byte_length / (this->bytes_per_sample * this->channels_);
+ }
+
+ *samples = samples_read;
+ this->current_sample_offset_ += samples_read;
+ return ReadResult::READ_RESULT_SUCCESS;
}
\ No newline at end of file
diff --git a/native/serverconnection/src/audio/file/wav.h b/native/serverconnection/src/audio/file/wav.h
index 88543d6..2fbdac7 100644
--- a/native/serverconnection/src/audio/file/wav.h
+++ b/native/serverconnection/src/audio/file/wav.h
@@ -1,51 +1,51 @@
-#pragma once
-
-#include
-#include
-
-namespace tc::audio::file {
- enum FileOpenResult {
- OPEN_RESULT_SUCCESS,
- OPEN_RESULT_INVALID_FORMAT,
- OPEN_RESULT_FORMAT_UNSUPPORTED,
- OPEN_RESULT_ERROR
- };
-
- enum ReadResult {
- READ_RESULT_SUCCESS,
- READ_RESULT_EOF,
- READ_RESULT_UNRECOVERABLE_ERROR
- };
-
- class WAVReader {
- public:
- explicit WAVReader(std::string /* path */);
- ~WAVReader();
-
- [[nodiscard]] inline const std::string& file_path() const { return this->file_path_; }
-
- [[nodiscard]] FileOpenResult open_file(std::string& /* error */);
- void close_file();
-
- [[nodiscard]] inline size_t channels() const { return this->channels_; }
- [[nodiscard]] inline size_t sample_rate() const { return this->sample_rate_; }
- [[nodiscard]] inline size_t total_samples() const { return this->total_samples_; }
- [[nodiscard]] inline size_t current_sample_offset() const { return this->current_sample_offset_; }
-
- /**
- * Audio data in interleaved floats reachting from [-1;1].
- * Must contains at least channels * sizeof(float) * sample_count bytes
- */
- [[nodiscard]] ReadResult read(void* /* target buffer */, size_t* /* sample count */);
- private:
- const std::string file_path_;
-
- std::ifstream is_{};
-
- size_t channels_{0};
- size_t sample_rate_{0};
- size_t total_samples_{0};
- size_t current_sample_offset_{0};
- uint8_t bytes_per_sample{0}; /* 1, 2 or 3 (8, 16, 24 bit) */
- };
+#pragma once
+
+#include
+#include
+
+namespace tc::audio::file {
+ enum FileOpenResult {
+ OPEN_RESULT_SUCCESS,
+ OPEN_RESULT_INVALID_FORMAT,
+ OPEN_RESULT_FORMAT_UNSUPPORTED,
+ OPEN_RESULT_ERROR
+ };
+
+ enum ReadResult {
+ READ_RESULT_SUCCESS,
+ READ_RESULT_EOF,
+ READ_RESULT_UNRECOVERABLE_ERROR
+ };
+
+ class WAVReader {
+ public:
+ explicit WAVReader(std::string /* path */);
+ ~WAVReader();
+
+ [[nodiscard]] inline const std::string& file_path() const { return this->file_path_; }
+
+ [[nodiscard]] FileOpenResult open_file(std::string& /* error */);
+ void close_file();
+
+ [[nodiscard]] inline size_t channels() const { return this->channels_; }
+ [[nodiscard]] inline size_t sample_rate() const { return this->sample_rate_; }
+ [[nodiscard]] inline size_t total_samples() const { return this->total_samples_; }
+ [[nodiscard]] inline size_t current_sample_offset() const { return this->current_sample_offset_; }
+
+ /**
+ * Audio data in interleaved floats reachting from [-1;1].
+ * Must contains at least channels * sizeof(float) * sample_count bytes
+ */
+ [[nodiscard]] ReadResult read(void* /* target buffer */, size_t* /* sample count */);
+ private:
+ const std::string file_path_;
+
+ std::ifstream is_{};
+
+ size_t channels_{0};
+ size_t sample_rate_{0};
+ size_t total_samples_{0};
+ size_t current_sample_offset_{0};
+ uint8_t bytes_per_sample{0}; /* 1, 2 or 3 (8, 16, 24 bit) */
+ };
}
\ No newline at end of file
diff --git a/native/serverconnection/src/audio/sounds/SoundPlayer.cpp b/native/serverconnection/src/audio/sounds/SoundPlayer.cpp
index b8f13ae..feb5a6b 100644
--- a/native/serverconnection/src/audio/sounds/SoundPlayer.cpp
+++ b/native/serverconnection/src/audio/sounds/SoundPlayer.cpp
@@ -1,309 +1,309 @@
-//
-// Created by WolverinDEV on 18/03/2020.
-//
-
-#include
-#include
-#include
-#include "./SoundPlayer.h"
-#include "../AudioOutput.h"
-#include "../file/wav.h"
-#include "../../EventLoop.h"
-#include "../../logger.h"
-#include "../AudioEventLoop.h"
-#include "../AudioResampler.h"
-#include "../AudioMerger.h"
-
-#ifdef max
- #undef max
-#endif
-
-using namespace tc::audio;
-
-extern tc::audio::AudioOutput* global_audio_output;
-namespace tc::audio::sounds {
- class FilePlayer : public event::EventEntry, public std::enable_shared_from_this {
- public:
- explicit FilePlayer(PlaybackSettings settings) : settings_{std::move(settings)} {
- log_trace(category::memory, tr("Allocated FilePlayer instance at {}"), (void*) this);
- }
-
- ~FilePlayer() {
- this->finalize(true);
- log_trace(category::memory, tr("Deleted FilePlayer instance at {}"), (void*) this);
- }
-
- [[nodiscard]] inline const PlaybackSettings& settings() const { return this->settings_; }
-
- [[nodiscard]] inline bool is_finished() const { return this->state_ == PLAYER_STATE_UNSET; }
-
- /* should not be blocking! */
- bool play() {
- if(this->state_ != PLAYER_STATE_UNSET) return false;
- this->state_ = PLAYER_STATE_INITIALIZE;
-
- audio::decode_event_loop->schedule(this->shared_from_this());
- return true;
- }
-
- /* should not be blocking! */
- void cancel() {
- this->state_ = PLAYER_STATE_CANCELED;
- audio::decode_event_loop->schedule(this->shared_from_this());
- }
- private:
- constexpr static auto kBufferChunkTimespan{0.2};
-
- const PlaybackSettings settings_;
- std::unique_ptr file_handle{nullptr};
- std::unique_ptr resampler{nullptr};
- std::shared_ptr output_source;
-
- void* cache_buffer{nullptr};
-
- enum {
- PLAYER_STATE_INITIALIZE,
- PLAYER_STATE_PLAYING,
- PLAYER_STATE_AWAIT_FINISH,
- PLAYER_STATE_FINISHED,
- PLAYER_STATE_CANCELED,
- PLAYER_STATE_UNSET
- } state_{PLAYER_STATE_UNSET};
-
- void finalize(bool is_destructor_call) {
- if(this->output_source && global_audio_output)
- global_audio_output->delete_source(this->output_source);
- if(auto buffer{std::exchange(this->cache_buffer, nullptr)}; buffer)
- ::free(buffer);
- if(!is_destructor_call)
- audio::decode_event_loop->cancel(this->shared_from_this());
- this->state_ = PLAYER_STATE_UNSET;
- }
-
- void event_execute(const std::chrono::system_clock::time_point &) override {
- if(this->state_ == PLAYER_STATE_INITIALIZE) {
- this->file_handle = std::make_unique(this->settings_.file);
- std::string error{};
- if(auto err{this->file_handle->open_file(error)}; err != file::OPEN_RESULT_SUCCESS) {
- if(auto callback{this->settings_.callback}; callback)
- callback(PlaybackResult::FILE_OPEN_ERROR, error);
- this->finalize(false);
- return;
- }
-
- if(!global_audio_output) {
- if(auto callback{this->settings_.callback}; callback)
- callback(PlaybackResult::SOUND_NOT_INITIALIZED, "");
- this->finalize(false);
- return;
- }
-
- this->initialize_playback();
-
- auto max_samples = (size_t)
- std::max(this->output_source->sample_rate, this->file_handle->sample_rate()) * kBufferChunkTimespan * 8 *
- std::max(this->file_handle->channels(), this->output_source->channel_count);
- this->cache_buffer = ::malloc((size_t) (max_samples * sizeof(float)));
- if(!this->cache_buffer) {
- if(auto callback{this->settings_.callback}; callback)
- callback(PlaybackResult::PLAYBACK_ERROR, "failed to allocate cached buffer");
-
- this->finalize(false);
- return;
- }
- this->state_ = PLAYER_STATE_PLAYING;
- }
- if(this->state_ == PLAYER_STATE_PLAYING) {
- if(!this->could_enqueue_next_buffer()) return;
-
- auto samples_to_read = (size_t) (this->file_handle->sample_rate() * kBufferChunkTimespan);
- auto errc = this->file_handle->read(this->cache_buffer, &samples_to_read);
- switch (errc) {
- case file::READ_RESULT_SUCCESS:
- break;
-
- case file::READ_RESULT_EOF:
- this->state_ = PLAYER_STATE_AWAIT_FINISH;
- return;
-
-
- case file::READ_RESULT_UNRECOVERABLE_ERROR:
- if(auto callback{this->settings_.callback}; callback)
- callback(PlaybackResult::PLAYBACK_ERROR, "read resulted in an unrecoverable error");
-
- this->finalize(false);
- return;
- }
-
- if(!merge::merge_channels_interleaved(this->cache_buffer, this->output_source->channel_count, this->cache_buffer, this->file_handle->channels(), samples_to_read)) {
- log_warn(category::audio, tr("failed to merge channels for replaying a sound"));
- return;
- }
-
- auto resampled_samples = this->resampler->process(this->cache_buffer, this->cache_buffer, samples_to_read);
- if(resampled_samples <= 0) {
- log_warn(category::audio, tr("failed to resample file audio buffer ({})"), resampled_samples);
- return;
- }
-
- this->output_source->enqueue_samples(this->cache_buffer, resampled_samples);
- if(this->could_enqueue_next_buffer())
- audio::decode_event_loop->schedule(this->shared_from_this());
- } else if(this->state_ == PLAYER_STATE_FINISHED || this->state_ == PLAYER_STATE_CANCELED) {
- this->finalize(false);
- if(auto callback{this->settings_.callback}; callback)
- callback(this->state_ == PLAYER_STATE_CANCELED ? PlaybackResult::CANCELED : PlaybackResult::SUCCEEDED, "");
- this->state_ = PLAYER_STATE_UNSET;
- return;
- }
- auto filled_samples = this->output_source->current_latency();
- }
-
- void initialize_playback() {
- assert(this->file_handle);
- assert(global_audio_output);
-
- const auto max_buffer = (size_t) ceil(global_audio_output->sample_rate() * kBufferChunkTimespan * 3);
- this->output_source = global_audio_output->create_source(max_buffer);
- this->output_source->overflow_strategy = audio::overflow_strategy::ignore;
- this->output_source->max_buffered_samples = max_buffer;
- this->output_source->min_buffered_samples = (size_t) floor(this->output_source->sample_rate * 0.04);
-
- auto weak_this = this->weak_from_this();
- this->output_source->on_underflow = [weak_this](size_t sample_count){
- auto self = weak_this.lock();
- if(!self) return false;
-
- if(self->state_ == PLAYER_STATE_PLAYING)
- log_warn(category::audio, tr("Having an audio underflow while playing a sound."));
- else if(self->state_ == PLAYER_STATE_AWAIT_FINISH)
- self->state_ = PLAYER_STATE_FINISHED;
- audio::decode_event_loop->schedule(self);
- return false;
- };
- this->output_source->on_read = [weak_this] {
- auto self = weak_this.lock();
- if(!self) return;
-
- if(self->could_enqueue_next_buffer() && self->state_ == PLAYER_STATE_PLAYING)
- audio::decode_event_loop->schedule(self);
- };
-
- this->output_source->on_overflow = [weak_this](size_t count) {
- log_warn(category::audio, tr("Having an audio overflow while playing a sound."));
- };
-
- this->resampler = std::make_unique(this->file_handle->sample_rate(), this->output_source->sample_rate, this->output_source->channel_count);
- }
-
-
- [[nodiscard]] inline size_t cache_buffer_sample_size() const {
- return (size_t) (this->output_source->sample_rate * kBufferChunkTimespan);
- }
-
- [[nodiscard]] inline bool could_enqueue_next_buffer() const {
- if(!this->output_source) return false;
-
- const auto current_size = this->output_source->current_latency();
- const auto max_size = this->output_source->max_buffered_samples;
- if(current_size > max_size) return false;
-
- const auto size_left = max_size - current_size;
- return size_left >= this->cache_buffer_sample_size();
- }
- };
-
- std::mutex file_player_mutex{};
- std::deque> file_players{};
-
- sound_playback_id playback_sound(const PlaybackSettings& settings) {
- if(!audio::initialized()) {
- settings.callback(PlaybackResult::SOUND_NOT_INITIALIZED, "");
- return 0;
- }
-
- std::unique_lock fplock{file_player_mutex};
- file_players.erase(std::remove_if(file_players.begin(), file_players.end(), [](const auto& player) {
- return player->is_finished();
- }), file_players.end());
-
- auto player = std::make_shared(settings);
- file_players.push_back(player);
- if(!player->play()) {
- if(auto callback{settings.callback}; callback)
- callback(PlaybackResult::PLAYBACK_ERROR, "failed to start playback.");
- return 0;
- }
- fplock.unlock();
- return (sound_playback_id) &*player;
- }
-
- void cancel_playback(const sound_playback_id& id) {
- std::unique_lock fplock{file_player_mutex};
- auto player_it = std::find_if(file_players.begin(), file_players.end(), [&](const auto& player) { return (sound_playback_id) &*player == id; });
- if(player_it == file_players.end()) return;
-
- auto player = *player_it;
- file_players.erase(player_it);
- fplock.unlock();
-
- player->cancel();
- }
-}
-
-NAN_METHOD(tc::audio::sounds::playback_sound_js) {
- if(info.Length() != 1 || !info[0]->IsObject()) {
- Nan::ThrowError("invalid arguments");
- return;
- }
-
- auto data = info[0].As();
- auto file = Nan::GetLocal(data, "file");
- auto volume = Nan::GetLocal(data, "volume", Nan::New(1.f));
- v8::Local callback = Nan::GetLocal(data, "callback");
- if(file.IsEmpty() || !file->IsString()) {
- Nan::ThrowError("missing file path");
- return;
- }
- if(volume.IsEmpty() || !volume->IsNumber()) {
- Nan::ThrowError("invalid volume");
- return;
- }
-
- PlaybackSettings settings{};
- settings.file = *Nan::Utf8String(file);
- settings.volume = volume->Value();
- if(!callback.IsEmpty()) {
- if(!callback->IsFunction()) {
- Nan::ThrowError("invalid callback function");
- return;
- }
-
- Nan::Global cb{callback};
- auto async_callback = Nan::async_callback([cb = std::move(cb)](PlaybackResult result, std::string error) mutable {
- Nan::HandleScope scope{};
- auto callback = cb.Get(Nan::GetCurrentContext()->GetIsolate()).As();
- cb.Reset();
-
- v8::Local arguments[2];
- arguments[0] = Nan::New((int) result);
- arguments[1] = Nan::LocalStringUTF8(error);
- (void) callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 2, arguments);
- }).option_destroyed_execute(true);
-
- settings.callback = [async_callback](PlaybackResult result, const std::string& error) mutable {
- async_callback.call_cpy(result, error);
- };
- }
-
- info.GetReturnValue().Set((uint32_t) playback_sound(settings));
-}
-
-NAN_METHOD(tc::audio::sounds::cancel_playback_js) {
- if(info.Length() != 1 || !info[0]->IsNumber()) {
- Nan::ThrowError("invalid arguments");
- return;
- }
-
- cancel_playback(info[0].As()->Value());
+//
+// Created by WolverinDEV on 18/03/2020.
+//
+
+#include
+#include
+#include
+#include "./SoundPlayer.h"
+#include "../AudioOutput.h"
+#include "../file/wav.h"
+#include "../../EventLoop.h"
+#include "../../logger.h"
+#include "../AudioEventLoop.h"
+#include "../AudioResampler.h"
+#include "../AudioMerger.h"
+
+#ifdef max
+ #undef max
+#endif
+
+using namespace tc::audio;
+
+extern tc::audio::AudioOutput* global_audio_output;
+namespace tc::audio::sounds {
+ class FilePlayer : public event::EventEntry, public std::enable_shared_from_this {
+ public:
+ explicit FilePlayer(PlaybackSettings settings) : settings_{std::move(settings)} {
+ log_trace(category::memory, tr("Allocated FilePlayer instance at {}"), (void*) this);
+ }
+
+ ~FilePlayer() {
+ this->finalize(true);
+ log_trace(category::memory, tr("Deleted FilePlayer instance at {}"), (void*) this);
+ }
+
+ [[nodiscard]] inline const PlaybackSettings& settings() const { return this->settings_; }
+
+ [[nodiscard]] inline bool is_finished() const { return this->state_ == PLAYER_STATE_UNSET; }
+
+ /* should not be blocking! */
+ bool play() {
+ if(this->state_ != PLAYER_STATE_UNSET) return false;
+ this->state_ = PLAYER_STATE_INITIALIZE;
+
+ audio::decode_event_loop->schedule(this->shared_from_this());
+ return true;
+ }
+
+ /* should not be blocking! */
+ void cancel() {
+ this->state_ = PLAYER_STATE_CANCELED;
+ audio::decode_event_loop->schedule(this->shared_from_this());
+ }
+ private:
+ constexpr static auto kBufferChunkTimespan{0.2};
+
+ const PlaybackSettings settings_;
+ std::unique_ptr file_handle{nullptr};
+ std::unique_ptr resampler{nullptr};
+ std::shared_ptr output_source;
+
+ void* cache_buffer{nullptr};
+
+ enum {
+ PLAYER_STATE_INITIALIZE,
+ PLAYER_STATE_PLAYING,
+ PLAYER_STATE_AWAIT_FINISH,
+ PLAYER_STATE_FINISHED,
+ PLAYER_STATE_CANCELED,
+ PLAYER_STATE_UNSET
+ } state_{PLAYER_STATE_UNSET};
+
+ void finalize(bool is_destructor_call) {
+ if(this->output_source && global_audio_output)
+ global_audio_output->delete_source(this->output_source);
+ if(auto buffer{std::exchange(this->cache_buffer, nullptr)}; buffer)
+ ::free(buffer);
+ if(!is_destructor_call)
+ audio::decode_event_loop->cancel(this->shared_from_this());
+ this->state_ = PLAYER_STATE_UNSET;
+ }
+
+ void event_execute(const std::chrono::system_clock::time_point &) override {
+ if(this->state_ == PLAYER_STATE_INITIALIZE) {
+ this->file_handle = std::make_unique(this->settings_.file);
+ std::string error{};
+ if(auto err{this->file_handle->open_file(error)}; err != file::OPEN_RESULT_SUCCESS) {
+ if(auto callback{this->settings_.callback}; callback)
+ callback(PlaybackResult::FILE_OPEN_ERROR, error);
+ this->finalize(false);
+ return;
+ }
+
+ if(!global_audio_output) {
+ if(auto callback{this->settings_.callback}; callback)
+ callback(PlaybackResult::SOUND_NOT_INITIALIZED, "");
+ this->finalize(false);
+ return;
+ }
+
+ this->initialize_playback();
+
+ auto max_samples = (size_t)
+ std::max(this->output_source->sample_rate, this->file_handle->sample_rate()) * kBufferChunkTimespan * 8 *
+ std::max(this->file_handle->channels(), this->output_source->channel_count);
+ this->cache_buffer = ::malloc((size_t) (max_samples * sizeof(float)));
+ if(!this->cache_buffer) {
+ if(auto callback{this->settings_.callback}; callback)
+ callback(PlaybackResult::PLAYBACK_ERROR, "failed to allocate cached buffer");
+
+ this->finalize(false);
+ return;
+ }
+ this->state_ = PLAYER_STATE_PLAYING;
+ }
+ if(this->state_ == PLAYER_STATE_PLAYING) {
+ if(!this->could_enqueue_next_buffer()) return;
+
+ auto samples_to_read = (size_t) (this->file_handle->sample_rate() * kBufferChunkTimespan);
+ auto errc = this->file_handle->read(this->cache_buffer, &samples_to_read);
+ switch (errc) {
+ case file::READ_RESULT_SUCCESS:
+ break;
+
+ case file::READ_RESULT_EOF:
+ this->state_ = PLAYER_STATE_AWAIT_FINISH;
+ return;
+
+
+ case file::READ_RESULT_UNRECOVERABLE_ERROR:
+ if(auto callback{this->settings_.callback}; callback)
+ callback(PlaybackResult::PLAYBACK_ERROR, "read resulted in an unrecoverable error");
+
+ this->finalize(false);
+ return;
+ }
+
+ if(!merge::merge_channels_interleaved(this->cache_buffer, this->output_source->channel_count, this->cache_buffer, this->file_handle->channels(), samples_to_read)) {
+ log_warn(category::audio, tr("failed to merge channels for replaying a sound"));
+ return;
+ }
+
+ auto resampled_samples = this->resampler->process(this->cache_buffer, this->cache_buffer, samples_to_read);
+ if(resampled_samples <= 0) {
+ log_warn(category::audio, tr("failed to resample file audio buffer ({})"), resampled_samples);
+ return;
+ }
+
+ this->output_source->enqueue_samples(this->cache_buffer, resampled_samples);
+ if(this->could_enqueue_next_buffer())
+ audio::decode_event_loop->schedule(this->shared_from_this());
+ } else if(this->state_ == PLAYER_STATE_FINISHED || this->state_ == PLAYER_STATE_CANCELED) {
+ this->finalize(false);
+ if(auto callback{this->settings_.callback}; callback)
+ callback(this->state_ == PLAYER_STATE_CANCELED ? PlaybackResult::CANCELED : PlaybackResult::SUCCEEDED, "");
+ this->state_ = PLAYER_STATE_UNSET;
+ return;
+ }
+ auto filled_samples = this->output_source->current_latency();
+ }
+
+ void initialize_playback() {
+ assert(this->file_handle);
+ assert(global_audio_output);
+
+ const auto max_buffer = (size_t) ceil(global_audio_output->sample_rate() * kBufferChunkTimespan * 3);
+ this->output_source = global_audio_output->create_source(max_buffer);
+ this->output_source->overflow_strategy = audio::overflow_strategy::ignore;
+ this->output_source->max_buffered_samples = max_buffer;
+ this->output_source->min_buffered_samples = (size_t) floor(this->output_source->sample_rate * 0.04);
+
+ auto weak_this = this->weak_from_this();
+ this->output_source->on_underflow = [weak_this](size_t sample_count){
+ auto self = weak_this.lock();
+ if(!self) return false;
+
+ if(self->state_ == PLAYER_STATE_PLAYING)
+ log_warn(category::audio, tr("Having an audio underflow while playing a sound."));
+ else if(self->state_ == PLAYER_STATE_AWAIT_FINISH)
+ self->state_ = PLAYER_STATE_FINISHED;
+ audio::decode_event_loop->schedule(self);
+ return false;
+ };
+ this->output_source->on_read = [weak_this] {
+ auto self = weak_this.lock();
+ if(!self) return;
+
+ if(self->could_enqueue_next_buffer() && self->state_ == PLAYER_STATE_PLAYING)
+ audio::decode_event_loop->schedule(self);
+ };
+
+ this->output_source->on_overflow = [weak_this](size_t count) {
+ log_warn(category::audio, tr("Having an audio overflow while playing a sound."));
+ };
+
+ this->resampler = std::make_unique(this->file_handle->sample_rate(), this->output_source->sample_rate, this->output_source->channel_count);
+ }
+
+
+ [[nodiscard]] inline size_t cache_buffer_sample_size() const {
+ return (size_t) (this->output_source->sample_rate * kBufferChunkTimespan);
+ }
+
+ [[nodiscard]] inline bool could_enqueue_next_buffer() const {
+ if(!this->output_source) return false;
+
+ const auto current_size = this->output_source->current_latency();
+ const auto max_size = this->output_source->max_buffered_samples;
+ if(current_size > max_size) return false;
+
+ const auto size_left = max_size - current_size;
+ return size_left >= this->cache_buffer_sample_size();
+ }
+ };
+
+ std::mutex file_player_mutex{};
+ std::deque> file_players{};
+
+ sound_playback_id playback_sound(const PlaybackSettings& settings) {
+ if(!audio::initialized()) {
+ settings.callback(PlaybackResult::SOUND_NOT_INITIALIZED, "");
+ return 0;
+ }
+
+ std::unique_lock fplock{file_player_mutex};
+ file_players.erase(std::remove_if(file_players.begin(), file_players.end(), [](const auto& player) {
+ return player->is_finished();
+ }), file_players.end());
+
+ auto player = std::make_shared(settings);
+ file_players.push_back(player);
+ if(!player->play()) {
+ if(auto callback{settings.callback}; callback)
+ callback(PlaybackResult::PLAYBACK_ERROR, "failed to start playback.");
+ return 0;
+ }
+ fplock.unlock();
+ return (sound_playback_id) &*player;
+ }
+
+ void cancel_playback(const sound_playback_id& id) {
+ std::unique_lock fplock{file_player_mutex};
+ auto player_it = std::find_if(file_players.begin(), file_players.end(), [&](const auto& player) { return (sound_playback_id) &*player == id; });
+ if(player_it == file_players.end()) return;
+
+ auto player = *player_it;
+ file_players.erase(player_it);
+ fplock.unlock();
+
+ player->cancel();
+ }
+}
+
+NAN_METHOD(tc::audio::sounds::playback_sound_js) {
+ if(info.Length() != 1 || !info[0]->IsObject()) {
+ Nan::ThrowError("invalid arguments");
+ return;
+ }
+
+ auto data = info[0].As();
+ auto file = Nan::GetLocal(data, "file");
+ auto volume = Nan::GetLocal(data, "volume", Nan::New(1.f));
+ v8::Local callback = Nan::GetLocal(data, "callback");
+ if(file.IsEmpty() || !file->IsString()) {
+ Nan::ThrowError("missing file path");
+ return;
+ }
+ if(volume.IsEmpty() || !volume->IsNumber()) {
+ Nan::ThrowError("invalid volume");
+ return;
+ }
+
+ PlaybackSettings settings{};
+ settings.file = *Nan::Utf8String(file);
+ settings.volume = volume->Value();
+ if(!callback.IsEmpty()) {
+ if(!callback->IsFunction()) {
+ Nan::ThrowError("invalid callback function");
+ return;
+ }
+
+ Nan::Global cb{callback};
+ auto async_callback = Nan::async_callback([cb = std::move(cb)](PlaybackResult result, std::string error) mutable {
+ Nan::HandleScope scope{};
+ auto callback = cb.Get(Nan::GetCurrentContext()->GetIsolate()).As();
+ cb.Reset();
+
+ v8::Local arguments[2];
+ arguments[0] = Nan::New((int) result);
+ arguments[1] = Nan::LocalStringUTF8(error);
+ (void) callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 2, arguments);
+ }).option_destroyed_execute(true);
+
+ settings.callback = [async_callback](PlaybackResult result, const std::string& error) mutable {
+ async_callback.call_cpy(result, error);
+ };
+ }
+
+ info.GetReturnValue().Set((uint32_t) playback_sound(settings));
+}
+
+NAN_METHOD(tc::audio::sounds::cancel_playback_js) {
+ if(info.Length() != 1 || !info[0]->IsNumber()) {
+ Nan::ThrowError("invalid arguments");
+ return;
+ }
+
+ cancel_playback(info[0].As()->Value());
}
\ No newline at end of file
diff --git a/native/serverconnection/src/audio/sounds/SoundPlayer.h b/native/serverconnection/src/audio/sounds/SoundPlayer.h
index b01dbe6..6042bf0 100644
--- a/native/serverconnection/src/audio/sounds/SoundPlayer.h
+++ b/native/serverconnection/src/audio/sounds/SoundPlayer.h
@@ -1,31 +1,31 @@
-#pragma once
-
-#include
-#include
-#include
-
-namespace tc::audio::sounds {
- typedef uintptr_t sound_playback_id;
-
- enum struct PlaybackResult {
- SUCCEEDED,
- CANCELED,
- SOUND_NOT_INITIALIZED,
- FILE_OPEN_ERROR,
- PLAYBACK_ERROR /* has the extra error set */
- };
-
- typedef std::function callback_status_t;
- struct PlaybackSettings {
- std::string file{};
- double volume{1.f};
- /* ATTENTION: This callback may be called within the audio loop! */
- callback_status_t callback{};
- };
-
- extern sound_playback_id playback_sound(const PlaybackSettings& /* settings */);
- extern void cancel_playback(const sound_playback_id&);
-
- extern NAN_METHOD(playback_sound_js);
- extern NAN_METHOD(cancel_playback_js);
+#pragma once
+
+#include
+#include
+#include
+
+namespace tc::audio::sounds {
+ typedef uintptr_t sound_playback_id;
+
+ enum struct PlaybackResult {
+ SUCCEEDED,
+ CANCELED,
+ SOUND_NOT_INITIALIZED,
+ FILE_OPEN_ERROR,
+ PLAYBACK_ERROR /* has the extra error set */
+ };
+
+ typedef std::function callback_status_t;
+ struct PlaybackSettings {
+ std::string file{};
+ double volume{1.f};
+ /* ATTENTION: This callback may be called within the audio loop! */
+ callback_status_t callback{};
+ };
+
+ extern sound_playback_id playback_sound(const PlaybackSettings& /* settings */);
+ extern void cancel_playback(const sound_playback_id&);
+
+ extern NAN_METHOD(playback_sound_js);
+ extern NAN_METHOD(cancel_playback_js);
}
\ No newline at end of file