<commit_before>//
//  yas_audio_ios_io_core.mm
//

#include "yas_audio_ios_io_core.h"

#if TARGET_OS_IPHONE

#include <AVFoundation/AVFoundation.h>
#include <cpp_utils/yas_cf_utils.h>
#include <cpp_utils/yas_objc_ptr.h>
#include <sstream>
#include "yas_audio_debug.h"
#include "yas_audio_ios_device.h"

using namespace yas;

namespace yas::audio {
static void log_formats(std::string const &prefix, AVAudioFormat const *node_format,
                        audio::format const &device_format) {
    std::ostringstream stream;
    stream << prefix << " - ";
    stream << "node [sample_rate: " << node_format.sampleRate << ", channel_count: " << node_format.channelCount << "]";
    stream << " - ";
    stream << "device [sample_rate: " << device_format.sample_rate()
           << ", channel_count: " << device_format.channel_count() << "]";
    yas_audio_log(stream.str());
}
}

struct audio::ios_io_core::impl {
    std::optional<objc_ptr<AVAudioEngine *>> _avf_engine = std::nullopt;
    std::optional<objc_ptr<AVAudioSourceNode *>> _source_node = std::nullopt;
    std::optional<objc_ptr<AVAudioSinkNode *>> _sink_node = std::nullopt;
};

audio::ios_io_core::ios_io_core(ios_device_ptr const &device) : _device(device), _impl(std::make_unique<impl>()) {
}

audio::ios_io_core::~ios_io_core() {
    this->uninitialize();
}

void audio::ios_io_core::initialize() {
    auto engine = objc_ptr_with_move_object([[AVAudioEngine alloc] init]);
    this->_impl->_avf_engine = engine;

    if (auto const &output_format = this->_device->output_format()) {
        double const sample_rate = output_format->sample_rate();
        uint32_t const channel_count = output_format->channel_count();

        AVAudioFormat *node_format = [engine.object().outputNode outputFormatForBus:0];

        if (sample_rate == node_format.sampleRate) {
            auto source_node = objc_ptr_with_move_object([[AVAudioSourceNode alloc]
                initWithFormat:node_format
                   renderBlock:[weak_io_core = this->_weak_core](
                                   BOOL *_Nonnull isSilence, const AudioTimeStamp *_Nonnull timestamp,
                                   AVAudioFrameCount frameCount, AudioBufferList *_Nonnull outputData) {
                       auto io_core = weak_io_core.lock();
                       if (!io_core) {
                           *isSilence = YES;
                           return OSStatus(noErr);
                       }

                       auto const kernel_opt = io_core->_kernel();

                       if (kernel_opt) {
                           auto const &kernel = kernel_opt.value();
                           if (auto const &output_buffer_opt = kernel->output_buffer) {
                               auto const &output_buffer = *output_buffer_opt;
                               if (outputData) {
                                   uint32_t const frame_length =
                                       audio::frame_length(outputData, output_buffer->format().sample_byte_count());
                                   if (frame_length > 0) {
                                       output_buffer->set_frame_length(frame_length);
                                       audio::time time(*timestamp, output_buffer->format().sample_rate());
                                       output_buffer->clear();
                                       kernel->render_handler(
                                           io_render_args{.output_buffer = output_buffer, .when = std::move(time)});
                                       output_buffer->copy_to(outputData);
                                   } else {
                                       *isSilence = YES;
                                   }
                               } else {
                                   *isSilence = YES;
                               }
                           } else {
                               if (kernel->input_buffer) {
                                   kernel->render_handler(
                                       io_render_args{.output_buffer = std::nullopt, .when = std::nullopt});
                               }
                               *isSilence = YES;
                           }
                       } else {
                           *isSilence = YES;
                       }

                       io_core->_input_buffer_on_render = std::nullopt;
                       io_core->_input_time_on_render = std::nullopt;

                       return OSStatus(noErr);
                   }]);

            auto const objc_channel_layout =
                objc_ptr_with_move_object([[AVAudioChannelLayout alloc] initWithLayoutTag:channel_count]);
            auto const objc_output_format = objc_ptr_with_move_object([[AVAudioFormat alloc]
                initStandardFormatWithSampleRate:output_format->sample_rate()
                                   channelLayout:objc_channel_layout.object()]);

            [engine.object() attachNode:source_node.object()];
            [engine.object() connect:source_node.object()
                                  to:engine.object().outputNode
                              format:objc_output_format.object()];

            this->_impl->_source_node = source_node;

            log_formats("ios_io_core initialize output connected.", node_format, *output_format);
        } else {
            log_formats("ios_io_core initialize output formats did not match.", node_format, *output_format);
        }
    }

    if (auto const &input_format = this->_device->input_format()) {
        double const sample_rate = input_format->sample_rate();
        uint32_t const channel_count = input_format->channel_count();

        AVAudioFormat *node_format = [engine.object().inputNode inputFormatForBus:0];

        if (sample_rate == node_format.sampleRate) {
            auto sink_node = objc_ptr_with_move_object([[AVAudioSinkNode alloc]
                initWithReceiverBlock:[weak_io_core = this->_weak_core](const AudioTimeStamp *_Nonnull timestamp,
                                                                        AVAudioFrameCount frameCount,
                                                                        const AudioBufferList *_Nonnull inputData) {
                    if (auto io_core = weak_io_core.lock()) {
                        if (auto kernel_opt = io_core->_kernel()) {
                            auto const &kernel = kernel_opt.value();

                            kernel->reset_buffers();

                            if (inputData) {
                                if (auto const &input_buffer_opt = kernel->input_buffer) {
                                    auto const &input_buffer = *input_buffer_opt;

                                    input_buffer->copy_from(inputData);

                                    uint32_t const input_frame_length = input_buffer->frame_length();
                                    if (input_frame_length > 0) {
                                        io_core->_input_buffer_on_render = input_buffer;
                                        io_core->_input_time_on_render = std::make_shared<audio::time>(
                                            *timestamp, input_buffer->format().sample_rate());

                                        if (!kernel->output_buffer) {
                                            kernel->render_handler(
                                                io_render_args{.output_buffer = std::nullopt, .when = std::nullopt});
                                            io_core->_input_buffer_on_render = std::nullopt;
                                            io_core->_input_time_on_render = std::nullopt;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    return OSStatus(noErr);
                }]);

            auto const objc_channel_layout =
                objc_ptr_with_move_object([[AVAudioChannelLayout alloc] initWithLayoutTag:channel_count]);
            auto const objc_input_format = objc_ptr_with_move_object([[AVAudioFormat alloc]
                initStandardFormatWithSampleRate:input_format->sample_rate()
                                   channelLayout:objc_channel_layout.object()]);

            [engine.object() attachNode:sink_node.object()];
            [engine.object() connect:engine.object().inputNode to:sink_node.object() format:objc_input_format.object()];

            this->_impl->_sink_node = sink_node;

            log_formats("ios_io_core initialize input connected.", node_format, *input_format);
        } else {
            log_formats("ios_io_core initialize input formats did not match.", node_format, *input_format);
        }
    }

    this->_update_kernel();
}

void audio::ios_io_core::uninitialize() {
    this->stop();

    if (auto const &engine_opt = this->_impl->_avf_engine) {
        auto const &engine = engine_opt.value();

        if (auto const &sink_node_opt = this->_impl->_sink_node) {
            auto const &sink_node = sink_node_opt.value();
            if ([engine.object().attachedNodes containsObject:sink_node.object()]) {
                [engine.object() disconnectNodeInput:sink_node.object()];
                [engine.object() detachNode:sink_node.object()];
            }
            this->_impl->_sink_node = std::nullopt;
        }

        if (auto const &source_node_opt = this->_impl->_source_node) {
            auto const &source_node = source_node_opt.value();
            if ([engine.object().attachedNodes containsObject:source_node.object()]) {
                [engine.object() disconnectNodeInput:source_node.object()];
                [engine.object() detachNode:source_node.object()];
            }
            this->_impl->_source_node = std::nullopt;
        }

        this->_impl->_avf_engine = nullptr;
    }

    this->_update_kernel();
}

void audio::ios_io_core::set_render_handler(std::optional<io_render_f> handler) {
    if (this->__render_handler && handler) {
        std::lock_guard<std::recursive_mutex> lock(this->_kernel_mutex);
        this->__render_handler = std::move(handler);
        this->_update_kernel();
    }
}

void audio::ios_io_core::set_maximum_frames_per_slice(uint32_t const frames) {
    if (this->__maximum_frames != frames) {<commit_msg>fix update render_handler<commit_after>//
//  yas_audio_ios_io_core.mm
//

#include "yas_audio_ios_io_core.h"

#if TARGET_OS_IPHONE

#include <AVFoundation/AVFoundation.h>
#include <cpp_utils/yas_cf_utils.h>
#include <cpp_utils/yas_objc_ptr.h>
#include <sstream>
#include "yas_audio_debug.h"
#include "yas_audio_ios_device.h"

using namespace yas;

namespace yas::audio {
static void log_formats(std::string const &prefix, AVAudioFormat const *node_format,
                        audio::format const &device_format) {
    std::ostringstream stream;
    stream << prefix << " - ";
    stream << "node [sample_rate: " << node_format.sampleRate << ", channel_count: " << node_format.channelCount << "]";
    stream << " - ";
    stream << "device [sample_rate: " << device_format.sample_rate()
           << ", channel_count: " << device_format.channel_count() << "]";
    yas_audio_log(stream.str());
}
}

struct audio::ios_io_core::impl {
    std::optional<objc_ptr<AVAudioEngine *>> _avf_engine = std::nullopt;
    std::optional<objc_ptr<AVAudioSourceNode *>> _source_node = std::nullopt;
    std::optional<objc_ptr<AVAudioSinkNode *>> _sink_node = std::nullopt;
};

audio::ios_io_core::ios_io_core(ios_device_ptr const &device) : _device(device), _impl(std::make_unique<impl>()) {
}

audio::ios_io_core::~ios_io_core() {
    this->uninitialize();
}

void audio::ios_io_core::initialize() {
    auto engine = objc_ptr_with_move_object([[AVAudioEngine alloc] init]);
    this->_impl->_avf_engine = engine;

    if (auto const &output_format = this->_device->output_format()) {
        double const sample_rate = output_format->sample_rate();
        uint32_t const channel_count = output_format->channel_count();

        AVAudioFormat *node_format = [engine.object().outputNode outputFormatForBus:0];

        if (sample_rate == node_format.sampleRate) {
            auto source_node = objc_ptr_with_move_object([[AVAudioSourceNode alloc]
                initWithFormat:node_format
                   renderBlock:[weak_io_core = this->_weak_core](
                                   BOOL *_Nonnull isSilence, const AudioTimeStamp *_Nonnull timestamp,
                                   AVAudioFrameCount frameCount, AudioBufferList *_Nonnull outputData) {
                       auto io_core = weak_io_core.lock();
                       if (!io_core) {
                           *isSilence = YES;
                           return OSStatus(noErr);
                       }

                       auto const kernel_opt = io_core->_kernel();

                       if (kernel_opt) {
                           auto const &kernel = kernel_opt.value();
                           if (auto const &output_buffer_opt = kernel->output_buffer) {
                               auto const &output_buffer = *output_buffer_opt;
                               if (outputData) {
                                   uint32_t const frame_length =
                                       audio::frame_length(outputData, output_buffer->format().sample_byte_count());
                                   if (frame_length > 0) {
                                       output_buffer->set_frame_length(frame_length);
                                       audio::time time(*timestamp, output_buffer->format().sample_rate());
                                       output_buffer->clear();
                                       kernel->render_handler(
                                           io_render_args{.output_buffer = output_buffer, .when = std::move(time)});
                                       output_buffer->copy_to(outputData);
                                   } else {
                                       *isSilence = YES;
                                   }
                               } else {
                                   *isSilence = YES;
                               }
                           } else {
                               if (kernel->input_buffer) {
                                   kernel->render_handler(
                                       io_render_args{.output_buffer = std::nullopt, .when = std::nullopt});
                               }
                               *isSilence = YES;
                           }
                       } else {
                           *isSilence = YES;
                       }

                       io_core->_input_buffer_on_render = std::nullopt;
                       io_core->_input_time_on_render = std::nullopt;

                       return OSStatus(noErr);
                   }]);

            auto const objc_channel_layout =
                objc_ptr_with_move_object([[AVAudioChannelLayout alloc] initWithLayoutTag:channel_count]);
            auto const objc_output_format = objc_ptr_with_move_object([[AVAudioFormat alloc]
                initStandardFormatWithSampleRate:output_format->sample_rate()
                                   channelLayout:objc_channel_layout.object()]);

            [engine.object() attachNode:source_node.object()];
            [engine.object() connect:source_node.object()
                                  to:engine.object().outputNode
                              format:objc_output_format.object()];

            this->_impl->_source_node = source_node;

            log_formats("ios_io_core initialize output connected.", node_format, *output_format);
        } else {
            log_formats("ios_io_core initialize output formats did not match.", node_format, *output_format);
        }
    }

    if (auto const &input_format = this->_device->input_format()) {
        double const sample_rate = input_format->sample_rate();
        uint32_t const channel_count = input_format->channel_count();

        AVAudioFormat *node_format = [engine.object().inputNode inputFormatForBus:0];

        if (sample_rate == node_format.sampleRate) {
            auto sink_node = objc_ptr_with_move_object([[AVAudioSinkNode alloc]
                initWithReceiverBlock:[weak_io_core = this->_weak_core](const AudioTimeStamp *_Nonnull timestamp,
                                                                        AVAudioFrameCount frameCount,
                                                                        const AudioBufferList *_Nonnull inputData) {
                    if (auto io_core = weak_io_core.lock()) {
                        if (auto kernel_opt = io_core->_kernel()) {
                            auto const &kernel = kernel_opt.value();

                            kernel->reset_buffers();

                            if (inputData) {
                                if (auto const &input_buffer_opt = kernel->input_buffer) {
                                    auto const &input_buffer = *input_buffer_opt;

                                    input_buffer->copy_from(inputData);

                                    uint32_t const input_frame_length = input_buffer->frame_length();
                                    if (input_frame_length > 0) {
                                        io_core->_input_buffer_on_render = input_buffer;
                                        io_core->_input_time_on_render = std::make_shared<audio::time>(
                                            *timestamp, input_buffer->format().sample_rate());

                                        if (!kernel->output_buffer) {
                                            kernel->render_handler(
                                                io_render_args{.output_buffer = std::nullopt, .when = std::nullopt});
                                            io_core->_input_buffer_on_render = std::nullopt;
                                            io_core->_input_time_on_render = std::nullopt;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    return OSStatus(noErr);
                }]);

            auto const objc_channel_layout =
                objc_ptr_with_move_object([[AVAudioChannelLayout alloc] initWithLayoutTag:channel_count]);
            auto const objc_input_format = objc_ptr_with_move_object([[AVAudioFormat alloc]
                initStandardFormatWithSampleRate:input_format->sample_rate()
                                   channelLayout:objc_channel_layout.object()]);

            [engine.object() attachNode:sink_node.object()];
            [engine.object() connect:engine.object().inputNode to:sink_node.object() format:objc_input_format.object()];

            this->_impl->_sink_node = sink_node;

            log_formats("ios_io_core initialize input connected.", node_format, *input_format);
        } else {
            log_formats("ios_io_core initialize input formats did not match.", node_format, *input_format);
        }
    }

    this->_update_kernel();
}

void audio::ios_io_core::uninitialize() {
    this->stop();

    if (auto const &engine_opt = this->_impl->_avf_engine) {
        auto const &engine = engine_opt.value();

        if (auto const &sink_node_opt = this->_impl->_sink_node) {
            auto const &sink_node = sink_node_opt.value();
            if ([engine.object().attachedNodes containsObject:sink_node.object()]) {
                [engine.object() disconnectNodeInput:sink_node.object()];
                [engine.object() detachNode:sink_node.object()];
            }
            this->_impl->_sink_node = std::nullopt;
        }

        if (auto const &source_node_opt = this->_impl->_source_node) {
            auto const &source_node = source_node_opt.value();
            if ([engine.object().attachedNodes containsObject:source_node.object()]) {
                [engine.object() disconnectNodeInput:source_node.object()];
                [engine.object() detachNode:source_node.object()];
            }
            this->_impl->_source_node = std::nullopt;
        }

        this->_impl->_avf_engine = nullptr;
    }

    this->_update_kernel();
}

void audio::ios_io_core::set_render_handler(std::optional<io_render_f> handler) {
    if (this->__render_handler || handler) {
        std::lock_guard<std::recursive_mutex> lock(this->_kernel_mutex);
        this->__render_handler = std::move(handler);
        this->_update_kernel();
    }
}

void audio::ios_io_core::set_maximum_frames_per_slice(uint32_t const frames) {
    if (this->__maximum_frames != frames) {
{'fraction_non_alphanumeric': 0.09905072394285167, 'fraction_numerical': 0.0013424105858663342, 'mean_word_length': 1.6181749717585039, 'pattern_counts': {'":': 0, '<': 83, '<?xml version=': 0, '>': 161, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 104, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4928526', 'n_tokens_mistral': 5439, 'n_tokens_neox': 5208, 'n_words': 846}