You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jack MIDI消息无限重复发送求助:端口缓冲区异常输出

JACK MIDI 消息重复发送问题

问题描述

使用JACK MIDI时,端口缓冲区中的MIDI消息会被反复发送。调用Keyboard::dump_performance内的jack_midi_event_write能正常发送消息,但消息会无限重复——即使断开重连到其他客户端也无法停止,必须关闭自身客户端才行。程序无显式循环,但每条消息持续重复输出,已通过REAPER录制确认该问题,仅发送单条消息时现象依旧。

相关代码

const char* client_name {"Commander"};
jack_client_t* client {NULL};
jack_port_t* output_port {NULL};
jack_nframes_t local_nframes = {0};

int process(jack_nframes_t nframes, [[maybe_unused]]void* arg) {/*{{{*/
    local_nframes = nframes;
    return 0;
}/*}}}*/

void Keyboard::connect() noexcept{/*{{{*/
    // Open a connection to the JACK server
    client = jack_client_open(client_name, JackNullOption, NULL);
    if (client == NULL) {
        std::cerr << "Failed to open " << client_name << "client at Keyboard::connect()
";
        std::exit(EXIT_FAILURE);
    }

    // Create a MIDI output port
    output_port = jack_port_register(client, "output", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);
    if (output_port == NULL) {
        // Failed to create output port
        jack_client_close(client);
        std::cerr << "Failed to open " << client_name << "port at Keyboard::connect()
";
        std::exit(EXIT_FAILURE);
    }

    // Set the process callback function
    jack_set_process_callback(client, process, 0);

    // Activate the client
    if (jack_activate(client) != 0) {
        // Failed to activate client
        jack_port_unregister(client, output_port);
        jack_client_close(client);
        std::cerr << "Failed to activate " << client_name << "client at Keyboard::connect()
";
        std::exit(EXIT_FAILURE);
    }

    MIDI = Switch::ON;
}/*}}}*/

void Keyboard::dump_performance(const Performance& _Performance) noexcept/*{{{*/
{
    set_performance_buffer(_Performance); // Pass to the buffer

    // Sleep
    static struct timespec keyboardTimer;
    keyboardTimer.tv_sec = 0;
    keyboardTimer.tv_nsec = 200000000;

    // Change page
    [[maybe_unused]]jack_midi_data_t combiSysEx[7] = {0xF0, 0x42, 0x30, 0x7A, 0x4E, 0x00, 0xF7};
    [[maybe_unused]]jack_midi_data_t timbreSysEx[7] = {0xF0, 0x42, 0x30, 0x7A, 0x4E, 0x01, 0xF7};

    // MIDI Ch = msb[0], lsb[0], pc[0] are the same, only the last digit changes
    [[maybe_unused]]jack_midi_data_t msb[3] = {0xB0, 0x00, 0x3F};
    [[maybe_unused]]jack_midi_data_t lsb[3] = {0xB0, 0x20, 0x00};
    [[maybe_unused]]jack_midi_data_t pc[2] = {0xC0, 0x00};

    // Adjustment
    lsb[2] = performance_buffer.patch.bnk - 65; /*LSB*/
    pc[1] = performance_buffer.patch.num; /*PC*/

    // Retrieve the output port buffer
    void* port_buffer = jack_port_get_buffer(output_port, local_nframes);

    // Clear the port buffer before writing MIDI events
    jack_midi_clear_buffer(port_buffer);

    // Send the MIDI messages to the output port
    if (output_port != nullptr) {
        jack_midi_event_write(port_buffer, 0, combiSysEx,   sizeof(combiSysEx));
        jack_midi_event_write(port_buffer, 0, timbreSysEx,  sizeof(timbreSysEx));
        jack_midi_event_write(port_buffer, 0, msb,          sizeof(msb));
        jack_midi_event_write(port_buffer, 0, lsb,          sizeof(lsb));
        jack_midi_event_write(port_buffer, 0, pc,           sizeof(pc));
    }

    // Sleep
    nanosleep(&keyboardTimer, NULL);

    set_scene(performance_buffer.default_scene);
    //dump_scene();
}/*}}}*/

void Keyboard::disconnect() noexcept {/*{{{*/
    // Deactivate, unregister, and close the client
    jack_deactivate(client);
    jack_port_unregister(client, output_port);
    jack_client_close(client);

    // Set MIDI switch to OFF or perform any necessary cleanup
    MIDI = Switch::OFF;
}/*}}}*/

解决方案

核心问题:JACK的端口缓冲区会在每个处理周期被自动发送,你的代码仅在dump_performance中写入一次消息,但后续的process回调周期内没有清空或覆盖缓冲区内容,导致旧消息被持续重复发送。

修复步骤

  1. 将MIDI消息发送逻辑移至process回调:JACK的缓冲区属于当前处理周期,非回调时机操作缓冲区会导致数据残留到后续周期。
  2. 添加标志位控制单次发送:用布尔变量标记是否需要发送消息,仅在标记为true的周期写入数据,写完后重置标志避免重复。
  3. 移除阻塞函数:不要在JACK回调或业务逻辑中使用nanosleep,会导致JACK服务器卡顿,时序交由JACK处理周期控制。

修改后关键代码示例

// 添加成员变量控制消息发送(需保证线程安全,若跨线程调用dump_performance,建议用原子变量)
bool _needs_send = false;
Performance _pending_performance;

int process(jack_nframes_t nframes, [[maybe_unused]]void* arg) {
    void* port_buffer = jack_port_get_buffer(output_port, nframes);
    jack_midi_clear_buffer(port_buffer); // 每次周期先清空缓冲区

    if (_needs_send) {
        // 构建并发送MIDI消息
        jack_midi_data_t combiSysEx[7] = {0xF0, 0x42, 0x30, 0x7A, 0x4E, 0x00, 0xF7};
        jack_midi_data_t timbreSysEx[7] = {0xF0, 0x42, 0x30, 0x7A, 0x4E, 0x01, 0xF7};
        jack_midi_data_t msb[3] = {0xB0, 0x00, 0x3F};
        jack_midi_data_t lsb[3] = {0xB0, 0x20, 0x00};
        jack_midi_data_t pc[2] = {0xC0, 0x00};

        lsb[2] = _pending_performance.patch.bnk - 65;
        pc[1] = _pending_performance.patch.num;

        jack_midi_event_write(port_buffer, 0, combiSysEx,   sizeof(combiSysEx));
        jack_midi_event_write(port_buffer, 0, timbreSysEx,  sizeof(timbreSysEx));
        jack_midi_event_write(port_buffer, 0, msb,          sizeof(msb));
        jack_midi_event_write(port_buffer, 0, lsb,          sizeof(lsb));
        jack_midi_event_write(port_buffer, 0, pc,           sizeof(pc));

        _needs_send = false; // 发送完成后重置标志
        set_scene(_pending_performance.default_scene);
    }
    return 0;
}

void Keyboard::dump_performance(const Performance& _Performance) noexcept {
    _pending_performance = _Performance;
    _needs_send = true; // 标记需要发送消息
    // 移除原有的缓冲区操作和nanosleep
}

额外注意

  • 若dump_performance在非JACK实时线程调用,需确保_needs_send和_pending_performance的线程安全,可使用std::atomic<bool>或无锁队列传递消息。
  • 避免在process回调中执行耗时操作,防止JACK触发实时线程超时警告。

内容的提问来源于stack exchange,提问作者Juanca

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 16:45:20