home.social

#include — Public Fediverse posts

Live and recent posts from across the Fediverse tagged #include, aggregated by home.social.

  1. Europe without Turkey ‘Incomplete, Vulnerable in Managing Crises: Erdogan

    Islam Times – Turkish President Recep Tayyip Erdogan said on Saturday that a European architecture that does not…
    #Europe #EU #‘Incomplete #a #and #Architecture #Crises #does #Erdogan #European #in #include #Islam #its #Managing #not #on #place #President #Recep #remain #rightful #said #Saturday #Tayyip #that #Times #turkey #Turkish #vulnerable #without #would
    europesays.com/europe/36773/

  2. CW: C/C++ hot take

    as far as I can tell header files have no advantages over just putting your forward declarations at the top of your .c / .cpp files

    like it’s bad enough that you have to declare everything twice, but putting your second declaration in a totally separate file that you have to remember to keep updated? that will definitely bite you in the ass

    also the existence of header files requires you to use some arcane rube goldberg build system like make that’s held together with chewing gum and rubber bands, and probably isn’t even portable. when instead you could literally just include your .c / .cpp files directly and bypass that whole process

    and it’s not even hard to write a .c/.cpp file that doesn’t need a headerfile:

    // inside of funcs.cpp:
    #ifndef FUNCS_CPP
    #define FUNCS_CPP
    int someFunc();
    int someFunc() { return 666; }
    #endif
    
    // and you just import it like this:
    #include "./funcs.cpp"
    

    like why didn’t people figure this out decades ago. who even thought that headerfiles were a good idea. maybe I’m missing something but they genuinely seem to have only downsides and aren’t even the obvious way to solve this problem?

  3. CW: C/C++ hot take

    as far as I can tell header files have no advantages over just putting your forward declarations at the top of your .c / .cpp files

    like it’s bad enough that you have to declare everything twice, but putting your second declaration in a totally separate file that you have to remember to keep updated? that will definitely bite you in the ass

    also the existence of header files requires you to use some arcane rube goldberg build system like make that’s held together with chewing gum and rubber bands, and probably isn’t even portable. when instead you could literally just include your .c / .cpp files directly and bypass that whole process

    and it’s not even hard to write a .c/.cpp file that doesn’t need a headerfile:

    // inside of funcs.cpp:
    #ifndef FUNCS_CPP
    #define FUNCS_CPP
    int someFunc();
    int someFunc() { return 666; }
    #endif
    
    // and you just import it like this:
    #include "./funcs.cpp"
    

    like why didn’t people figure this out decades ago. who even thought that headerfiles were a good idea. maybe I’m missing something but they genuinely seem to have only downsides and aren’t even the obvious way to solve this problem?

  4. CW: C/C++ hot take

    as far as I can tell header files have no advantages over just putting your forward declarations at the top of your .c / .cpp files

    like it’s bad enough that you have to declare everything twice, but putting your second declaration in a totally separate file that you have to remember to keep updated? that will definitely bite you in the ass

    also the existence of header files requires you to use some arcane rube goldberg build system like make that’s held together with chewing gum and rubber bands, and probably isn’t even portable. when instead you could literally just include your .c / .cpp files directly and bypass that whole process

    and it’s not even hard to write a .c/.cpp file that doesn’t need a headerfile:

    // inside of funcs.cpp:
    #ifndef FUNCS_CPP
    #define FUNCS_CPP
    int someFunc();
    int someFunc() { return 666; }
    #endif
    
    // and you just import it like this:
    #include "./funcs.cpp"
    

    like why didn’t people figure this out decades ago. who even thought that headerfiles were a good idea. maybe I’m missing something but they genuinely seem to have only downsides and aren’t even the obvious way to solve this problem?

  5. CW: C/C++ hot take

    as far as I can tell header files have no advantages over just putting your forward declarations at the top of your .c / .cpp files

    like it’s bad enough that you have to declare everything twice, but putting your second declaration in a totally separate file that you have to remember to keep updated? that will definitely bite you in the ass

    also the existence of header files requires you to use some arcane rube goldberg build system like make that’s held together with chewing gum and rubber bands, and probably isn’t even portable. when instead you could literally just include your .c / .cpp files directly and bypass that whole process

    and it’s not even hard to write a .c/.cpp file that doesn’t need a headerfile:

    // inside of funcs.cpp:
    #ifndef FUNCS_CPP
    #define FUNCS_CPP
    int someFunc();
    int someFunc() { return 666; }
    #endif
    
    // and you just import it like this:
    #include "./funcs.cpp"
    

    like why didn’t people figure this out decades ago. who even thought that headerfiles were a good idea. maybe I’m missing something but they genuinely seem to have only downsides and aren’t even the obvious way to solve this problem?

  6. CW: C/C++ hot take

    as far as I can tell header files have no advantages over just putting your forward declarations at the top of your .c / .cpp files

    like it’s bad enough that you have to declare everything twice, but putting your second declaration in a totally separate file that you have to remember to keep updated? that will definitely bite you in the ass

    also the existence of header files requires you to use some arcane rube goldberg build system like make that’s held together with chewing gum and rubber bands, and probably isn’t even portable. when instead you could literally just include your .c / .cpp files directly and bypass that whole process

    and it’s not even hard to write a .c/.cpp file that doesn’t need a headerfile:

    // inside of funcs.cpp:
    #ifndef FUNCS_CPP
    #define FUNCS_CPP
    int someFunc();
    int someFunc() { return 666; }
    #endif
    
    // and you just import it like this:
    #include "./funcs.cpp"
    

    like why didn’t people figure this out decades ago. who even thought that headerfiles were a good idea. maybe I’m missing something but they genuinely seem to have only downsides and aren’t even the obvious way to solve this problem?

  7. CW: C++
    #include "util/ffmpeg_movie_writer.h"

    #include <godot_cpp/classes/image.hpp>

    #ifdef FFMPEG_AVAILABLE
    extern "C" {
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libswresample/swresample.h>
    #include <libswscale/swscale.h>
    }
    #endif

    void FFmpegMovieWriter::_bind_methods() {
    }

    uint32_t FFmpegMovieWriter::_get_audio_mix_rate() const {
    return 48000;
    }

    AudioServer::SpeakerMode FFmpegMovieWriter::_get_audio_speaker_mode() const {
    return AudioServer::SPEAKER_MODE_STEREO;
    }

    bool FFmpegMovieWriter::_handles_file(const String &p_path) const {
    return p_path.get_extension().to_lower() == "mkv";
    }

    PackedStringArray FFmpegMovieWriter::_get_supported_extensions() const {
    return { "mkv" };
    }

    Error FFmpegMovieWriter::_write_begin(const Vector2i &p_movie_size, uint32_t p_fps, const String &p_base_path) {
    #ifdef FFMPEG_AVAILABLE
    int error = avformat_alloc_output_context2(&_context, nullptr, "matroska", p_base_path.utf8().get_data());
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    const AVCodec *video_codec = avcodec_find_encoder(AV_CODEC_ID_AV1);
    ERR_FAIL_NULL_V_MSG(video_codec, FAILED, "ffmpeg was not compiled with AV1 support");

    _video_context = avcodec_alloc_context3(video_codec);
    ERR_FAIL_NULL_V_MSG(_video_context, FAILED, "failed to allocate video context");

    _video_context->time_base = av_make_q(1, p_fps);
    _video_context->framerate = av_make_q(p_fps, 1);
    _video_context->width = p_movie_size.x;
    _video_context->height = p_movie_size.y;
    _video_context->sample_aspect_ratio = av_make_q(1, 1);
    _video_context->pix_fmt = AV_PIX_FMT_YUV420P;

    AVDictionary *video_options = nullptr;
    av_dict_set_int(&video_options, "crf", 30, 0);

    error = avcodec_open2(_video_context, video_codec, &video_options);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    av_dict_free(&video_options);

    _video_stream = avformat_new_stream(_context, video_codec);
    ERR_FAIL_NULL_V_MSG(_video_stream, FAILED, "failed to create video stream");
    error = avcodec_parameters_from_context(_video_stream->codecpar, _video_context);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    const AVCodec *audio_codec = avcodec_find_encoder(AV_CODEC_ID_OPUS);
    ERR_FAIL_NULL_V_MSG(audio_codec, FAILED, "ffmpeg was not compiled with Opus support");

    _audio_context = avcodec_alloc_context3(audio_codec);
    ERR_FAIL_NULL_V_MSG(_audio_context, FAILED, "failed to allocate audio context");

    _audio_context->bit_rate = 96000;
    _audio_context->time_base = av_make_q(1, 48000);
    _audio_context->sample_rate = 48000;
    _audio_context->sample_fmt = AV_SAMPLE_FMT_S16;
    _audio_context->ch_layout = AV_CHANNEL_LAYOUT_STEREO;

    error = avcodec_open2(_audio_context, audio_codec, nullptr);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _audio_stream = avformat_new_stream(_context, audio_codec);
    ERR_FAIL_NULL_V_MSG(_audio_stream, FAILED, "failed to create audio stream");
    error = avcodec_parameters_from_context(_audio_stream->codecpar, _audio_context);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _image_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_image_frame, FAILED, "failed to create image frame");
    _image_frame->width = p_movie_size.x;
    _image_frame->height = p_movie_size.y;
    _image_frame->format = AV_PIX_FMT_RGBA;
    _image_frame->sample_aspect_ratio = av_make_q(1, 1);
    error = av_frame_get_buffer(_image_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _video_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_video_frame, FAILED, "failed to create video frame");
    _video_frame->width = _video_context->width;
    _video_frame->height = _video_context->height;
    _video_frame->format = _video_context->pix_fmt;
    _video_frame->sample_aspect_ratio = _video_context->sample_aspect_ratio;
    error = av_frame_get_buffer(_video_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _sws_context = sws_alloc_context();
    ERR_FAIL_NULL_V_MSG(_sws_context, FAILED, "failed to allocate libswscale context");

    error = sws_frame_setup(_sws_context, _video_frame, _image_frame);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    DEV_ASSERT(_audio_context->sample_rate % p_fps == 0);
    _raw_audio_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_raw_audio_frame, FAILED, "failed to create raw audio frame");
    _raw_audio_frame->nb_samples = _audio_context->sample_rate / p_fps;
    _raw_audio_frame->format = AV_SAMPLE_FMT_S32;
    _raw_audio_frame->sample_rate = _audio_context->sample_rate;
    error = av_channel_layout_copy(&_raw_audio_frame->ch_layout, &_audio_context->ch_layout);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));
    error = av_frame_get_buffer(_raw_audio_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _audio_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_audio_frame, FAILED, "failed to create audio frame");
    _audio_frame->nb_samples = _audio_context->frame_size;
    _audio_frame->format = _audio_context->sample_fmt;
    _audio_frame->sample_rate = _audio_context->sample_rate;
    error = av_channel_layout_copy(&_audio_frame->ch_layout, &_audio_context->ch_layout);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));
    error = av_frame_get_buffer(_audio_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _swr_context = swr_alloc();
    ERR_FAIL_NULL_V_MSG(_swr_context, FAILED, "failed to allocate libswresample context");

    error = swr_config_frame(_swr_context, _audio_frame, _raw_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    error = swr_init(_swr_context);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _packet = av_packet_alloc();

    error = avio_open(&_context->pb, p_base_path.utf8().get_data(), AVIO_FLAG_WRITE);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    error = avformat_write_header(_context, nullptr);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    return OK;
    #else
    return ERR_UNAVAILABLE;
    #endif
    }

    Error FFmpegMovieWriter::_write_frame(const Ref<Image> &p_frame_image, const void *p_audio_frame_block) {
    #ifdef FFMPEG_AVAILABLE
    ERR_FAIL_COND_V(!_write_image(p_frame_image), FAILED);
    ERR_FAIL_COND_V(!_write_audio(p_audio_frame_block), FAILED);

    return OK;
    #else
    return ERR_UNAVAILABLE;
    #endif
    }

    void FFmpegMovieWriter::_write_end() {
    #ifdef FFMPEG_AVAILABLE
    int error = avcodec_send_frame(_video_context, nullptr);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    ERR_FAIL_COND(!_write_packets(_video_context, _video_stream));

    // check for any remaining samples at the end of the stream
    if (swr_get_delay(_swr_context, _audio_frame->sample_rate) > 0) {
    error = av_frame_make_writable(_audio_frame);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    error = swr_convert_frame(_swr_context, _audio_frame, nullptr);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    _audio_frame->pts = _audio_pts;

    error = avcodec_send_frame(_audio_context, _audio_frame);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));
    }

    error = avcodec_send_frame(_audio_context, nullptr);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    ERR_FAIL_COND(!_write_packets(_audio_context, _audio_stream));

    av_write_trailer(_context);

    avio_closep(&_context->pb);

    av_packet_free(&_packet);
    av_frame_free(&_raw_audio_frame);
    av_frame_free(&_audio_frame);
    av_frame_free(&_video_frame);
    av_frame_free(&_image_frame);
    swr_free(&_swr_context);
    sws_free_context(&_sws_context);
    avcodec_free_context(&_audio_context);
    avcodec_free_context(&_video_context);
    avformat_free_context(_context);
    #endif
    }

    #ifdef FFMPEG_AVAILABLE
    bool FFmpegMovieWriter::_write_packets(AVCodecContext *p_context, AVStream *p_stream) {
    while (avcodec_receive_packet(p_context, _packet) == 0) {
    av_packet_rescale_ts(_packet, p_context->time_base, p_stream->time_base);
    _packet->stream_index = p_stream->index;

    if (p_context->pix_fmt != AV_PIX_FMT_NONE) {
    __builtin_debugtrap();
    }

    int error = av_interleaved_write_frame(_context, _packet);
    CRASH_COND(error < 0);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    }

    return true;
    }

    bool FFmpegMovieWriter::_write_image(const Ref<Image> &p_frame_image) {
    DEV_ASSERT(p_frame_image->get_format() == Image::FORMAT_RGBA8);
    DEV_ASSERT(p_frame_image->get_width() == _image_frame->width);
    DEV_ASSERT(p_frame_image->get_height() == _image_frame->height);

    int error = av_frame_make_writable(_image_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    memcpy(_image_frame->data[0], p_frame_image->get_data().ptr(), p_frame_image->get_data_size());

    error = av_frame_make_writable(_video_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    error = sws_scale_frame(_sws_context, _video_frame, _image_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    _video_frame->pts = _video_pts;
    _video_pts++;

    error = avcodec_send_frame(_video_context, _video_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    return _write_packets(_video_context, _video_stream);
    }

    bool FFmpegMovieWriter::_write_audio(const void *p_audio_frame_block) {
    int error = av_frame_make_writable(_raw_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    memcpy(_raw_audio_frame->data[0], p_audio_frame_block, _raw_audio_frame->nb_samples * sizeof(int32_t));

    error = swr_convert_frame(_swr_context, nullptr, _raw_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    while (swr_get_delay(_swr_context, _audio_frame->sample_rate) >= _audio_frame->nb_samples) {
    error = av_frame_make_writable(_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    error = swr_convert_frame(_swr_context, _audio_frame, nullptr);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    _audio_frame->pts = _audio_pts;
    _audio_pts += _audio_frame->nb_samples;

    error = avcodec_send_frame(_audio_context, _audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    }

    return _write_packets(_audio_context, _audio_stream);
    }
    #endif
  8. CW: C++
    #include "util/ffmpeg_movie_writer.h"

    #include <godot_cpp/classes/image.hpp>

    #ifdef FFMPEG_AVAILABLE
    extern "C" {
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libswresample/swresample.h>
    #include <libswscale/swscale.h>
    }
    #endif

    void FFmpegMovieWriter::_bind_methods() {
    }

    uint32_t FFmpegMovieWriter::_get_audio_mix_rate() const {
    return 48000;
    }

    AudioServer::SpeakerMode FFmpegMovieWriter::_get_audio_speaker_mode() const {
    return AudioServer::SPEAKER_MODE_STEREO;
    }

    bool FFmpegMovieWriter::_handles_file(const String &p_path) const {
    return p_path.get_extension().to_lower() == "mkv";
    }

    PackedStringArray FFmpegMovieWriter::_get_supported_extensions() const {
    return { "mkv" };
    }

    Error FFmpegMovieWriter::_write_begin(const Vector2i &p_movie_size, uint32_t p_fps, const String &p_base_path) {
    #ifdef FFMPEG_AVAILABLE
    int error = avformat_alloc_output_context2(&_context, nullptr, "matroska", p_base_path.utf8().get_data());
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    const AVCodec *video_codec = avcodec_find_encoder(AV_CODEC_ID_AV1);
    ERR_FAIL_NULL_V_MSG(video_codec, FAILED, "ffmpeg was not compiled with AV1 support");

    _video_context = avcodec_alloc_context3(video_codec);
    ERR_FAIL_NULL_V_MSG(_video_context, FAILED, "failed to allocate video context");

    _video_context->time_base = av_make_q(1, p_fps);
    _video_context->framerate = av_make_q(p_fps, 1);
    _video_context->width = p_movie_size.x;
    _video_context->height = p_movie_size.y;
    _video_context->sample_aspect_ratio = av_make_q(1, 1);
    _video_context->pix_fmt = AV_PIX_FMT_YUV420P;

    AVDictionary *video_options = nullptr;
    av_dict_set_int(&video_options, "crf", 30, 0);

    error = avcodec_open2(_video_context, video_codec, &video_options);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    av_dict_free(&video_options);

    _video_stream = avformat_new_stream(_context, video_codec);
    ERR_FAIL_NULL_V_MSG(_video_stream, FAILED, "failed to create video stream");
    error = avcodec_parameters_from_context(_video_stream->codecpar, _video_context);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    const AVCodec *audio_codec = avcodec_find_encoder(AV_CODEC_ID_OPUS);
    ERR_FAIL_NULL_V_MSG(audio_codec, FAILED, "ffmpeg was not compiled with Opus support");

    _audio_context = avcodec_alloc_context3(audio_codec);
    ERR_FAIL_NULL_V_MSG(_audio_context, FAILED, "failed to allocate audio context");

    _audio_context->bit_rate = 96000;
    _audio_context->time_base = av_make_q(1, 48000);
    _audio_context->sample_rate = 48000;
    _audio_context->sample_fmt = AV_SAMPLE_FMT_S16;
    _audio_context->ch_layout = AV_CHANNEL_LAYOUT_STEREO;

    error = avcodec_open2(_audio_context, audio_codec, nullptr);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _audio_stream = avformat_new_stream(_context, audio_codec);
    ERR_FAIL_NULL_V_MSG(_audio_stream, FAILED, "failed to create audio stream");
    error = avcodec_parameters_from_context(_audio_stream->codecpar, _audio_context);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _image_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_image_frame, FAILED, "failed to create image frame");
    _image_frame->width = p_movie_size.x;
    _image_frame->height = p_movie_size.y;
    _image_frame->format = AV_PIX_FMT_RGBA;
    _image_frame->sample_aspect_ratio = av_make_q(1, 1);
    error = av_frame_get_buffer(_image_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _video_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_video_frame, FAILED, "failed to create video frame");
    _video_frame->width = _video_context->width;
    _video_frame->height = _video_context->height;
    _video_frame->format = _video_context->pix_fmt;
    _video_frame->sample_aspect_ratio = _video_context->sample_aspect_ratio;
    error = av_frame_get_buffer(_video_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _sws_context = sws_alloc_context();
    ERR_FAIL_NULL_V_MSG(_sws_context, FAILED, "failed to allocate libswscale context");

    error = sws_frame_setup(_sws_context, _video_frame, _image_frame);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    DEV_ASSERT(_audio_context->sample_rate % p_fps == 0);
    _raw_audio_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_raw_audio_frame, FAILED, "failed to create raw audio frame");
    _raw_audio_frame->nb_samples = _audio_context->sample_rate / p_fps;
    _raw_audio_frame->format = AV_SAMPLE_FMT_S32;
    _raw_audio_frame->sample_rate = _audio_context->sample_rate;
    error = av_channel_layout_copy(&_raw_audio_frame->ch_layout, &_audio_context->ch_layout);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));
    error = av_frame_get_buffer(_raw_audio_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _audio_frame = av_frame_alloc();
    ERR_FAIL_NULL_V_MSG(_audio_frame, FAILED, "failed to create audio frame");
    _audio_frame->nb_samples = _audio_context->frame_size;
    _audio_frame->format = _audio_context->sample_fmt;
    _audio_frame->sample_rate = _audio_context->sample_rate;
    error = av_channel_layout_copy(&_audio_frame->ch_layout, &_audio_context->ch_layout);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));
    error = av_frame_get_buffer(_audio_frame, 0);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _swr_context = swr_alloc();
    ERR_FAIL_NULL_V_MSG(_swr_context, FAILED, "failed to allocate libswresample context");

    error = swr_config_frame(_swr_context, _audio_frame, _raw_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    error = swr_init(_swr_context);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    _packet = av_packet_alloc();

    error = avio_open(&_context->pb, p_base_path.utf8().get_data(), AVIO_FLAG_WRITE);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    error = avformat_write_header(_context, nullptr);
    ERR_FAIL_COND_V_MSG(error < 0, FAILED, av_err2str(error));

    return OK;
    #else
    return ERR_UNAVAILABLE;
    #endif
    }

    Error FFmpegMovieWriter::_write_frame(const Ref<Image> &p_frame_image, const void *p_audio_frame_block) {
    #ifdef FFMPEG_AVAILABLE
    ERR_FAIL_COND_V(!_write_image(p_frame_image), FAILED);
    ERR_FAIL_COND_V(!_write_audio(p_audio_frame_block), FAILED);

    return OK;
    #else
    return ERR_UNAVAILABLE;
    #endif
    }

    void FFmpegMovieWriter::_write_end() {
    #ifdef FFMPEG_AVAILABLE
    int error = avcodec_send_frame(_video_context, nullptr);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    ERR_FAIL_COND(!_write_packets(_video_context, _video_stream));

    // check for any remaining samples at the end of the stream
    if (swr_get_delay(_swr_context, _audio_frame->sample_rate) > 0) {
    error = av_frame_make_writable(_audio_frame);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    error = swr_convert_frame(_swr_context, _audio_frame, nullptr);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    _audio_frame->pts = _audio_pts;

    error = avcodec_send_frame(_audio_context, _audio_frame);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));
    }

    error = avcodec_send_frame(_audio_context, nullptr);
    ERR_FAIL_COND_MSG(error < 0, av_err2str(error));

    ERR_FAIL_COND(!_write_packets(_audio_context, _audio_stream));

    av_write_trailer(_context);

    avio_closep(&_context->pb);

    av_packet_free(&_packet);
    av_frame_free(&_raw_audio_frame);
    av_frame_free(&_audio_frame);
    av_frame_free(&_video_frame);
    av_frame_free(&_image_frame);
    swr_free(&_swr_context);
    sws_free_context(&_sws_context);
    avcodec_free_context(&_audio_context);
    avcodec_free_context(&_video_context);
    avformat_free_context(_context);
    #endif
    }

    #ifdef FFMPEG_AVAILABLE
    bool FFmpegMovieWriter::_write_packets(AVCodecContext *p_context, AVStream *p_stream) {
    while (avcodec_receive_packet(p_context, _packet) == 0) {
    av_packet_rescale_ts(_packet, p_context->time_base, p_stream->time_base);
    _packet->stream_index = p_stream->index;

    if (p_context->pix_fmt != AV_PIX_FMT_NONE) {
    __builtin_debugtrap();
    }

    int error = av_interleaved_write_frame(_context, _packet);
    CRASH_COND(error < 0);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    }

    return true;
    }

    bool FFmpegMovieWriter::_write_image(const Ref<Image> &p_frame_image) {
    DEV_ASSERT(p_frame_image->get_format() == Image::FORMAT_RGBA8);
    DEV_ASSERT(p_frame_image->get_width() == _image_frame->width);
    DEV_ASSERT(p_frame_image->get_height() == _image_frame->height);

    int error = av_frame_make_writable(_image_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    memcpy(_image_frame->data[0], p_frame_image->get_data().ptr(), p_frame_image->get_data_size());

    error = av_frame_make_writable(_video_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    error = sws_scale_frame(_sws_context, _video_frame, _image_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    _video_frame->pts = _video_pts;
    _video_pts++;

    error = avcodec_send_frame(_video_context, _video_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    return _write_packets(_video_context, _video_stream);
    }

    bool FFmpegMovieWriter::_write_audio(const void *p_audio_frame_block) {
    int error = av_frame_make_writable(_raw_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    memcpy(_raw_audio_frame->data[0], p_audio_frame_block, _raw_audio_frame->nb_samples * sizeof(int32_t));

    error = swr_convert_frame(_swr_context, nullptr, _raw_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    while (swr_get_delay(_swr_context, _audio_frame->sample_rate) >= _audio_frame->nb_samples) {
    error = av_frame_make_writable(_audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    error = swr_convert_frame(_swr_context, _audio_frame, nullptr);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));

    _audio_frame->pts = _audio_pts;
    _audio_pts += _audio_frame->nb_samples;

    error = avcodec_send_frame(_audio_context, _audio_frame);
    ERR_FAIL_COND_V_MSG(error < 0, false, av_err2str(error));
    }

    return _write_packets(_audio_context, _audio_stream);
    }
    #endif
  9. GitLab CI кажется сложным, если не знать этих трюков: как убрать дублирование и ускорить пайплайны в 3 раза

    У вас в GitLab несколько проектов с одинаковым CI? Или просто надоела копипаста в пайплайнах? Каждое изменение нужно отразить в 10 местах? Рассказываем, как сократить код на 56 %, убрать дублирование и сделать один CI для всей группы проектов. Шаблоны, матрицы и практика. Избавиться от копипасты

    habr.com/ru/companies/flant/ar

    #gitlab_ci #gitlabci #ci #dry #Common_ci #дублирование_кода #Оптимизация_пайпланов #include #автоматизация_деплоя

  10. GitLab CI кажется сложным, если не знать этих трюков: как убрать дублирование и ускорить пайплайны в 3 раза

    У вас в GitLab несколько проектов с одинаковым CI? Или просто надоела копипаста в пайплайнах? Каждое изменение нужно отразить в 10 местах? Рассказываем, как сократить код на 56 %, убрать дублирование и сделать один CI для всей группы проектов. Шаблоны, матрицы и практика. Избавиться от копипасты

    habr.com/ru/companies/flant/ar

    #gitlab_ci #gitlabci #ci #dry #Common_ci #дублирование_кода #Оптимизация_пайпланов #include #автоматизация_деплоя

  11. GitLab CI кажется сложным, если не знать этих трюков: как убрать дублирование и ускорить пайплайны в 3 раза

    У вас в GitLab несколько проектов с одинаковым CI? Или просто надоела копипаста в пайплайнах? Каждое изменение нужно отразить в 10 местах? Рассказываем, как сократить код на 56 %, убрать дублирование и сделать один CI для всей группы проектов. Шаблоны, матрицы и практика. Избавиться от копипасты

    habr.com/ru/companies/flant/ar

    #gitlab_ci #gitlabci #ci #dry #Common_ci #дублирование_кода #Оптимизация_пайпланов #include #автоматизация_деплоя

  12. GitLab CI кажется сложным, если не знать этих трюков: как убрать дублирование и ускорить пайплайны в 3 раза

    У вас в GitLab несколько проектов с одинаковым CI? Или просто надоела копипаста в пайплайнах? Каждое изменение нужно отразить в 10 местах? Рассказываем, как сократить код на 56 %, убрать дублирование и сделать один CI для всей группы проектов. Шаблоны, матрицы и практика. Избавиться от копипасты

    habr.com/ru/companies/flant/ar

    #gitlab_ci #gitlabci #ci #dry #Common_ci #дублирование_кода #Оптимизация_пайпланов #include #автоматизация_деплоя

  13. @funkylab it’s really to do with it not being standardised more than anything else. Like what is the expectation of preprocessing the following file lol.h

    #ifndef foo
    #define foo
    #else
    #pragma once
    #endif
    #include "lol.h"
    Yo

    Is it a single “Yo” or two lines of “Yo”?

    If it was defined that it needed to be the first thing in a file following whitespace, I think i’f be fine with it.

  14. @funkylab it’s really to do with it not being standardised more than anything else. Like what is the expectation of preprocessing the following file lol.h

    #ifndef foo
    #define foo
    #else
    #pragma once
    #endif
    #include "lol.h"
    Yo

    Is it a single “Yo” or two lines of “Yo”?

    If it was defined that it needed to be the first thing in a file following whitespace, I think i’f be fine with it.

  15. @funkylab it’s really to do with it not being standardised more than anything else. Like what is the expectation of preprocessing the following file lol.h

    #ifndef foo
    #define foo
    #else
    #pragma once
    #endif
    #include "lol.h"
    Yo

    Is it a single “Yo” or two lines of “Yo”?

    If it was defined that it needed to be the first thing in a file following whitespace, I think i’f be fine with it.

  16. @funkylab it’s really to do with it not being standardised more than anything else. Like what is the expectation of preprocessing the following file lol.h

    #ifndef foo
    #define foo
    #else
    #pragma once
    #endif
    #include "lol.h"
    Yo

    Is it a single “Yo” or two lines of “Yo”?

    If it was defined that it needed to be the first thing in a file following whitespace, I think i’f be fine with it.

  17. @funkylab it’s really to do with it not being standardised more than anything else. Like what is the expectation of preprocessing the following file lol.h

    #ifndef foo
    #define foo
    #else
    #pragma once
    #endif
    #include "lol.h"
    Yo

    Is it a single “Yo” or two lines of “Yo”?

    If it was defined that it needed to be the first thing in a file following whitespace, I think i’f be fine with it.

  18. Thanks to the #c23 #embed macro, I may have written the world's shortest #quine.
    ```c
    #include<stdio.h>
    int main(){puts((char[]){
    #embed"q.c"
    ,0});}
    ```
    (I doubt that I'm the first to do this)
    We just need to politely ignore the extra `\n` injected by `puts`. Otherwise, replace `puts` with `printf`.

  19. #MAME 0.285 compiles on #HaikuOS after adding a small upstream patch (a missing #include).

    If you're struggling to compile the latest version of MAME (I bet #FreeBSD and #NetBSD probably have the same issue), give this a go: github.com/mamedev/mame/commit

    Anyway, there's a PR on HaikuPorts for the update, so it should be with you lovely people in the next few days.

  20. Here is the code in the file "HELLO.C". I'm afraid it is rather non-standard.

    /* Take a look at win.c for full details about
    using #link and #run etc..
    */

    #link "-om:\img\hello.img hello.o stdio.o"
    #run "m:\img\hello.img"

    #include <stdio.h>

    main()
    {
    printf("Hello world\n\r");
    fgetc(stdin);
    exit(0);
    }

    #smallc #psion3 #sibo #epoc16

  21. #include <stdpwdgripe.h>

    New policy came down at work today requiring password rotations every 84 days, so best to put that on my #remind(1) calendar:

    REM Oct 22 2025 *84 \
    MSG %"Change Password%"%_\
    Open powershell.exe%_\
    PS> (New-Object -COM Shell.Application).WindowsSecurity()

    FWIW, I have a reminder how to trigger it over remote-desktop because control-alt-delete gets interpreted locally, the alternative control-alt-end doesn't work either (I suspect that's a Windows-RDP-client-specific key chord) and the on-screen-keyboard method didn't work either the last time I tried. But PowerShell can launch the correct dialog. Sometimes you just do what works, even if it's inelegant.

    I miss the Unix ability to just type `passwd` and change passwords without folderol.

  22. Arduino and AY-3-8910 – Part 3

    I suggested in Part 2 that it might be possible to do some simple modulation of the amplitude of the AY-3-8910 channels rather than drive frequencies directly. This is taking a look at the possibilities of some kind of lo-fi direct digital synthesis using that as a basis.

    https://makertube.net/w/uCSiBG5RBufGqspoHMYFPt

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    These are the key tutorials for the main concepts used in this project:

    If you are new to Arduino, see the Getting Started pages.

    Parts list

    • Arduino Uno.
    • AY-3-8910 chip.
    • Either GadgetReboot’s PCB or patch using solderless breadboard or prototyping boards.
    • 5V compatible MIDI interface.
    • Jumper wires.

    Direct Digital Synthesis on the AY-3-8910

    I’ve talked about direct digital synthesis before, so won’t go into full detail again. For more, see Arduino R2R Digital Audio – Part 3 and Arduino PWM Sound Output.

    But the top-level idea is to set the level of the signal according to a value in a wavetable. If this value is updated at a useful audio rate then it will be interpreted as sound.

    There are some pretty major limitations with attempting to do this on the AY-3-8910 however. The biggest one being that there are only 15 levels for the output on each channel.

    So I’ll be working to the following properties:

    • 4-bit resolution for the output.
    • 8-bit wavetable.
    • 8.8 fixed point accumulator to index into the wavetable.
    • 8096 Hz sample rate.

    YouTuber https://www.youtube.com/@inazumadenki5588 had a look at this and showed that the AY-3-8910 needs to be set up as follows:

    • Frequency value for the channel should be set to the highest frequency possible.
    • All channels should be disabled.

    This is due to comments in the datasheet stating that the only way to fully disable a channel is to have 0 in the amplitude field.

    Note: for a 8192 sample rate, that means writing out a sample to the AY-3-8910 registers approximately once every 124uS. With a 256 value wavetable, it takes almost 32 mS to write a complete cycle at the native sample rate, which would be around a 30 Hz output.

    I’m not sure what the largest increment that would still give a useful signal might be, but say it was 8 values from the wavetable, then that would make the highest frequency supported around 1kHz. Not great, but certainly audible, so worth a try.

    Setting up for DDS

    I want a regular, reliable, periodic routine to output the levels from the wavetable, and the usual way to achieve this is using a timer and interrupt. As Timer 1 is already in use to generate the 1MHz clock for the AY-3-8910, I’m going to be configuring Timer 2 as follows:

    • Timer 2 is an 8-bit timer.
    • Use prescalar of 32 which gives a 500kHz clock source (16MHz/32).
    • Use CTC (clear timer on compare) mode.
    • Generate a compare match interrupt.
    • Do not enable any output pins.

    The appropriate ATMega328 registers to enable this are:

      // COM2A[1:0] = 00  No output
    // WGM2[2:0] = 010 CTC mode
    // CS2[2:0] = 011 Prescalar=32
    ASSR = 0;
    TCCR2A = _BV(WGM21);
    TCCR2B = _BV(CS21) | _BV(CS20);
    TCNT2 = 0;
    OCR2A = 60;
    TIMSK2 = _BV(OCIE2A);

    Although it is worth noting that enabling OC1A can be quite useful for debugging. The following toggles the OC2A output (on D11) every time there is a compare match. The frequency seen on D11 will thus be half the anticipated sample frequency.

    pinMode(11, OUTPUT);
    TCCR2A |= _BV(COM2A0); // COM2A[1:0] = 01 for OC2A toggle

    And this does indeed generate a signal. Here is a trace showing a timing GPIO pin and the AY-3-8910 output.

    The problem is that this is meant to be a 440Hz sine wave, and whilst the shape isn’t too bad (it is a little distorted as the amplitude isn’t a true linear shape), the frequency is much nearer 100Hz than 440.

    Analysis of Performance

    The clue is the other trace, which is a timing pin being toggled every time the Interrupt routine is called. This is showing a 1kHz frequency, which means the IRS is being called with a 2kHz frequency rather than the anticipated 8192Hz. Curiously though I am getting an accurate 4kHz toggle on the timer output pin OC1A indicating the timer is correctly counting with a 8kHz frequency.

    No matter how I configured things, the interrupt routine just would not do anything at a faster rate. I had to drop the frequency right down to 2kHz to get the output pin and interrupt routing running together. This means that something in the interrupt routine seems to be taking ~ 450uS to run.

    After a fair bit of prodding and probing and checking the ATMega328 datasheet and double checking the register values, I have to conclude that the AY3891x library is just too slow at updating the registers for it to be able to run from the interrupt routine at this speed.

    Taking a look at the register write() function in the library, which I need to use to update the channel level, I can see the following is happening:

    void AY3891x::write(byte regAddr, byte data) {
    latchAddressMode(regAddr);
    daPinsOutput(data);
    noInterrupts();
    mode010to110();
    mode110to010();
    interrupts();
    daPinsInput();
    }

    void AY3891x::latchAddressMode(byte regAddr) {
    mode010to000();
    daPinsOutput(_chipAddress | regAddr); // Register address is 4 lsb
    mode000to001();
    mode001to000();
    mode000to010();
    }

    void AY3891x::daPinsOutput(byte data) {
    byte i;

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) pinMode(_DA_pin[i], OUTPUT);
    }

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) {
    digitalWrite(_DA_pin[i], data & 0x01);
    data = data >> 1;
    }
    }
    }

    void AY3891x::daPinsInput() {
    byte i;

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) pinMode(_DA_pin[i], INPUT);
    }
    }

    And every one of those modeXXXtoYYY() functions is a call to digitalWrite(), so I make that 22 calls to ditigalWrite() in order to write a single register value, plus around 16 calls to pinMode(). There are also 5 loops each looping over 8 values.

    One person measured the Arduino Uno digitalWrite() function and concluded that it takes 3.4uS to run, so that is a minimum of 75uS of processing in every run through the interrupt routine just for those calls alone. That doesn’t include the calls and other logic going on. It could easily be more than twice that when everything is taken into account.

    Dropping in some temporary pin IO either side of the call to the AY write function itself, and I’m measuring just over 250uS for the register update to happen, and that is just for one channel. This means that anything with a period of that or faster is starving the processor from running at all.

    Measuring the Basic Performance

    At this point I took a step back and created a free-running test sketch to really see what is going on.

    #include "AY3891x.h"

    AY3891x psg( 17, 8, 7, 6, 5, 4, 3, 2, 16, 15, 14);

    #define AY_CLOCK 9 // D9
    void aySetup () {
    pinMode(AY_CLOCK, OUTPUT);
    digitalWrite(AY_CLOCK, LOW);

    TCCR1A = (1 << COM1A0);
    TCCR1B = (1 << WGM12) | (1 << CS10);
    TCCR1C = 0;
    TIMSK1 = 0;
    OCR1AH = 0;
    OCR1AL = 7; // 16MHz / 8 = 2MHz Counter

    psg.begin();

    // Output highest frequency on each channel, but set level to 0
    // Highest freq = 1000000 / (16 * 1) = 62500
    psg.write(AY3891x::ChA_Amplitude, 0);
    psg.write(AY3891x::ChA_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChA_Tone_Period_Fine_Reg, 0);
    psg.write(AY3891x::ChB_Amplitude, 0);
    psg.write(AY3891x::ChB_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChB_Tone_Period_Fine_Reg, 0);
    psg.write(AY3891x::ChC_Amplitude, 0);
    psg.write(AY3891x::ChC_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChC_Tone_Period_Fine_Reg, 0);

    // LOW = channel is in the mix.
    // Turn everything off..
    psg.write(AY3891x::Enable_Reg, 0xFF);
    }

    int toggle;
    void setup() {
    pinMode(11, OUTPUT);
    toggle = LOW;
    digitalWrite(11, toggle);
    aySetup();
    }

    void loop() {
    toggle = !toggle;
    digitalWrite(11, toggle);
    for (int i=0; i<16; i++) {
    psg.write(AY3891x::ChA_Amplitude, i);
    }
    }

    All this is doing is continually writing 0 to 15 to the channel A level register whilst toggling a GPIO pin. Putting an oscilloscope trace on the IO pin and the AY-3-8910 channel A output gives me the following:

    This is running with a period of 6.96mS, meaning each cycle of 16 writes takes 3.5mS, giving me almost 220uS per call to the AY write function which seems to align pretty well with what I was seeing before.

    And this is generating an audible tone at around 280Hz, so regardless of any timer settings or waveform processing, this is going to be the baseline frequency on which everything else would have to rest, which isn’t great.

    Optimising Register Writes

    So at this point I have the choice of attempting to write to the AY-3-8910 myself using PORT IO to eliminate the time it takes for all those loops and digitalWrite() calls. Or I could try some alternative libraries.

    The library I’m using aims for the most portable compatibility: “This library uses the generic digitalWrite() function instead of direct port manipulation, and should therefore work across most, if not all, processors supported by Arduino, so long as enough I/O pins are available for the interface to the PSG.”

    It is a deliberate design choice, but does require all three bus control signals to be used: BDIR, BC1, BC2.

    Alternatives are possible with less pin state changes, but much stricter timing requirements. Some options include:

    The following are projects that have not used a library, but just done their own thing:

    Unfortunately none of these really solves the problem as the PCB I’m using does not neatly map onto IO ports to allow the use of direct PORT IO for the data.

    So to improve things whilst using this same PCB will require me to re-write the library myself.

    As a test however, it is possible to take the IO pin definitions used with the PCB and write a bespoke, optimised register write routine as follows:

    void ayFastWrite (byte reg, byte val) {
    // Mode=Addr Latch
    digitalWrite(BC1, HIGH);
    digitalWrite(BDIR, HIGH);

    // Latch address
    // NB: Addresses are all in range 0..15 so don't need to
    // worry about writing out bits 6,7 - just ensure set to zero
    PORTD = (PORTD & 0x03) | ((reg & 0xCF)<<2);
    PORTB = (PORTB & 0xFE);
    PORTC = (PORTC & 0xF7);

    // Mode = Inactive
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, LOW);

    delayMicroseconds(10);

    // Mode = Write
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, HIGH);

    // Write data
    PORTD = (PORTD & 0x03) | ((val & 0xCF)<<2); // Shift bits 0:5 to 2:7
    PORTB = (PORTB & 0xFE) | ((val & 0x40)>>6); // Shift bit 6 to 0
    PORTC = (PORTC & 0xF7) | ((val & 0x80)>>4); // Shift bit 7 to 3

    // Mode = Inactive
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, LOW);
    }

    I’m using the following mapping of data pins to Arduino digital IO pins to PORTS:

    DA0-DA5D2-D7PORTD Bits 0-5DA6D8PORT B Bit 0DA7A3/D17PORT C Bit 3

    To make this happen I have to ensure that the right bits are set to OUTPUTs and that BC2 is held HIGH prior to using the fastWrite function.

      digitalWrite(BC2, HIGH);
    DDRD |= 0xFC;
    DDRC |= 0x04;
    DDRB |= 0x01;

    This now improves on that previous 280Hz and gives me 1600Hz performance.

    So can I do any better? Well there are still between 6 and 8 calls to digitalWrite going on to handle the control signals…

    #define BC1LOW  {PORTC &= 0xFE;} // A0 LOW
    #define BC1HIGH {PORTC |= 0x01;} // A0 HIGH
    #define BC2LOW {PORTC &= 0xFD;} // A1 LOW
    #define BC2HIGH {PORTC |= 0x02;} // A1 HIGH
    #define BDIRLOW {PORTC &= 0xF7;} // A2 LOW
    #define BDIRHIGH {PORTC |= 0x04;} // A2 HIGH

    void ayFastWrite (byte reg, byte val) {
    // Mode=Addr Latch
    BC1HIGH;
    BDIRHIGH;

    // Latch address
    PORTD = (PORTD & 0x03) | ((reg & 0xCF)<<2);
    PORTB = (PORTB & 0xFE);
    PORTC = (PORTC & 0xF7);

    // Need 400nS Min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

    // Need 100nS settle then 50nS preamble
    delayMicroseconds(1);

    // Mode = Write
    BC1LOW;
    BDIRHIGH;

    // Write data
    PORTD = (PORTD & 0x03) | ((val & 0xCF)<<2); // Shift bits 0:5 to 2:7
    PORTB = (PORTB & 0xFE) | ((val & 0x40)>>6); // Shift bit 6 to 0
    PORTC = (PORTC & 0xF7) | ((val & 0x80)>>4); // Shift bit 7 to 3

    // Need 500nS min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

    // Need 100nS min
    }

    The timings come from the AY-3-8910 datasheet:

    The actual minimum and maximum timings for the various “t” values are given in the preceeding table. Most have a minimum value, but tBD has to be noted: the “associative delay time” is 50nS. This means that any changing of BC1, BC2 and BDIR has to occur within 50nS to be considered part of the same action.

    There is no means of having a nano-second delay (well, other than just spinning code), so I’ve just used a delayMicroseconds(1) here and there. This isn’t reliably accurate on an Arduino, but as I’m have delays of around half of that as a maximum it seems to be fine.

    This now gives me the following:

    This is now supporting a natural “as fast as possible” frequency of around 24kHz, meaning each call to the write function is now around 3uS. That is almost a 100x improvement over using all those pinMode and digitalWrite calls.

    The downside of this method:

    • It is ATMega328 specific.
    • It is specific to the pin mappings and PORT usage of this PCB.
    • It does not support reading or other chip operations between the writes.

    It is also interesting to see that the traces also show the high frequency oscillation (62.5kHz) that is being modulated regardless of the channel frequency and enable settings.

    DDS Part 2

    Success! At least with a single channel. This is now playing a pretty well in tune 440Hz A.

    Notice how the frequency of the timing pin is now ~4.2kHz meaning that the ISR is now indeed firing at the required 8192 Hz.

    Here is a close-up of the output signal. The oscilloscope was struggling to get a clean frequency reading, but this is one time I caught it reading something close! I checked the sound itself with a tuning fork (see video). It is indeed 440Hz.

    Find it on GitHub here.

    Closing Thoughts

    I wanted to get something put together to allow me to drive a DSS wavetable over MIDI, with different waveforms, and so on, but it turned out to be a little more involved getting this far than I anticipated, so I’ll leave it here for now.

    But hopefully filling in the gaps won’t take too long and will be the subject of a further post.

    Now that I have something that works, I’m actually quite surprised by how well it is working.

    Kevin

    #arduinoNano #ay38910 #dds #define #directDigitalSynthesis #include #midi

  23. Arduino and AY-3-8910 – Part 3

    I suggested in Part 2 that it might be possible to do some simple modulation of the amplitude of the AY-3-8910 channels rather than drive frequencies directly. This is taking a look at the possibilities of some kind of lo-fi direct digital synthesis using that as a basis.

    https://makertube.net/w/uCSiBG5RBufGqspoHMYFPt

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    These are the key tutorials for the main concepts used in this project:

    If you are new to Arduino, see the Getting Started pages.

    Parts list

    • Arduino Uno.
    • AY-3-8910 chip.
    • Either GadgetReboot’s PCB or patch using solderless breadboard or prototyping boards.
    • 5V compatible MIDI interface.
    • Jumper wires.

    Direct Digital Synthesis on the AY-3-8910

    I’ve talked about direct digital synthesis before, so won’t go into full detail again. For more, see Arduino R2R Digital Audio – Part 3 and Arduino PWM Sound Output.

    But the top-level idea is to set the level of the signal according to a value in a wavetable. If this value is updated at a useful audio rate then it will be interpreted as sound.

    There are some pretty major limitations with attempting to do this on the AY-3-8910 however. The biggest one being that there are only 15 levels for the output on each channel.

    So I’ll be working to the following properties:

    • 4-bit resolution for the output.
    • 8-bit wavetable.
    • 8.8 fixed point accumulator to index into the wavetable.
    • 8096 Hz sample rate.

    YouTuber https://www.youtube.com/@inazumadenki5588 had a look at this and showed that the AY-3-8910 needs to be set up as follows:

    • Frequency value for the channel should be set to the highest frequency possible.
    • All channels should be disabled.

    This is due to comments in the datasheet stating that the only way to fully disable a channel is to have 0 in the amplitude field.

    Note: for a 8192 sample rate, that means writing out a sample to the AY-3-8910 registers approximately once every 124uS. With a 256 value wavetable, it takes almost 32 mS to write a complete cycle at the native sample rate, which would be around a 30 Hz output.

    I’m not sure what the largest increment that would still give a useful signal might be, but say it was 8 values from the wavetable, then that would make the highest frequency supported around 1kHz. Not great, but certainly audible, so worth a try.

    Setting up for DDS

    I want a regular, reliable, periodic routine to output the levels from the wavetable, and the usual way to achieve this is using a timer and interrupt. As Timer 1 is already in use to generate the 1MHz clock for the AY-3-8910, I’m going to be configuring Timer 2 as follows:

    • Timer 2 is an 8-bit timer.
    • Use prescalar of 32 which gives a 500kHz clock source (16MHz/32).
    • Use CTC (clear timer on compare) mode.
    • Generate a compare match interrupt.
    • Do not enable any output pins.

    The appropriate ATMega328 registers to enable this are:

      // COM2A[1:0] = 00  No output
    // WGM2[2:0] = 010 CTC mode
    // CS2[2:0] = 011 Prescalar=32
    ASSR = 0;
    TCCR2A = _BV(WGM21);
    TCCR2B = _BV(CS21) | _BV(CS20);
    TCNT2 = 0;
    OCR2A = 60;
    TIMSK2 = _BV(OCIE2A);

    Although it is worth noting that enabling OC1A can be quite useful for debugging. The following toggles the OC2A output (on D11) every time there is a compare match. The frequency seen on D11 will thus be half the anticipated sample frequency.

    pinMode(11, OUTPUT);
    TCCR2A |= _BV(COM2A0); // COM2A[1:0] = 01 for OC2A toggle

    And this does indeed generate a signal. Here is a trace showing a timing GPIO pin and the AY-3-8910 output.

    The problem is that this is meant to be a 440Hz sine wave, and whilst the shape isn’t too bad (it is a little distorted as the amplitude isn’t a true linear shape), the frequency is much nearer 100Hz than 440.

    Analysis of Performance

    The clue is the other trace, which is a timing pin being toggled every time the Interrupt routine is called. This is showing a 1kHz frequency, which means the IRS is being called with a 2kHz frequency rather than the anticipated 8192Hz. Curiously though I am getting an accurate 4kHz toggle on the timer output pin OC1A indicating the timer is correctly counting with a 8kHz frequency.

    No matter how I configured things, the interrupt routine just would not do anything at a faster rate. I had to drop the frequency right down to 2kHz to get the output pin and interrupt routing running together. This means that something in the interrupt routine seems to be taking ~ 450uS to run.

    After a fair bit of prodding and probing and checking the ATMega328 datasheet and double checking the register values, I have to conclude that the AY3891x library is just too slow at updating the registers for it to be able to run from the interrupt routine at this speed.

    Taking a look at the register write() function in the library, which I need to use to update the channel level, I can see the following is happening:

    void AY3891x::write(byte regAddr, byte data) {
    latchAddressMode(regAddr);
    daPinsOutput(data);
    noInterrupts();
    mode010to110();
    mode110to010();
    interrupts();
    daPinsInput();
    }

    void AY3891x::latchAddressMode(byte regAddr) {
    mode010to000();
    daPinsOutput(_chipAddress | regAddr); // Register address is 4 lsb
    mode000to001();
    mode001to000();
    mode000to010();
    }

    void AY3891x::daPinsOutput(byte data) {
    byte i;

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) pinMode(_DA_pin[i], OUTPUT);
    }

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) {
    digitalWrite(_DA_pin[i], data & 0x01);
    data = data >> 1;
    }
    }
    }

    void AY3891x::daPinsInput() {
    byte i;

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) pinMode(_DA_pin[i], INPUT);
    }
    }

    And every one of those modeXXXtoYYY() functions is a call to digitalWrite(), so I make that 22 calls to ditigalWrite() in order to write a single register value, plus around 16 calls to pinMode(). There are also 5 loops each looping over 8 values.

    One person measured the Arduino Uno digitalWrite() function and concluded that it takes 3.4uS to run, so that is a minimum of 75uS of processing in every run through the interrupt routine just for those calls alone. That doesn’t include the calls and other logic going on. It could easily be more than twice that when everything is taken into account.

    Dropping in some temporary pin IO either side of the call to the AY write function itself, and I’m measuring just over 250uS for the register update to happen, and that is just for one channel. This means that anything with a period of that or faster is starving the processor from running at all.

    Measuring the Basic Performance

    At this point I took a step back and created a free-running test sketch to really see what is going on.

    #include "AY3891x.h"

    AY3891x psg( 17, 8, 7, 6, 5, 4, 3, 2, 16, 15, 14);

    #define AY_CLOCK 9 // D9
    void aySetup () {
    pinMode(AY_CLOCK, OUTPUT);
    digitalWrite(AY_CLOCK, LOW);

    TCCR1A = (1 << COM1A0);
    TCCR1B = (1 << WGM12) | (1 << CS10);
    TCCR1C = 0;
    TIMSK1 = 0;
    OCR1AH = 0;
    OCR1AL = 7; // 16MHz / 8 = 2MHz Counter

    psg.begin();

    // Output highest frequency on each channel, but set level to 0
    // Highest freq = 1000000 / (16 * 1) = 62500
    psg.write(AY3891x::ChA_Amplitude, 0);
    psg.write(AY3891x::ChA_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChA_Tone_Period_Fine_Reg, 0);
    psg.write(AY3891x::ChB_Amplitude, 0);
    psg.write(AY3891x::ChB_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChB_Tone_Period_Fine_Reg, 0);
    psg.write(AY3891x::ChC_Amplitude, 0);
    psg.write(AY3891x::ChC_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChC_Tone_Period_Fine_Reg, 0);

    // LOW = channel is in the mix.
    // Turn everything off..
    psg.write(AY3891x::Enable_Reg, 0xFF);
    }

    int toggle;
    void setup() {
    pinMode(11, OUTPUT);
    toggle = LOW;
    digitalWrite(11, toggle);
    aySetup();
    }

    void loop() {
    toggle = !toggle;
    digitalWrite(11, toggle);
    for (int i=0; i<16; i++) {
    psg.write(AY3891x::ChA_Amplitude, i);
    }
    }

    All this is doing is continually writing 0 to 15 to the channel A level register whilst toggling a GPIO pin. Putting an oscilloscope trace on the IO pin and the AY-3-8910 channel A output gives me the following:

    This is running with a period of 6.96mS, meaning each cycle of 16 writes takes 3.5mS, giving me almost 220uS per call to the AY write function which seems to align pretty well with what I was seeing before.

    And this is generating an audible tone at around 280Hz, so regardless of any timer settings or waveform processing, this is going to be the baseline frequency on which everything else would have to rest, which isn’t great.

    Optimising Register Writes

    So at this point I have the choice of attempting to write to the AY-3-8910 myself using PORT IO to eliminate the time it takes for all those loops and digitalWrite() calls. Or I could try some alternative libraries.

    The library I’m using aims for the most portable compatibility: “This library uses the generic digitalWrite() function instead of direct port manipulation, and should therefore work across most, if not all, processors supported by Arduino, so long as enough I/O pins are available for the interface to the PSG.”

    It is a deliberate design choice, but does require all three bus control signals to be used: BDIR, BC1, BC2.

    Alternatives are possible with less pin state changes, but much stricter timing requirements. Some options include:

    The following are projects that have not used a library, but just done their own thing:

    Unfortunately none of these really solves the problem as the PCB I’m using does not neatly map onto IO ports to allow the use of direct PORT IO for the data.

    So to improve things whilst using this same PCB will require me to re-write the library myself.

    As a test however, it is possible to take the IO pin definitions used with the PCB and write a bespoke, optimised register write routine as follows:

    void ayFastWrite (byte reg, byte val) {
    // Mode=Addr Latch
    digitalWrite(BC1, HIGH);
    digitalWrite(BDIR, HIGH);

    // Latch address
    // NB: Addresses are all in range 0..15 so don't need to
    // worry about writing out bits 6,7 - just ensure set to zero
    PORTD = (PORTD & 0x03) | ((reg & 0xCF)<<2);
    PORTB = (PORTB & 0xFE);
    PORTC = (PORTC & 0xF7);

    // Mode = Inactive
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, LOW);

    delayMicroseconds(10);

    // Mode = Write
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, HIGH);

    // Write data
    PORTD = (PORTD & 0x03) | ((val & 0xCF)<<2); // Shift bits 0:5 to 2:7
    PORTB = (PORTB & 0xFE) | ((val & 0x40)>>6); // Shift bit 6 to 0
    PORTC = (PORTC & 0xF7) | ((val & 0x80)>>4); // Shift bit 7 to 3

    // Mode = Inactive
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, LOW);
    }

    I’m using the following mapping of data pins to Arduino digital IO pins to PORTS:

    DA0-DA5D2-D7PORTD Bits 0-5DA6D8PORT B Bit 0DA7A3/D17PORT C Bit 3

    To make this happen I have to ensure that the right bits are set to OUTPUTs and that BC2 is held HIGH prior to using the fastWrite function.

      digitalWrite(BC2, HIGH);
    DDRD |= 0xFC;
    DDRC |= 0x04;
    DDRB |= 0x01;

    This now improves on that previous 280Hz and gives me 1600Hz performance.

    So can I do any better? Well there are still between 6 and 8 calls to digitalWrite going on to handle the control signals…

    #define BC1LOW  {PORTC &= 0xFE;} // A0 LOW
    #define BC1HIGH {PORTC |= 0x01;} // A0 HIGH
    #define BC2LOW {PORTC &= 0xFD;} // A1 LOW
    #define BC2HIGH {PORTC |= 0x02;} // A1 HIGH
    #define BDIRLOW {PORTC &= 0xF7;} // A2 LOW
    #define BDIRHIGH {PORTC |= 0x04;} // A2 HIGH

    void ayFastWrite (byte reg, byte val) {
    // Mode=Addr Latch
    BC1HIGH;
    BDIRHIGH;

    // Latch address
    PORTD = (PORTD & 0x03) | ((reg & 0xCF)<<2);
    PORTB = (PORTB & 0xFE);
    PORTC = (PORTC & 0xF7);

    // Need 400nS Min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

    // Need 100nS settle then 50nS preamble
    delayMicroseconds(1);

    // Mode = Write
    BC1LOW;
    BDIRHIGH;

    // Write data
    PORTD = (PORTD & 0x03) | ((val & 0xCF)<<2); // Shift bits 0:5 to 2:7
    PORTB = (PORTB & 0xFE) | ((val & 0x40)>>6); // Shift bit 6 to 0
    PORTC = (PORTC & 0xF7) | ((val & 0x80)>>4); // Shift bit 7 to 3

    // Need 500nS min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

    // Need 100nS min
    }

    The timings come from the AY-3-8910 datasheet:

    The actual minimum and maximum timings for the various “t” values are given in the preceeding table. Most have a minimum value, but tBD has to be noted: the “associative delay time” is 50nS. This means that any changing of BC1, BC2 and BDIR has to occur within 50nS to be considered part of the same action.

    There is no means of having a nano-second delay (well, other than just spinning code), so I’ve just used a delayMicroseconds(1) here and there. This isn’t reliably accurate on an Arduino, but as I’m have delays of around half of that as a maximum it seems to be fine.

    This now gives me the following:

    This is now supporting a natural “as fast as possible” frequency of around 24kHz, meaning each call to the write function is now around 3uS. That is almost a 100x improvement over using all those pinMode and digitalWrite calls.

    The downside of this method:

    • It is ATMega328 specific.
    • It is specific to the pin mappings and PORT usage of this PCB.
    • It does not support reading or other chip operations between the writes.

    It is also interesting to see that the traces also show the high frequency oscillation (62.5kHz) that is being modulated regardless of the channel frequency and enable settings.

    DDS Part 2

    Success! At least with a single channel. This is now playing a pretty well in tune 440Hz A.

    Notice how the frequency of the timing pin is now ~4.2kHz meaning that the ISR is now indeed firing at the required 8192 Hz.

    Here is a close-up of the output signal. The oscilloscope was struggling to get a clean frequency reading, but this is one time I caught it reading something close! I checked the sound itself with a tuning fork (see video). It is indeed 440Hz.

    Find it on GitHub here.

    Closing Thoughts

    I wanted to get something put together to allow me to drive a DSS wavetable over MIDI, with different waveforms, and so on, but it turned out to be a little more involved getting this far than I anticipated, so I’ll leave it here for now.

    But hopefully filling in the gaps won’t take too long and will be the subject of a further post.

    Now that I have something that works, I’m actually quite surprised by how well it is working.

    Kevin

    #arduinoNano #ay38910 #dds #define #directDigitalSynthesis #include #midi

  24. Arduino and AY-3-8910 – Part 3

    I suggested in Part 2 that it might be possible to do some simple modulation of the amplitude of the AY-3-8910 channels rather than drive frequencies directly. This is taking a look at the possibilities of some kind of lo-fi direct digital synthesis using that as a basis.

    https://makertube.net/w/uCSiBG5RBufGqspoHMYFPt

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    These are the key tutorials for the main concepts used in this project:

    If you are new to Arduino, see the Getting Started pages.

    Parts list

    • Arduino Uno.
    • AY-3-8910 chip.
    • Either GadgetReboot’s PCB or patch using solderless breadboard or prototyping boards.
    • 5V compatible MIDI interface.
    • Jumper wires.

    Direct Digital Synthesis on the AY-3-8910

    I’ve talked about direct digital synthesis before, so won’t go into full detail again. For more, see Arduino R2R Digital Audio – Part 3 and Arduino PWM Sound Output.

    But the top-level idea is to set the level of the signal according to a value in a wavetable. If this value is updated at a useful audio rate then it will be interpreted as sound.

    There are some pretty major limitations with attempting to do this on the AY-3-8910 however. The biggest one being that there are only 15 levels for the output on each channel.

    So I’ll be working to the following properties:

    • 4-bit resolution for the output.
    • 8-bit wavetable.
    • 8.8 fixed point accumulator to index into the wavetable.
    • 8096 Hz sample rate.

    YouTuber https://www.youtube.com/@inazumadenki5588 had a look at this and showed that the AY-3-8910 needs to be set up as follows:

    • Frequency value for the channel should be set to the highest frequency possible.
    • All channels should be disabled.

    This is due to comments in the datasheet stating that the only way to fully disable a channel is to have 0 in the amplitude field.

    Note: for a 8192 sample rate, that means writing out a sample to the AY-3-8910 registers approximately once every 124uS. With a 256 value wavetable, it takes almost 32 mS to write a complete cycle at the native sample rate, which would be around a 30 Hz output.

    I’m not sure what the largest increment that would still give a useful signal might be, but say it was 8 values from the wavetable, then that would make the highest frequency supported around 1kHz. Not great, but certainly audible, so worth a try.

    Setting up for DDS

    I want a regular, reliable, periodic routine to output the levels from the wavetable, and the usual way to achieve this is using a timer and interrupt. As Timer 1 is already in use to generate the 1MHz clock for the AY-3-8910, I’m going to be configuring Timer 2 as follows:

    • Timer 2 is an 8-bit timer.
    • Use prescalar of 32 which gives a 500kHz clock source (16MHz/32).
    • Use CTC (clear timer on compare) mode.
    • Generate a compare match interrupt.
    • Do not enable any output pins.

    The appropriate ATMega328 registers to enable this are:

      // COM2A[1:0] = 00  No output
    // WGM2[2:0] = 010 CTC mode
    // CS2[2:0] = 011 Prescalar=32
    ASSR = 0;
    TCCR2A = _BV(WGM21);
    TCCR2B = _BV(CS21) | _BV(CS20);
    TCNT2 = 0;
    OCR2A = 60;
    TIMSK2 = _BV(OCIE2A);

    Although it is worth noting that enabling OC1A can be quite useful for debugging. The following toggles the OC2A output (on D11) every time there is a compare match. The frequency seen on D11 will thus be half the anticipated sample frequency.

    pinMode(11, OUTPUT);
    TCCR2A |= _BV(COM2A0); // COM2A[1:0] = 01 for OC2A toggle

    And this does indeed generate a signal. Here is a trace showing a timing GPIO pin and the AY-3-8910 output.

    The problem is that this is meant to be a 440Hz sine wave, and whilst the shape isn’t too bad (it is a little distorted as the amplitude isn’t a true linear shape), the frequency is much nearer 100Hz than 440.

    Analysis of Performance

    The clue is the other trace, which is a timing pin being toggled every time the Interrupt routine is called. This is showing a 1kHz frequency, which means the IRS is being called with a 2kHz frequency rather than the anticipated 8192Hz. Curiously though I am getting an accurate 4kHz toggle on the timer output pin OC1A indicating the timer is correctly counting with a 8kHz frequency.

    No matter how I configured things, the interrupt routine just would not do anything at a faster rate. I had to drop the frequency right down to 2kHz to get the output pin and interrupt routing running together. This means that something in the interrupt routine seems to be taking ~ 450uS to run.

    After a fair bit of prodding and probing and checking the ATMega328 datasheet and double checking the register values, I have to conclude that the AY3891x library is just too slow at updating the registers for it to be able to run from the interrupt routine at this speed.

    Taking a look at the register write() function in the library, which I need to use to update the channel level, I can see the following is happening:

    void AY3891x::write(byte regAddr, byte data) {
    latchAddressMode(regAddr);
    daPinsOutput(data);
    noInterrupts();
    mode010to110();
    mode110to010();
    interrupts();
    daPinsInput();
    }

    void AY3891x::latchAddressMode(byte regAddr) {
    mode010to000();
    daPinsOutput(_chipAddress | regAddr); // Register address is 4 lsb
    mode000to001();
    mode001to000();
    mode000to010();
    }

    void AY3891x::daPinsOutput(byte data) {
    byte i;

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) pinMode(_DA_pin[i], OUTPUT);
    }

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) {
    digitalWrite(_DA_pin[i], data & 0x01);
    data = data >> 1;
    }
    }
    }

    void AY3891x::daPinsInput() {
    byte i;

    for (i = 0; i < NUM_DA_LINES; i++) {
    if (_DA_pin[i] != NO_PIN) pinMode(_DA_pin[i], INPUT);
    }
    }

    And every one of those modeXXXtoYYY() functions is a call to digitalWrite(), so I make that 22 calls to ditigalWrite() in order to write a single register value, plus around 16 calls to pinMode(). There are also 5 loops each looping over 8 values.

    One person measured the Arduino Uno digitalWrite() function and concluded that it takes 3.4uS to run, so that is a minimum of 75uS of processing in every run through the interrupt routine just for those calls alone. That doesn’t include the calls and other logic going on. It could easily be more than twice that when everything is taken into account.

    Dropping in some temporary pin IO either side of the call to the AY write function itself, and I’m measuring just over 250uS for the register update to happen, and that is just for one channel. This means that anything with a period of that or faster is starving the processor from running at all.

    Measuring the Basic Performance

    At this point I took a step back and created a free-running test sketch to really see what is going on.

    #include "AY3891x.h"

    AY3891x psg( 17, 8, 7, 6, 5, 4, 3, 2, 16, 15, 14);

    #define AY_CLOCK 9 // D9
    void aySetup () {
    pinMode(AY_CLOCK, OUTPUT);
    digitalWrite(AY_CLOCK, LOW);

    TCCR1A = (1 << COM1A0);
    TCCR1B = (1 << WGM12) | (1 << CS10);
    TCCR1C = 0;
    TIMSK1 = 0;
    OCR1AH = 0;
    OCR1AL = 7; // 16MHz / 8 = 2MHz Counter

    psg.begin();

    // Output highest frequency on each channel, but set level to 0
    // Highest freq = 1000000 / (16 * 1) = 62500
    psg.write(AY3891x::ChA_Amplitude, 0);
    psg.write(AY3891x::ChA_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChA_Tone_Period_Fine_Reg, 0);
    psg.write(AY3891x::ChB_Amplitude, 0);
    psg.write(AY3891x::ChB_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChB_Tone_Period_Fine_Reg, 0);
    psg.write(AY3891x::ChC_Amplitude, 0);
    psg.write(AY3891x::ChC_Tone_Period_Coarse_Reg, 0);
    psg.write(AY3891x::ChC_Tone_Period_Fine_Reg, 0);

    // LOW = channel is in the mix.
    // Turn everything off..
    psg.write(AY3891x::Enable_Reg, 0xFF);
    }

    int toggle;
    void setup() {
    pinMode(11, OUTPUT);
    toggle = LOW;
    digitalWrite(11, toggle);
    aySetup();
    }

    void loop() {
    toggle = !toggle;
    digitalWrite(11, toggle);
    for (int i=0; i<16; i++) {
    psg.write(AY3891x::ChA_Amplitude, i);
    }
    }

    All this is doing is continually writing 0 to 15 to the channel A level register whilst toggling a GPIO pin. Putting an oscilloscope trace on the IO pin and the AY-3-8910 channel A output gives me the following:

    This is running with a period of 6.96mS, meaning each cycle of 16 writes takes 3.5mS, giving me almost 220uS per call to the AY write function which seems to align pretty well with what I was seeing before.

    And this is generating an audible tone at around 280Hz, so regardless of any timer settings or waveform processing, this is going to be the baseline frequency on which everything else would have to rest, which isn’t great.

    Optimising Register Writes

    So at this point I have the choice of attempting to write to the AY-3-8910 myself using PORT IO to eliminate the time it takes for all those loops and digitalWrite() calls. Or I could try some alternative libraries.

    The library I’m using aims for the most portable compatibility: “This library uses the generic digitalWrite() function instead of direct port manipulation, and should therefore work across most, if not all, processors supported by Arduino, so long as enough I/O pins are available for the interface to the PSG.”

    It is a deliberate design choice, but does require all three bus control signals to be used: BDIR, BC1, BC2.

    Alternatives are possible with less pin state changes, but much stricter timing requirements. Some options include:

    The following are projects that have not used a library, but just done their own thing:

    Unfortunately none of these really solves the problem as the PCB I’m using does not neatly map onto IO ports to allow the use of direct PORT IO for the data.

    So to improve things whilst using this same PCB will require me to re-write the library myself.

    As a test however, it is possible to take the IO pin definitions used with the PCB and write a bespoke, optimised register write routine as follows:

    void ayFastWrite (byte reg, byte val) {
    // Mode=Addr Latch
    digitalWrite(BC1, HIGH);
    digitalWrite(BDIR, HIGH);

    // Latch address
    // NB: Addresses are all in range 0..15 so don't need to
    // worry about writing out bits 6,7 - just ensure set to zero
    PORTD = (PORTD & 0x03) | ((reg & 0xCF)<<2);
    PORTB = (PORTB & 0xFE);
    PORTC = (PORTC & 0xF7);

    // Mode = Inactive
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, LOW);

    delayMicroseconds(10);

    // Mode = Write
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, HIGH);

    // Write data
    PORTD = (PORTD & 0x03) | ((val & 0xCF)<<2); // Shift bits 0:5 to 2:7
    PORTB = (PORTB & 0xFE) | ((val & 0x40)>>6); // Shift bit 6 to 0
    PORTC = (PORTC & 0xF7) | ((val & 0x80)>>4); // Shift bit 7 to 3

    // Mode = Inactive
    digitalWrite(BC1, LOW);
    digitalWrite(BDIR, LOW);
    }

    I’m using the following mapping of data pins to Arduino digital IO pins to PORTS:

    DA0-DA5D2-D7PORTD Bits 0-5DA6D8PORT B Bit 0DA7A3/D17PORT C Bit 3

    To make this happen I have to ensure that the right bits are set to OUTPUTs and that BC2 is held HIGH prior to using the fastWrite function.

      digitalWrite(BC2, HIGH);
    DDRD |= 0xFC;
    DDRC |= 0x04;
    DDRB |= 0x01;

    This now improves on that previous 280Hz and gives me 1600Hz performance.

    So can I do any better? Well there are still between 6 and 8 calls to digitalWrite going on to handle the control signals…

    #define BC1LOW  {PORTC &= 0xFE;} // A0 LOW
    #define BC1HIGH {PORTC |= 0x01;} // A0 HIGH
    #define BC2LOW {PORTC &= 0xFD;} // A1 LOW
    #define BC2HIGH {PORTC |= 0x02;} // A1 HIGH
    #define BDIRLOW {PORTC &= 0xF7;} // A2 LOW
    #define BDIRHIGH {PORTC |= 0x04;} // A2 HIGH

    void ayFastWrite (byte reg, byte val) {
    // Mode=Addr Latch
    BC1HIGH;
    BDIRHIGH;

    // Latch address
    PORTD = (PORTD & 0x03) | ((reg & 0xCF)<<2);
    PORTB = (PORTB & 0xFE);
    PORTC = (PORTC & 0xF7);

    // Need 400nS Min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

    // Need 100nS settle then 50nS preamble
    delayMicroseconds(1);

    // Mode = Write
    BC1LOW;
    BDIRHIGH;

    // Write data
    PORTD = (PORTD & 0x03) | ((val & 0xCF)<<2); // Shift bits 0:5 to 2:7
    PORTB = (PORTB & 0xFE) | ((val & 0x40)>>6); // Shift bit 6 to 0
    PORTC = (PORTC & 0xF7) | ((val & 0x80)>>4); // Shift bit 7 to 3

    // Need 500nS min
    delayMicroseconds(1);

    // Mode = Inactive
    BC1LOW;
    BDIRLOW;

    // Need 100nS min
    }

    The timings come from the AY-3-8910 datasheet:

    The actual minimum and maximum timings for the various “t” values are given in the preceeding table. Most have a minimum value, but tBD has to be noted: the “associative delay time” is 50nS. This means that any changing of BC1, BC2 and BDIR has to occur within 50nS to be considered part of the same action.

    There is no means of having a nano-second delay (well, other than just spinning code), so I’ve just used a delayMicroseconds(1) here and there. This isn’t reliably accurate on an Arduino, but as I’m have delays of around half of that as a maximum it seems to be fine.

    This now gives me the following:

    This is now supporting a natural “as fast as possible” frequency of around 24kHz, meaning each call to the write function is now around 3uS. That is almost a 100x improvement over using all those pinMode and digitalWrite calls.

    The downside of this method:

    • It is ATMega328 specific.
    • It is specific to the pin mappings and PORT usage of this PCB.
    • It does not support reading or other chip operations between the writes.

    It is also interesting to see that the traces also show the high frequency oscillation (62.5kHz) that is being modulated regardless of the channel frequency and enable settings.

    DDS Part 2

    Success! At least with a single channel. This is now playing a pretty well in tune 440Hz A.

    Notice how the frequency of the timing pin is now ~4.2kHz meaning that the ISR is now indeed firing at the required 8192 Hz.

    Here is a close-up of the output signal. The oscilloscope was struggling to get a clean frequency reading, but this is one time I caught it reading something close! I checked the sound itself with a tuning fork (see video). It is indeed 440Hz.

    Find it on GitHub here.

    Closing Thoughts

    I wanted to get something put together to allow me to drive a DSS wavetable over MIDI, with different waveforms, and so on, but it turned out to be a little more involved getting this far than I anticipated, so I’ll leave it here for now.

    But hopefully filling in the gaps won’t take too long and will be the subject of a further post.

    Now that I have something that works, I’m actually quite surprised by how well it is working.

    Kevin

    #arduinoNano #ay38910 #dds #define #directDigitalSynthesis #include #midi

  25. Atari 2600 Controller Shield PCB Revisited – Part 3

    Following on from Atari 2600 Controller Shield PCB Revisited – Part 2 someone on Mastodon made the point that the reason they tended to use RC circuits to read paddles “back in the day” was due to the expense of ADCs.

    Which triggered a bit of an “oh yeah” moment.

    The whole point was not to worry about the analog levels at all, and just measure the time it takes for the pin to read HIGH again.

    So this looks back at removing the whole ADC thing with a simple “if (digitalRead(pin))” condition!

    Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

    If you are new to Arduino, see the Getting Started pages.

    The Code

    The overarching principles are the same as for Atari 2600 Controller Shield PCB Revisited – Part 2 but instead of all the bespoke code to read the analog to digital converter, I’m relying on the following:

    • A digital input pin has a threshold for which the input is considered HIGH.
    • We can wait for the input reading to register as HIGH instead of looking for absolute thresholds of an analog value.
    • For an ATMega328P the threshold is 0.6 x VCC or around 3V. This is equivalent to just over 610 on a 0 to 1023 scale of an equivalent analog reading.

    Taking this into account and using largely the same ideas as before, I can reuse most of the code but with the following timing and threshold values instead:

    • Start scaling (the 0 point): 10
    • End scaling (the 1023 point): 350

    The timer TICK is still 100uS and the “breakout” point is still 1000.

    When it comes to reading the digital INPUT, I’m using PORT IO once again for speed and expediency.

    for (int i=0; i<4; i++) {
    if ((PINC & (1<<i)) == 0) {
    // Still not HIGH yet
    }
    }

    Here is the complete, now greatly simplified, basic code:

    #include <TimerOne.h>

    #define RAW_START 10
    #define RAW_END 350
    #define RAW_BREAK 1000
    #define RAW_TICK 100

    unsigned padState;
    unsigned padCount[4];
    unsigned atariValue[4];

    void atariAnalogSetup() {
    Timer1.initialize(RAW_TICK);
    Timer1.attachInterrupt(atariAnalogScan);
    padState = 0;
    }

    void atariAnalogScan (void) {
    if (padState == 0) {
    DDRC = DDRC | 0x0F; // A0-A3 set to OUTPUT
    PORTC = PORTC & ~(0x0F); // A0-A3 set to LOW (0)
    padState++;
    } else if (padState == 1) {
    DDRC = DDRC & ~(0x0F); // A0-A3 set to INPUT
    for (int i=0; i<4; i++) {
    padCount[i] = 0;
    }
    padState++;
    } else if (padState > RAW_BREAK) {
    for (int i=0; i<4; i++) {
    atariValue[i] = 1023 - map(constrain(padCount[i],RAW_START,RAW_END),RAW_START,RAW_END,0,1023);
    }
    padState = 0;
    } else {
    for (int i=0; i<4; i++) {
    if ((PINC & (1<<i)) == 0) {
    padCount[i]++;
    }
    }
    padState++;
    }
    }

    int atariAnalogRead (int pin) {
    return atariValue[pin-A0];
    }

    void setup() {
    Serial.begin(9600);
    atariAnalogSetup();
    }

    void loop() {
    Serial.print(padState);
    Serial.print("\t[ ");
    for (int i=0; i<4; i++) {
    Serial.print(atariAnalogRead(A0+i));
    Serial.print("\t");
    Serial.print(padCount[i]);
    Serial.print("\t][ ");
    }
    Serial.print("\n");
    }

    Closing Thoughts

    Sometimes one really can’t see the “wood for the trees” and this was one of those occasions. I was so took up with thinking about how a modern system might think about a problem without thinking about the original reason for the particular solution.

    It makes so much more sense thinking about it in these terms now. All it took was an observation from another, namely:

    “So I know the RC timer is the classic way to sense analog paddles but they also didn’t have cheap ADCs back then.”

    Many thanks “Chip” for that observation 🙂

    Kevin

    #arduinoUno #atari #atari2600 #include #potentiometer #TICKs

  26. Still whiling away time on fooling around with the Yamaha Clainova CVP-50 I found in a neighbors trash. Quickly found I can have a rather simple piece of music play by essentially setting up a sequencer, selecting a rhythm to go along with it #, #and #riffing #along, #adding #flourishes #and #random #effects. #Effects #include #5-6 #revedb #settings, #a #chorus #setting #and #quite #a #few #other #selections. #And #besides #the #rhythm #settings #builtin #in, #you # #can #build #your #own #presets #and #record #them #to #as #far #as #I #can #the, #the #floppy #drive #in #the #control #panel. #Yeah, #floppy #drive. #After #all #the #CVP-50 #came #out #in #1989.

  27. Still whiling away time on fooling around with the Yamaha Clainova CVP-50 I found in a neighbors trash. Quickly found I can have a rather simple piece of music play by essentially setting up a sequencer, selecting a rhythm to go along with it #, #and #riffing #along, #adding #flourishes #and #random #effects. #Effects #include #5-6 #revedb #settings, #a #chorus #setting #and #quite #a #few #other #selections. #And #besides #the #rhythm #settings #builtin #in, #you # #can #build #your #own #presets #and #record #them #to #as #far #as #I #can #the, #the #floppy #drive #in #the #control #panel. #Yeah, #floppy #drive. #After #all #the #CVP-50 #came #out #in #1989.

  28. Still whiling away time on fooling around with the Yamaha Clainova CVP-50 I found in a neighbors trash. Quickly found I can have a rather simple piece of music play by essentially setting up a sequencer, selecting a rhythm to go along with it #, #and #riffing #along, #adding #flourishes #and #random #effects. #Effects #include #5-6 #revedb #settings, #a #chorus #setting #and #quite #a #few #other #selections. #And #besides #the #rhythm #settings #builtin #in, #you # #can #build #your #own #presets #and #record #them #to #as #far #as #I #can #the, #the #floppy #drive #in #the #control #panel. #Yeah, #floppy #drive. #After #all #the #CVP-50 #came #out #in #1989.