减少 MIDI GUI 中的延迟

发布于 2025-01-20 02:58:21 字数 2473 浏览 1 评论 0原文

我正在尝试使用 mido 和 PySimpleGUI 创建一个简单的 MIDI 显示。我让它工作得很好,但我希望减少 MIDI 控制器(即 MIDI 键盘)和界面显示之间的延迟。特别是,一旦音符弹奏得相对较快,显示就会开始滞后,甚至在我放慢速度并继续以较慢的速度弹奏之后。只有当我关闭 GUI 并重新启动它时,延迟才会消失。我无法准确判断问题是否出在 mido、PySimpleGUI 或我的实现中的其他内容上,但由于实际发出的声音没有任何延迟,并且当我单独使用 mido 时似乎没有延迟(即只是将笔记打印到 Jupyter 笔记本上),我的钱花在 PySimpleGUI 上,或者我的低效代码是罪魁祸首。

为了这篇文章的目的,我尝试将我的实现简化为尽可能简单的术语,这只是一个脚本,它使在 MIDI 控制器上按下的音符触发在计算机键盘上按下的“c”键,使用 pynput (这是一个奇怪的解决方法,因为据我所知,您无法通过 MIDI 控制器直接触发 PySimpleGUI 事件),以及显示正在演奏的音符的音高值的基本 PySimpleGUI 界面。

下面是我在单独的笔记本中异步运行的 MIDI 脚本:


from pynput.keyboard import Key, Controller

def trigger():

    keyboard = Controller()
    key = "c"
        
    try:
        with mido.open_input(name='IAC Driver Mido Test') as port:
            for message in port:
                keyboard.press(key)
                keyboard.release(key)                
    except KeyboardInterrupt:
        pass

下面是用于读取 MIDI 数据的简化 PySimpleGUI 设置:


import PySimpleGUI as sg

with mido.open_input(name='IAC Driver Mido Test') as port:

    # Window Dimensions
    width = 1300
    height = 600

    # Arbitrary 'c' key linked to MIDI controller through pynput
    callbacks = ['c'] 

    canvas = [[sg.Canvas(size=(width, height), background_color='black', key= 'canvas')]]
    
    # Show the Window to the user
    window = sg.Window('MIDI Testing', canvas, size=(width, height), return_keyboard_events=True, use_default_focus=False)

    # Event loop. Read buttons, make callbacks
    while True:
        canvas = window['canvas']
        
        # Initialize note
        note = 0
        for msg in port.iter_pending():
            note_type = msg.type
            if note_type == 'note_on':
                note = msg.note
        
        # Read the Window
        event, value = window.read()

        # If a note is played
        if event in callbacks:        
            if note!=0:
                rect = canvas.TKCanvas.create_rectangle(0, 0, width, height)
                canvas.TKCanvas.itemconfig(rect, fill="Black")
                # Display the pitch value
                canvas.TKCanvas.create_text(width/2, height/2, text=str(note), fill="White", font=('Times', '24', 'bold'))

        # Close the window
        if event in (sg.WIN_CLOSED, 'Quit'):
            break

    window.close()

我很难找到关于这个问题的很多信息,因为它非常小众,但我想所有的如果有更先进的音乐软件具有低延迟 MIDI 显示(即 Ableton、GarageBand),可能有更好的方法来完成我在这里想要完成的任务。任何指示或批评将不胜感激!

I'm trying to create a simple MIDI display using mido and PySimpleGUI. I have it working decently well, but am hoping to reduce latency between the MIDI controller (i.e. a MIDI keyboard) and the interface display. Particularly, the display will begin to lag once notes are played relatively fast, and then even after I slow down and continue to play at a slower rate. The latency will then only go away if I close out of the GUI and re-launch it. I can't tell exactly if the issue is with mido, PySimpleGUI, or something else in my implementation, but since there isn't any latency in the actual sound coming out, and it appears there's no delay when I use mido in isolation (i.e. just printing notes to a Jupyter notebook), my money is on PySimpleGUI or my inefficient code being the culprit.

For the sake of this post I've tried to reduce my implementation to the simplest terms possible, which is just a script that makes a note being pressed on the MIDI controller trigger a 'c' key being pressed on the computer keyboard using pynput (this is a weird workaround because as far as I can tell you cannot directly trigger a PySimpleGUI event through a MIDI controller), as well as a basic PySimpleGUI interface that displays the pitch value of the note being played.

Below is the MIDI script which I run asynchronously in a separate notebook:


from pynput.keyboard import Key, Controller

def trigger():

    keyboard = Controller()
    key = "c"
        
    try:
        with mido.open_input(name='IAC Driver Mido Test') as port:
            for message in port:
                keyboard.press(key)
                keyboard.release(key)                
    except KeyboardInterrupt:
        pass

And below is the simplified PySimpleGUI setup to read MIDI data:


import PySimpleGUI as sg

with mido.open_input(name='IAC Driver Mido Test') as port:

    # Window Dimensions
    width = 1300
    height = 600

    # Arbitrary 'c' key linked to MIDI controller through pynput
    callbacks = ['c'] 

    canvas = [[sg.Canvas(size=(width, height), background_color='black', key= 'canvas')]]
    
    # Show the Window to the user
    window = sg.Window('MIDI Testing', canvas, size=(width, height), return_keyboard_events=True, use_default_focus=False)

    # Event loop. Read buttons, make callbacks
    while True:
        canvas = window['canvas']
        
        # Initialize note
        note = 0
        for msg in port.iter_pending():
            note_type = msg.type
            if note_type == 'note_on':
                note = msg.note
        
        # Read the Window
        event, value = window.read()

        # If a note is played
        if event in callbacks:        
            if note!=0:
                rect = canvas.TKCanvas.create_rectangle(0, 0, width, height)
                canvas.TKCanvas.itemconfig(rect, fill="Black")
                # Display the pitch value
                canvas.TKCanvas.create_text(width/2, height/2, text=str(note), fill="White", font=('Times', '24', 'bold'))

        # Close the window
        if event in (sg.WIN_CLOSED, 'Quit'):
            break

    window.close()

I've had trouble finding much info out there on this issue as it's pretty niche, but I imagine with all the much more advanced music software out there that have low latency MIDI displays (i.e. Ableton, GarageBand), there might be a better way to go about doing what I'm trying to accomplish here. Any pointers or critiques would be greatly appreciated!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

孤蝉 2025-01-27 02:58:21

对MIDO一无所知,但是这里可能

  • 不需要出错键盘事件,只需调用window.write_event_value生成事件。
  • 每次阅读事件之前,每次都会在事件循环中遍及port.iter_pending

在这里,用于监视MIDO的输入的多线程和调用write_event_value将事件生成事件循环以更新GUI。

不确定port.iter_pending是否会继续运行以监视MIDO的输入,因此添加了一段循环以保持其运行。 睡眠致电可能有助于减少CPU消耗,当然,延迟延迟10ms。

遵循尚未执行的代码,也许未能因错过或错误而运行。

from time import sleep
import threading
import PySimpleGUI as sg

def mido_thread(window):

    global running

    with mido.open_input(name='IAC Driver Mido Test') as port:
        while running:
            for msg in port.iter_pending():
                note_type = msg.type
                if note_type == 'note_on':
                    note = msg.note
                    if note != 0:
                        window.write_event_value('Note', note)
            sleep(0.01)
    window.write_event_value('Mido End', None)

width, height = size = (1300, 600)
layout = [
    [sg.Graph(size, (0, 0), size, background_color='black', key= '-Graph-')],
    [sg.Push(), sg.Button('Quit')],
]
window = sg.Window('MIDI Testing', layout, finalize=True,  enable_close_attempted_event=True)
graph = window['-Graph']
running, text = True, None
threading.Thread(target=mido_thread, args=(window, ), daemon=True).start()

while True:

    event, value = window.read()

    if event in (sg.WINDOW_CLOSE_ATTEMPTED_EVENT, 'Quit'):
        running = False

    elif event == 'Note':               # Update note from thread
        note = str(values[event])
        if text:
            graph.delete_figure(text)
        text = graph.draw_text(note, (width/2, height/2), color='white', font=('Times', '24', 'bold'))

    elif event == 'Mido End':           # wait thread end
        break

window.close()

Know nothing about mido, but something maybe wrong here

  • keyboard event is not necessary, just call window.write_event_value to generate event.
  • Iterate over port.iter_pending each time before you read event may got latency in your event loop.

Here, multithread used to monitor the input from mido and call write_event_value to generate event to event loop to update GUI.

Not sure if port.iter_pending will keep running to monitor the input of mido, so a while loop added to keep it running. A sleep call there maybe help to reduce CPU consumption, of course, there will be a 10ms delay.

Following code not yet executed, maybe failed to run for something missed or wrong.

from time import sleep
import threading
import PySimpleGUI as sg

def mido_thread(window):

    global running

    with mido.open_input(name='IAC Driver Mido Test') as port:
        while running:
            for msg in port.iter_pending():
                note_type = msg.type
                if note_type == 'note_on':
                    note = msg.note
                    if note != 0:
                        window.write_event_value('Note', note)
            sleep(0.01)
    window.write_event_value('Mido End', None)

width, height = size = (1300, 600)
layout = [
    [sg.Graph(size, (0, 0), size, background_color='black', key= '-Graph-')],
    [sg.Push(), sg.Button('Quit')],
]
window = sg.Window('MIDI Testing', layout, finalize=True,  enable_close_attempted_event=True)
graph = window['-Graph']
running, text = True, None
threading.Thread(target=mido_thread, args=(window, ), daemon=True).start()

while True:

    event, value = window.read()

    if event in (sg.WINDOW_CLOSE_ATTEMPTED_EVENT, 'Quit'):
        running = False

    elif event == 'Note':               # Update note from thread
        note = str(values[event])
        if text:
            graph.delete_figure(text)
        text = graph.draw_text(note, (width/2, height/2), color='white', font=('Times', '24', 'bold'))

    elif event == 'Mido End':           # wait thread end
        break

window.close()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文