From b37e43001c3499c9235a9ea049c09e265db4435d Mon Sep 17 00:00:00 2001 From: Gennadi Iosad Date: Fri, 10 Aug 2018 20:43:41 +0300 Subject: [PATCH 1/8] Rename xprint() to debug_log(). --- main.py | 4 ++-- midi_filter.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index 3bf352f..6b321f6 100644 --- a/main.py +++ b/main.py @@ -221,7 +221,7 @@ def setup_operations_frame(self): # Config. config_dir = appdirs.user_config_dir('midi-note-double-trigger-filter', '') config_path = os.path.join(config_dir, 'midi-note-double-trigger-filter.cfg') -xprint('Config at:', config_path) +debug_log('Config at:', config_path) config = configparser.ConfigParser() config.read(config_path) if 'general' not in config: config.add_section('general') @@ -246,4 +246,4 @@ def setup_operations_frame(self): os.makedirs(config_dir) config.write(open(config_path, 'w')) -xprint('EXIT') +debug_log('EXIT') diff --git a/midi_filter.py b/midi_filter.py index a35f0e0..8ebc215 100644 --- a/midi_filter.py +++ b/midi_filter.py @@ -13,7 +13,7 @@ import sys import time -def xprint(*args, **kwargs): +def debug_log(*args, **kwargs): t = time.time() timestamp = time.strftime('%H:%M:%S', time.localtime(t)) + '.{:03}'.format(int(t * 1000) % 1000) print(timestamp, *args, **kwargs) @@ -98,20 +98,20 @@ def handle_in(msg, useData): else: self.notes_on_events_skipped += 1 self.stats_updated() - xprint('Skipping note_on of note {}, delta {:.3f}, velocity={}'.format(msg[1], delta, msg[2])) + debug_log('Skipping note_on of note {}, delta {:.3f}, velocity={}'.format(msg[1], delta, msg[2])) self._oport.open_port(self._port_index_by_name(self._oport, oportname)) if not self._oport.is_port_open(): - xprint('Output MIDI device is not found') + debug_log('Output MIDI device is not found') return self._iport.set_callback(handle_in) self._iport.open_port(self._port_index_by_name(self._iport, iportname)) if not self._iport.is_port_open(): - xprint('Input MIDI device is not found') + debug_log('Input MIDI device is not found') return - xprint('Ports open') + debug_log('Ports open') self._is_running = True @@ -126,4 +126,4 @@ def stop(self): self._oport.close_port() self._oport = None - xprint('MIDI ports closed') + debug_log('MIDI ports closed') From d49c2872414439b381edad8323134e64b4b1436c Mon Sep 17 00:00:00 2001 From: Gennadi Iosad Date: Fri, 10 Aug 2018 20:43:59 +0300 Subject: [PATCH 2/8] Add python3 shebang. --- main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.py b/main.py index 6b321f6..53259d0 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + # Copyright (c) 2018 Gennadi Iosad. # All Rights Reserved. # You may use, distribute and modify this code under the From cfab98220bf0a51b7e19a9cb0b43fb9c55254802 Mon Sep 17 00:00:00 2001 From: Gennadi Iosad Date: Fri, 10 Aug 2018 20:45:06 +0300 Subject: [PATCH 3/8] Catch exception and silently ignore when device is not connected. --- main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 53259d0..9ec07c2 100644 --- a/main.py +++ b/main.py @@ -115,7 +115,10 @@ def update_option_menu(self, menu, choices, tkvar): def start(self): - self.midi_filter.start(self.iportname.get(), self.oportname.get()) + try: + self.midi_filter.start(self.iportname.get(), self.oportname.get()) + except OverflowError as e: + debug_log('Exception in midi_filter:', e) self.update_status() From e25c035b1b187d290335a9846ae4cd7a44369f89 Mon Sep 17 00:00:00 2001 From: Gennadi Iosad Date: Fri, 10 Aug 2018 20:59:33 +0300 Subject: [PATCH 4/8] main.py: get rid of global variables. --- main.py | 73 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/main.py b/main.py index 9ec07c2..7a7925e 100644 --- a/main.py +++ b/main.py @@ -24,17 +24,17 @@ def __init__(self, root, midi_filter, config): self.config = config # vars - self.iportname = tk.StringVar(window) - self.oportname = tk.StringVar(window) - self.status = tk.StringVar(window) - self.autostart = tk.IntVar(window) + self.iportname = tk.StringVar(self.root) + self.oportname = tk.StringVar(self.root) + self.status = tk.StringVar(self.root) + self.autostart = tk.IntVar(self.root) - self.filter1_enabled = tk.IntVar(window) - self.min_delay = tk.StringVar(window) - self.min_velocity = tk.StringVar(window) + self.filter1_enabled = tk.IntVar(self.root) + self.min_delay = tk.StringVar(self.root) + self.min_velocity = tk.StringVar(self.root) - self.notes_on_events_passed = tk.IntVar(window) - self.notes_on_events_skipped = tk.IntVar(window) + self.notes_on_events_passed = tk.IntVar(self.root) + self.notes_on_events_skipped = tk.IntVar(self.root) self.setup_devices_frame() self.setup_filter_frame() @@ -212,43 +212,46 @@ def setup_stats_frame(self): def setup_operations_frame(self): - operation_frame = tk.Frame(window) + operation_frame = tk.Frame(self.root) operation_frame.pack() start_btn = ttk.Button(operation_frame, textvariable = self.status, command = lambda:self.toggle_start_stop()) - start_btn.pack(pady=5, ipady=2) + start_btn.pack(pady=5, ipady=5) - autostart_chkbtn = ttk.Checkbutton(window, text='Autostart', variable = self.autostart) + autostart_chkbtn = ttk.Checkbutton(operation_frame, text='Autostart', variable = self.autostart) autostart_chkbtn.pack(pady=10) +def main(): + # Config. + config_dir = appdirs.user_config_dir('midi-note-double-trigger-filter', '') + config_path = os.path.join(config_dir, 'midi-note-double-trigger-filter.cfg') + debug_log('Config at:', config_path) + config = configparser.ConfigParser() + config.read(config_path) + if 'general' not in config: config.add_section('general') + if 'filter1' not in config: config.add_section('filter1') -# Config. -config_dir = appdirs.user_config_dir('midi-note-double-trigger-filter', '') -config_path = os.path.join(config_dir, 'midi-note-double-trigger-filter.cfg') -debug_log('Config at:', config_path) -config = configparser.ConfigParser() -config.read(config_path) -if 'general' not in config: config.add_section('general') -if 'filter1' not in config: config.add_section('filter1') + # Model and view + window = tk.Tk() + window.title("MIDI Note Double Trigger Filter") -# Model and view -window = tk.Tk() -window.title("MIDI Note Double Trigger Filter") + midi_filter = MIDIFilter() + view = DoubleTriggerFilterView(window, midi_filter, config) -midi_filter = MIDIFilter() -view = DoubleTriggerFilterView(window, midi_filter, config) + window.update() + window.minsize(window.winfo_width(), window.winfo_height()) + window.mainloop() -window.update() -window.minsize(window.winfo_width(), window.winfo_height()) -window.mainloop() + midi_filter.stats_updated_cb = None + view.update_config() -midi_filter.stats_updated_cb = None -view.update_config() + # Save config to file + if not os.path.exists(config_dir): + os.makedirs(config_dir) + config.write(open(config_path, 'w')) -# Save config to file -if not os.path.exists(config_dir): - os.makedirs(config_dir) -config.write(open(config_path, 'w')) + debug_log('EXIT') -debug_log('EXIT') + +main() From d2aa8fff57bbfd4b7d196fab5371869ae1d82d16 Mon Sep 17 00:00:00 2001 From: Gennadi Iosad Date: Fri, 10 Aug 2018 21:02:23 +0300 Subject: [PATCH 5/8] main.py: fix start button initial title absent if not clicked or auto-started. --- main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.py b/main.py index 7a7925e..8bf349d 100644 --- a/main.py +++ b/main.py @@ -76,6 +76,8 @@ def filter1_enabled(*args): if self.autostart.get(): self.start() + else: + self.update_status() def load_config(self): From b5ab414027ac4579e7222a1dfacb489b5d95dd95 Mon Sep 17 00:00:00 2001 From: Gennadi Iosad Date: Fri, 10 Aug 2018 21:10:07 +0300 Subject: [PATCH 6/8] main.py: make vertical spacing consistent for methods and nested functions. --- main.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/main.py b/main.py index 8bf349d..ede1105 100644 --- a/main.py +++ b/main.py @@ -44,7 +44,6 @@ def __init__(self, root, midi_filter, config): self.rescan() self.load_config() - def stats_updated_cb(): self.notes_on_events_passed.set(self.midi_filter.notes_on_events_passed) self.notes_on_events_skipped.set(self.midi_filter.notes_on_events_skipped) @@ -60,7 +59,6 @@ def set_min_velocity(*args): self.midi_filter.min_velocity = v self.min_velocity.trace('w', set_min_velocity) - def set_min_delay(*args): try: v = float(self.min_delay.get()) @@ -69,7 +67,6 @@ def set_min_delay(*args): self.midi_filter.min_delay = v self.min_delay.trace('w', set_min_delay) - def filter1_enabled(*args): self.midi_filter.enabled = self.filter1_enabled.get() self.filter1_enabled.trace('w', filter1_enabled) From 605583078e5305bc1e33be73d6bdc9518e3218ea Mon Sep 17 00:00:00 2001 From: Gennadi Iosad Date: Fri, 10 Aug 2018 21:16:22 +0300 Subject: [PATCH 7/8] main.py: fix accessing tkinter variables from rtmidi thread. Use event_generate() to wake main thread instead of directly updating tkinter variables. Not that I tried using after_idle() function but it doesn't wake the main thread reliably in macOs. --- main.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index ede1105..0cbbb14 100644 --- a/main.py +++ b/main.py @@ -44,12 +44,14 @@ def __init__(self, root, midi_filter, config): self.rescan() self.load_config() - def stats_updated_cb(): + def update_stats(): self.notes_on_events_passed.set(self.midi_filter.notes_on_events_passed) self.notes_on_events_skipped.set(self.midi_filter.notes_on_events_skipped) - self.midi_filter.stats_updated_cb = stats_updated_cb - stats_updated_cb() + REFRESH_GUI_EVENT = '<>' + self.root.bind(REFRESH_GUI_EVENT, lambda event: update_stats()) + self.midi_filter.stats_updated_cb = lambda: self.root.event_generate(REFRESH_GUI_EVENT, when="tail") + self.midi_filter.stats_updated_cb() def set_min_velocity(*args): try: From f727a091e80e3b2f1dc91e2eaabf0da162d09c83 Mon Sep 17 00:00:00 2001 From: Gennadi Iosad Date: Fri, 10 Aug 2018 21:41:32 +0300 Subject: [PATCH 8/8] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9fb2073..c7922de 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # [midi-double-trigger-filter](https://github.com/giosad/midi-double-trigger-filter) Processes MIDI events from a controller to filter note-on events caused by unintended double triggering. -Confirmed to work on Win10 with MPD218. +Confirmed to work with MPD218 in Win10 and macOs. +## Screenshot +[![midi-note-double-trigger-filter.png](https://s33.postimg.cc/p3emgfmnz/midi-note-double-trigger-filter.png)](https://postimg.cc/image/yny93bbzv/) ## Requirements For MIDI filtering you need : - Virtual midi device