#include #include "AudioOutputStream.h" #include "../AudioOutput.h" #include "../AudioResampler.h" using namespace std; using namespace tc; using namespace tc::audio; NAN_MODULE_INIT(AudioOutputStreamWrapper::Init) { auto klass = Nan::New(AudioOutputStreamWrapper::NewInstance); klass->SetClassName(Nan::New("AudioOutputStream").ToLocalChecked()); klass->InstanceTemplate()->SetInternalFieldCount(1); Nan::SetPrototypeMethod(klass, "write_data", AudioOutputStreamWrapper::_write_data); Nan::SetPrototypeMethod(klass, "get_buffer_latency", AudioOutputStreamWrapper::_get_buffer_latency); Nan::SetPrototypeMethod(klass, "set_buffer_latency", AudioOutputStreamWrapper::_set_buffer_latency); Nan::SetPrototypeMethod(klass, "get_buffer_max_latency", AudioOutputStreamWrapper::_get_buffer_max_latency); Nan::SetPrototypeMethod(klass, "set_buffer_max_latency", AudioOutputStreamWrapper::_set_buffer_max_latency); Nan::SetPrototypeMethod(klass, "write_data", AudioOutputStreamWrapper::_write_data); Nan::SetPrototypeMethod(klass, "write_data_rated", AudioOutputStreamWrapper::_write_data_rated); Nan::SetPrototypeMethod(klass, "deleted", AudioOutputStreamWrapper::_deleted); Nan::SetPrototypeMethod(klass, "delete", AudioOutputStreamWrapper::_delete); Nan::SetPrototypeMethod(klass, "clear", AudioOutputStreamWrapper::_clear); constructor().Reset(Nan::GetFunction(klass).ToLocalChecked()); } NAN_METHOD(AudioOutputStreamWrapper::NewInstance) { if(!info.IsConstructCall()) Nan::ThrowError("invalid invoke!"); } AudioOutputStreamWrapper::AudioOutputStreamWrapper(const std::shared_ptr &stream, bool owns) { this->_handle = stream; if(owns) { this->_own_handle = stream; } } AudioOutputStreamWrapper::~AudioOutputStreamWrapper() { this->drop_stream(); } void AudioOutputStreamWrapper::drop_stream() { if(this->_own_handle) { auto handle = this->_own_handle->handle; if(handle) { handle->delete_source(this->_own_handle); } this->_own_handle->on_underflow = nullptr; this->_own_handle->on_overflow = nullptr; } this->_handle.reset(); this->_own_handle = nullptr; } void AudioOutputStreamWrapper::do_wrap(const v8::Local &obj) { this->Wrap(obj); auto handle = this->_handle.lock(); if(!handle) { Nan::ThrowError("weak handle"); return; } Nan::ForceSet(this->handle(), Nan::New("sample_rate").ToLocalChecked(), Nan::New(handle->sample_rate), v8::ReadOnly); Nan::ForceSet(this->handle(), Nan::New("channels").ToLocalChecked(), Nan::New(handle->channel_count), v8::ReadOnly); if(this->_own_handle) { this->call_underflow = Nan::async_callback([&]{ Nan::HandleScope scope; auto handle = this->handle(); auto callback = Nan::Get(handle, Nan::New("callback_underflow").ToLocalChecked()).ToLocalChecked(); if(callback->IsFunction()) callback.As()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr); }); this->call_overflow = Nan::async_callback([&]{ Nan::HandleScope scope; auto handle = this->handle(); auto callback = Nan::Get(handle, Nan::New("callback_overflow").ToLocalChecked()).ToLocalChecked(); if(callback->IsFunction()) callback.As()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr); }); this->_own_handle->on_overflow = [&](size_t){ this->call_overflow(); }; this->_own_handle->on_underflow = [&]{ this->call_underflow(); }; } } NAN_METHOD(AudioOutputStreamWrapper::_clear) { auto client = ObjectWrap::Unwrap(info.Holder()); auto handle = client->_own_handle; if(!handle) { Nan::ThrowError("invalid handle"); return; } handle->clear(); } NAN_METHOD(AudioOutputStreamWrapper::_deleted) { auto client = ObjectWrap::Unwrap(info.Holder()); info.GetReturnValue().Set(!client->_own_handle || !client->_own_handle->handle); } NAN_METHOD(AudioOutputStreamWrapper::_delete) { auto client = ObjectWrap::Unwrap(info.Holder()); client->drop_stream(); } ssize_t AudioOutputStreamWrapper::write_data(const std::shared_ptr& handle, void *source, size_t samples, bool interleaved) { ssize_t result = 0; if(interleaved) { result = handle->enqueue_samples(source, samples); } else { auto buffer = SampleBuffer::allocate(handle->channel_count, samples); auto src_buffer = (float*) source; auto target_buffer = (float*) buffer->sample_data; while (samples-- > 0) { *target_buffer = *src_buffer; *(target_buffer + 1) = *(src_buffer + buffer->sample_size); target_buffer += 2; src_buffer++; } result = handle->enqueue_samples(buffer); } return result; } NAN_METHOD(AudioOutputStreamWrapper::_write_data) { auto client = ObjectWrap::Unwrap(info.Holder()); auto handle = client->_own_handle; if(!handle) { Nan::ThrowError("invalid handle"); return; } if(info.Length() != 2 || !info[0]->IsArrayBuffer() || !info[1]->IsBoolean()) { Nan::ThrowError("Invalid arguments"); return; } auto interleaved = info[1]->BooleanValue(info.GetIsolate()); auto js_buffer = info[0].As()->GetContents(); if(js_buffer.ByteLength() % (handle->channel_count * 4) != 0) { Nan::ThrowError("input buffer invalid size"); return; } auto samples = js_buffer.ByteLength() / handle->channel_count / 4; info.GetReturnValue().Set((int32_t) write_data(handle, js_buffer.Data(), samples, interleaved)); } NAN_METHOD(AudioOutputStreamWrapper::_write_data_rated) { auto client = ObjectWrap::Unwrap(info.Holder()); auto handle = client->_own_handle; if(!handle) { Nan::ThrowError("invalid handle"); return; } if(info.Length() != 3 || !info[0]->IsArrayBuffer() || !info[1]->IsBoolean() || !info[2]->IsNumber()) { Nan::ThrowError("Invalid arguments"); return; } auto sample_rate = info[2]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0); auto interleaved = info[1]->BooleanValue(info.GetIsolate()); auto js_buffer = info[0].As()->GetContents(); auto samples = js_buffer.ByteLength() / handle->channel_count / 4; if(sample_rate == handle->sample_rate) { info.GetReturnValue().Set((int32_t) write_data(handle, js_buffer.Data(), samples, interleaved)); } else { if(!client->_resampler || client->_resampler->input_rate() != sample_rate) client->_resampler = make_unique(sample_rate, handle->sample_rate, handle->channel_count); if(!client->_resampler || !client->_resampler->valid()) { Nan::ThrowError("Resampling failed (invalid resampler)"); return; } ssize_t target_samples = client->_resampler->estimated_output_size(samples); auto buffer = SampleBuffer::allocate(handle->channel_count, max((size_t) samples, (size_t) target_samples)); auto source_buffer = js_buffer.Data(); if(!interleaved) { auto src_buffer = (float*) js_buffer.Data(); auto target_buffer = (float*) buffer->sample_data; auto samples_count = samples; while (samples_count-- > 0) { *target_buffer = *src_buffer; *(target_buffer + 1) = *(src_buffer + samples); target_buffer += 2; src_buffer++; } source_buffer = buffer->sample_data; } target_samples = client->_resampler->process(buffer->sample_data, source_buffer, samples); if(target_samples < 0) { Nan::ThrowError("Resampling failed"); return; } buffer->sample_index = 0; buffer->sample_size = target_samples; info.GetReturnValue().Set((int32_t) handle->enqueue_samples(buffer)); } } NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_latency) { auto client = ObjectWrap::Unwrap(info.Holder()); auto handle = client->_handle.lock(); if(!handle) { Nan::ThrowError("weak handle"); return; } info.GetReturnValue().Set((float) handle->min_buffer / (float) handle->sample_rate); } NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_latency) { auto client = ObjectWrap::Unwrap(info.Holder()); auto handle = client->_handle.lock(); if(!handle) { Nan::ThrowError("weak handle"); return; } if(info.Length() != 1 || !info[0]->IsNumber()) { Nan::ThrowError("Invalid arguments"); return; } handle->min_buffer = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0)); } NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_max_latency) { auto client = ObjectWrap::Unwrap(info.Holder()); auto handle = client->_handle.lock(); if(!handle) { Nan::ThrowError("weak handle"); return; } info.GetReturnValue().Set((float) handle->max_latency / (float) handle->sample_rate); } NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_max_latency) { auto client = ObjectWrap::Unwrap(info.Holder()); auto handle = client->_handle.lock(); if(!handle) { Nan::ThrowError("weak handle"); return; } if(info.Length() != 1 || !info[0]->IsNumber()) { Nan::ThrowError("Invalid arguments"); return; } handle->max_latency = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0)); }