mubunt
Anmeldungsdatum: 24. September 2009
Beiträge: 50
Wohnort: Freiburg
|
Hab eine Weile nach Möglichkeiten gesucht, MIDI-Eingaben auf normale Tastaturbefehle zu mappen. Für windo's gibts mindestens zwei Programme, die das machen: MIDIkey2key und Bone's MIDI.
Für Linux konnte ich nichts finden.
Hintergrund wäre, mit einem Midicontroller häufig verwendete Shortcuts anzusteuern (zB Gimp, libreCAD, libreoffice, hydrogen, Clementine....), was einigen Workflow enorm vereinfachen würde.
Hast jemand Infos zu diesem topic?
Wenn's so ein Tool noch gar nicht gibt, ist es schwierig zu programmieren? Könnte man es über eine config/mapping-Datei hinkriegen, die man aktivieren bzw deaktivieren kann?
Grüße
Milan
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11237
Wohnort: München
|
mubunt schrieb: Ich habe leider kein Midi-Gerät da, aber ich würde da einen Versuch mit einem python3-mido für das Lesen von der Midi-Schnittstelle und python3-uinput oder https://pyautogui.readthedocs.io/en/latest/ für die Ausgabe über eine virtuelle Tastatur machen. Als ersten Schritt würde ich folgendes probieren:
sudo apt install python3-mido
Und dann mal mit einem kleinen Skript test-midi.py schauen, welche Midi-Ports vorhanden sind und ob etwas passiert, wenn du Tasten auf dem Midi-Gerät drückst:
| #!/usr/bin/env python3
import mido
print(mido.get_input_names())
with mido.open_input() as inport:
for msg in inport:
print(msg)
|
Wenn die Datei ausführbar gemacht wurde, solltest du sie so im Terminal starten können:
|
mubunt
(Themenstarter)
Anmeldungsdatum: 24. September 2009
Beiträge: 50
Wohnort: Freiburg
|
Hallo seahawk, hab's gleich mal gemacht, hier die Ausgabe:
milan@rapimil:~/Schreibtisch/MIDI-key2key$ ./test-midi.py
Traceback (most recent call last):
File "./test-midi.py", line 4, in <module>
print(mido.get_input_names())
File "/usr/lib/python3/dist-packages/mido/backends/backend.py", line 169, in get_input_names
devices = self._get_devices(**self._add_api(kwargs))
File "/usr/lib/python3/dist-packages/mido/backends/backend.py", line 162, in _get_devices
if hasattr(self.module, 'get_devices'):
File "/usr/lib/python3/dist-packages/mido/backends/backend.py", line 42, in module
self.load()
File "/usr/lib/python3/dist-packages/mido/backends/backend.py", line 58, in load
self._module = importlib.import_module(self.name)
File "/usr/lib/python3.8/importlib/__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
File "<frozen importlib._bootstrap>", line 991, in _find_and_load
File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 783, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/usr/lib/python3/dist-packages/mido/backends/rtmidi.py", line 8, in <module>
import rtmidi
ModuleNotFoundError: No module named 'rtmidi'
Bin allerdings mit dem genauen Verstehen überfordert. Die Fehlermeldung kann ich nicht beheben, python3 ist installiert. Ich denke durch den Fehler beendet das Script, und ich bekomme für das Drücken der MIDI-Pads nichts zurück.
Ich hab' ein AKAI mpd218 in Verwendung und das funktioniert tadellos zB in Verbindung mit hydrogen. Ich konnte auch über die (Windos-Konfigurationssoftware) auf dem mpd218 die MIDI-Keys auf die voreingestellten Kanäle von hydrogen mappen und das funktioniert perfekt.
Aber ich nehme an, dein Script wäre der erste Schritt, um ein Keymapping zu Tastaturbefehlen hin umzusetzen.
Grüße! Milan
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11237
Wohnort: München
|
mubunt schrieb: Hallo seahawk, hab's gleich mal gemacht, hier die Ausgabe:
import rtmidi
ModuleNotFoundError: No module named 'rtmidi'
Dann fehlt da noch das Paket python3-rtmidi - komisch, dass das nicht als Abhängigkeit von python3-mido eingetragen ist.
Aber ich nehme an, dein Script wäre der erste Schritt, um ein Keymapping zu Tastaturbefehlen hin umzusetzen.
Genau, ich will sehen, was da für Daten ankommen, damit ich entscheiden kann, wie man sie am besten weiterverarbeiten kann.
|
mubunt
(Themenstarter)
Anmeldungsdatum: 24. September 2009
Beiträge: 50
Wohnort: Freiburg
|
Hallo seahawk
Ich war einige Zeit unterwegs, komme erst jetzt wieder an den Rechner.
Danke für deine Antwort, das nützt, jetzt komme ich zu dieser Ausgabe (wenn hydrogen auch läuft):
| milan@rapimil:~/Schreibtisch/MIDI-key2key$ ./test-midi.py
['Hydrogen:Hydrogen Midi-Out 128:1', 'MPD218:MPD218 MIDI 1 20:0', 'Midi Through:Midi Through Port-0 14:0']
|
- allerdings bewirken Midi-Eingaben nichts weiter, normale Tastatureingaben werden ausgegeben, auch die Benutzung von zB dead-acute.
Hier zB Bild ab, Komma AltGr+acute, dann c bzw C:
Hydrogen spricht aber normal auf die MIDI-Eingaben an.
Grüße!
Milan
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11237
Wohnort: München
|
Dann ist das erste Midi-Gerät in der Liste vermutlich nicht das richtige... - probier es mal so:
1
2
3
4
5
6
7
8
9
10
11
12
13 | #!/usr/bin/env python3
import mido
midi_devices = mido.get_input_names())
for midi_device in midi_devices:
try:
with mido.open_input(midi_device) as inport:
print(f"reading from {midi_device}:")
for msg in inport:
print(msg)
except KeyboardInterrupt:
continue
|
Wenn keine Tastendrücke vom Midi-Gerät sichtbar sind, kannst du STRG + c drücken, damit er das nächste probiert (bzw. das Skript beendet, wenn er alle durchprobiert hat).
|
mubunt
(Themenstarter)
Anmeldungsdatum: 24. September 2009
Beiträge: 50
Wohnort: Freiburg
|
@seahawk
Super!
Das klappt, genau wie du's beschrieben hast (Nachdem ich eine überflüssige Klammer rausgenommen habe, falls das jd nachvollzieht mido.get_input_names()) > mido.get_input_names() ).
Ausgabe zB:
1
2
3
4
5
6
7
8
9
10
11
12
13 | control_change channel=0 control=12 value=13 time=0
control_change channel=0 control=12 value=12 time=0
control_change channel=0 control=12 value=11 time=0
note_on channel=9 note=61 velocity=80 time=0
note_off channel=9 note=61 velocity=0 time=0
note_on channel=9 note=37 velocity=61 time=0
aftertouch channel=9 value=36 time=0
aftertouch channel=9 value=0 time=0
note_off channel=9 note=37 velocity=0 time=0
note_on channel=9 note=38 velocity=75 time=0
aftertouch channel=9 value=57 time=0
aftertouch channel=9 value=0 time=0
note_off channel=9 note=38 velocity=0 time=0
|
Allerdings verstehe ich zuwenig, um damit weiterzumachen. Würdest du mich weiter coachen, um zu einem Mapping auf Tastatur(-Shortcuts) zu kommen?
Ich würde ja gerne so Dinge wie Alt-Shift-G etc über das MIDI-Pad ansprechen können.
Ich denke, dass das für viele Anwendungen und Anwender*innen superpraktisch wäre - 16+ shortcuts auf dem MIDI-Controller beim Zeichnen, Text&Grafik bearbeiten, Musik machen...
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11237
Wohnort: München
|
Für welches Midi-Gerät war das jetzt (das sollte das Skript jeweils ausgeben) und welche Tasten hast du gedrückt? note_on und note_off sind das drücken bzw. loslassen einer Taste - es wäre für mich noch wichtig zu wissen, unter welchen Umständen die übrigen Events wie control_change und aftertouch gesendet werden und ob die beachtet werden sollen.
|
mubunt
(Themenstarter)
Anmeldungsdatum: 24. September 2009
Beiträge: 50
Wohnort: Freiburg
|
@seahawk -das akai mpd218 (siehe Anhang)
Hier die Ausgabe (erstes Gerät KompleteAudio6 geskipped):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 | ^Cmilan@rapimil:~/Schreibtisch/MIDI-key2key$ ./test-midi2.py
reading from Komplete Audio 6:Komplete Audio 6 MIDI 1 32:0:
^Creading from MPD218:MPD218 MIDI 1 20:0:
note_on channel=9 note=35 velocity=72 time=0
note_off channel=9 note=35 velocity=0 time=0
note_on channel=9 note=30 velocity=80 time=0
note_off channel=9 note=30 velocity=0 time=0
note_on channel=9 note=31 velocity=76 time=0
note_off channel=9 note=31 velocity=0 time=0
note_on channel=9 note=32 velocity=66 time=0
note_off channel=9 note=32 velocity=0 time=0
note_on channel=9 note=36 velocity=88 time=0
note_off channel=9 note=36 velocity=0 time=0
note_on channel=9 note=37 velocity=79 time=0
note_off channel=9 note=37 velocity=0 time=0
note_on channel=9 note=38 velocity=88 time=0
note_off channel=9 note=38 velocity=0 time=0
note_on channel=9 note=39 velocity=83 time=0
note_off channel=9 note=39 velocity=0 time=0
note_on channel=9 note=40 velocity=90 time=0
note_off channel=9 note=40 velocity=0 time=0
note_on channel=9 note=41 velocity=96 time=0
note_off channel=9 note=41 velocity=0 time=0
note_on channel=9 note=42 velocity=94 time=0
note_off channel=9 note=42 velocity=0 time=0
note_on channel=9 note=43 velocity=95 time=0
note_off channel=9 note=43 velocity=0 time=0
note_on channel=9 note=44 velocity=107 time=0
note_off channel=9 note=44 velocity=0 time=0
note_on channel=9 note=45 velocity=102 time=0
note_off channel=9 note=45 velocity=0 time=0
note_on channel=9 note=46 velocity=93 time=0
note_off channel=9 note=46 velocity=0 time=0
note_on channel=9 note=47 velocity=99 time=0
note_off channel=9 note=47 velocity=0 time=0
control_change channel=0 control=3 value=16 time=0
control_change channel=0 control=3 value=17 time=0
control_change channel=0 control=3 value=18 time=0
control_change channel=0 control=3 value=19 time=0
control_change channel=0 control=3 value=20 time=0
control_change channel=0 control=3 value=21 time=0
control_change channel=0 control=3 value=22 time=0
control_change channel=0 control=3 value=23 time=0
control_change channel=0 control=3 value=24 time=0
control_change channel=0 control=3 value=25 time=0
control_change channel=0 control=3 value=26 time=0
control_change channel=0 control=3 value=27 time=0
control_change channel=0 control=3 value=28 time=0
control_change channel=0 control=3 value=29 time=0
control_change channel=0 control=3 value=30 time=0
control_change channel=0 control=3 value=31 time=0
control_change channel=0 control=3 value=32 time=0
control_change channel=0 control=9 value=23 time=0
control_change channel=0 control=9 value=24 time=0
control_change channel=0 control=9 value=25 time=0
control_change channel=0 control=12 value=12 time=0
control_change channel=0 control=12 value=13 time=0
control_change channel=0 control=12 value=14 time=0
control_change channel=0 control=13 value=97 time=0
control_change channel=0 control=13 value=98 time=0
control_change channel=0 control=13 value=99 time=0
control_change channel=0 control=13 value=100 time=0
control_change channel=0 control=14 value=1 time=0
control_change channel=0 control=14 value=2 time=0
control_change channel=0 control=14 value=3 time=0
control_change channel=0 control=14 value=4 time=0
control_change channel=0 control=15 value=16 time=0
control_change channel=0 control=15 value=17 time=0
control_change channel=0 control=15 value=18 time=0
control_change channel=0 control=15 value=19 time=0
|
Ich hab einfach mal die Pads 1-16 durchgetippt, die geben "noteon2+"note=##"+"velocity=###"(Anschlagstärke)aus, das Gleiche nochmal beim Loslassen (noteoff+note+velocity=0). "noteoff" spielt sicher keine Rolle, das gibt eine normale Tastatur wohl auch nicht aus? [MIDI-Werte gehen wohl immer von 0-127.]
Den "note"-Wert kann man den pads in der akai-Software zuweisen.
Dann hab' ich die Control-Knöpfe 1-6 gedreht, die geben dann jede Menge Änderungen von "value" aus. Die braucht man wohl erst mal nicht.
PS beim längeren Drücken der Anschlagpads kommen "aftertouch"-Werte zustande, die, nehme ich ziemlich sicher an, auch nicht beachtet werden müssen:
| note_on channel=9 note=39 velocity=82 time=0
aftertouch channel=9 value=63 time=0
aftertouch channel=9 value=70 time=0
...
aftertouch channel=9 value=0 time=0
note_off channel=9 note=39 velocity=0 time=0
|
- Bilder
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11237
Wohnort: München
|
Ok, dann sollte es ja eigentlich genügen auf note_on Events zu achten und darauf hin einen Tastendruck auszulösen, oder? pyautogui kannst du so für deinen User installieren:
sudo apt install python3-pip python3-xlib python3-tk python3-dev
pip install --user pyautogui Dann würde ich in der ersten Aubaustufe mal sowas als midi-xkeys.py ablegen und ausführbar machen, das mit einer einzigen Keymap arbeitet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 | #!/usr/bin/env python3
import argparse
import os
import sys
import mido
import pyautogui
parser = argparse.ArgumentParser(prog='convert midi to xorg keypresses')
parser.add_argument("-l", "--list-midi-devices", action='store_true',
help="list names of midi input devices and exit")
parser.add_argument("-c", "--config-file", default=None,
help="config file to read keymap from")
args = parser.parse_args()
if args.list_midi_devices:
for device in mido.get_input_names():
print(device)
sys.exit()
keymap = {}
if args.config_file:
with open(args.config_file) as f:
for n, line in enumerate(f, start=1):
line = line.strip()
if line.startswith('#'):
continue
try:
channel, note, action, *keys = line.split()
except ValueError:
print(f"invalid line {n}: {line}", file=sys.stderr)
keymap[(channel, note)] = (action, keys)
with mido.open_input() as inport:
input_device = os.environ.get('MIDO_DEFAULT_INPUT')
print(
f"reading from {input_device if input_device else 'default device'}:")
for msg in inport:
if msg.type == 'note_on':
print(msg)
action, keys = keymap.get(
(note.channel, note.note), ("unmapped", []))
print(f"{action=} {keys=}")
if keys:
if action == 'press':
pyautogui.press(keys)
elif action == 'hotkey':
pyautogui.hotkey(*keys)
|
Das Format der Keymap könnte dann z.B. so aussehen - press sollte eine oder mehrere Tasten nacheinander drücken und hotkey Tastenkombinationen, also könnte man z.B. so eine keymap.cfg anlegen:
# channel note (press|hotkey) keys
9 35 hotkey ctrl c
9 36 hotkey ctrl v
9 37 press h a l l o , w e l t Dann solltest du das Programm so ausführen können:
MIDO_DEFAULT_INPUT='MPD218:MPD218 MIDI 1 20:0' ./midi-xkeys.py -c keymap.cfg Edit: Der Schalter für die keymap ist -c nicht, -d
|
mubunt
(Themenstarter)
Anmeldungsdatum: 24. September 2009
Beiträge: 50
Wohnort: Freiburg
|
ok, umgesetzt und getestet ACHTUNG | pip install --user pyautogui > pip3 install --user pyautogui
|
Scheint funktionieren zu wollen, endet aber mit Fehler:
| milan@rapimil:~/Schreibtisch/MIDI-key2key$ MIDO_DEFAULT_INPUT='MPD218:MPD218 MIDI 1 20:0' ./midi-xkeys.py -c keymap.cfg
reading from MPD218:MPD218 MIDI 1 20:0:
note_on channel=9 note=33 velocity=15 time=0
Traceback (most recent call last):
File "./midi-xkeys.py", line 44, in <module>
(note.channel, note.note), ("unmapped", []))
AttributeError: 'str' object has no attribute 'channel'
milan@rapimil:~/Schreibtisch/MIDI-key2key$
|
Eine Frage, die ich mir noch stelle: Wenn's funktioniert, erfolgt die Ausgabe ins aktive Fenster?
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11237
Wohnort: München
|
Ups, da bin ich bei den Variablennamen durcheinander gekommen, das sollte in Zeile 44 so aussehen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 | #!/usr/bin/env python3
import argparse
import os
import sys
import mido
import pyautogui
parser = argparse.ArgumentParser(prog='convert midi to xorg keypresses')
parser.add_argument("-l", "--list-midi-devices", action='store_true',
help="list names of midi input devices and exit")
parser.add_argument("-c", "--config-file", default=None,
help="config file to read keymap from")
args = parser.parse_args()
if args.list_midi_devices:
for device in mido.get_input_names():
print(device)
sys.exit()
keymap = {}
if args.config_file:
with open(args.config_file) as f:
for n, line in enumerate(f, start=1):
line = line.strip()
if line.startswith('#'):
continue
try:
channel, note, action, *keys = line.split()
except ValueError:
print(f"invalid line {n}: {line}", file=sys.stderr)
keymap[(channel, note)] = (action, keys)
with mido.open_input() as inport:
input_device = os.environ.get('MIDO_DEFAULT_INPUT')
print(
f"reading from {input_device if input_device else 'default device'}:")
for msg in inport:
if msg.type == 'note_on':
print(msg)
action, keys = keymap.get(
(msg.channel, msg.note), ("unmapped", []))
print(f"{action=} {keys=}")
if keys:
if action == 'press':
pyautogui.press(keys)
elif action == 'hotkey':
pyautogui.hotkey(*keys)
|
mubunt schrieb: Eine Frage, die ich mir noch stelle: Wenn's funktioniert, erfolgt die Ausgabe ins aktive Fenster?
Soweit ich das gelesehen habe ist das ein X-Key Event, das vom X-Server und den daran hängenden Clients wie Tastendrücke, die von einer Tastatur kommen verarbeitet werden. Damit sollten globale Hotkeys genauso funktionieren wie das Drücken von Tasten in Anwendungen.
|
mubunt
(Themenstarter)
Anmeldungsdatum: 24. September 2009
Beiträge: 50
Wohnort: Freiburg
|
Yep jetzt ist die Fehlermeldung weg, allerdings scheint kein mapping zu erfolgen - weder im Terminal, im Editor oder im LO-Writer erfolgt eine Aktion, im Terminal sieht's so aus:
| milan@rapimil:~/Schreibtisch/MIDI-key2key$ MIDO_DEFAULT_INPUT='MPD218:MPD218 MIDI 1 20:0' ./midi-xkeys.py -c keymap.cfg
reading from MPD218:MPD218 MIDI 1 20:0:
note_on channel=9 note=32 velocity=59 time=0
action='unmapped' keys=[]
note_on channel=9 note=31 velocity=42 time=0
action='unmapped' keys=[]
note_on channel=9 note=31 velocity=18 time=0
action='unmapped' keys=[]
|
Ich hab mal den Inhalt des Verzeichnisses, indem midi-xkeys.py und keymap.cfg liegen angehängt
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11237
Wohnort: München
|
mubunt schrieb: Ich hab mal den Inhalt des Verzeichnisses, indem midi-xkeys.py und keymap.cfg liegen angehängt
Der Anhang fehlt leider.
|
mubunt
(Themenstarter)
Anmeldungsdatum: 24. September 2009
Beiträge: 50
Wohnort: Freiburg
|
- MIDI-key2keybyseahawk.zip (2.7 KiB)
- Download MIDI-key2keybyseahawk.zip
|