linux-midi-latch 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ *.egg-info
2
+ dist
3
+ __pycache__
4
+ token
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 @readwith
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: linux-midi-latch
3
+ Version: 1.0.0
4
+ Summary: MIDI note latch for Linux using mididings
5
+ Project-URL: Homepage, https://github.com/talwrii/linux-midi-latch
6
+ Project-URL: Repository, https://github.com/talwrii/linux-midi-latch
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: latch,linux,midi,mididings,music
10
+ Classifier: Environment :: Console
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Multimedia :: Sound/Audio :: MIDI
15
+ Requires-Python: >=3.8
16
+ Requires-Dist: mido
17
+ Requires-Dist: python-rtmidi
18
+ Description-Content-Type: text/markdown
19
+
20
+ # linux midi latch
21
+ Implement latch functionality where each key acts as a toggle for a note for midi devices on linux. Optionally switch this functionality on and off with the systain pedal. Uses mido, only works on linux
22
+
23
+ ## Alternatives and prior work
24
+ Some hardware implements latching. Apparently Nord Lead A1, Sequential Prophet 6 and Novation Summit amongt others.
25
+
26
+ mido gives you complete control over what midi doers so you can do what you want if you are willing to put in the work - which has become fairly cheap with LLMs. Apparently, the VST Bidule has a midi latch feature as does VCV rack. These will work on windows. `loopMIDI` combined with mido apparently gives you programmatic contorl on windows. I have not tried most of thse things.
27
+
28
+ ## Installation
29
+ pipx install linux-midi-latch
30
+
31
+ ## Usage
32
+ `linux-midi-latch --alsa`
33
+
34
+ Use `aconnect` to route midi traffic through linux-midi-latch.
35
+
36
+ There are --jack and --osc options. I have not tested them nor do I know how to use them, but mididings works with jthem.
37
+
38
+ ## My setup
39
+ I am using this with an impact gxp6 nektar on Ubuntu LTS with a yamaha sustain pedel using alsa midi and fluidsynth.
40
+
41
+ ## About me
42
+ I am @readwith. I make tools for reading and agency and research - with and without Obsidian and AI.
43
+ You can follow me https::/readwithai.substack.com/ and https://x.com/readwithai
44
+
45
+ I also mess around with music and fitness technology. I have a treadmill desk with a lot of musical instruments. See [r/musicaltreadmilldesk](https://www.reddit.com/r/musicaltreadmilldesk/) for this. If you like this sort of tool you may well want to follow me there.
46
+
@@ -0,0 +1,27 @@
1
+ # linux midi latch
2
+ Implement latch functionality where each key acts as a toggle for a note for midi devices on linux. Optionally switch this functionality on and off with the systain pedal. Uses mido, only works on linux
3
+
4
+ ## Alternatives and prior work
5
+ Some hardware implements latching. Apparently Nord Lead A1, Sequential Prophet 6 and Novation Summit amongt others.
6
+
7
+ mido gives you complete control over what midi doers so you can do what you want if you are willing to put in the work - which has become fairly cheap with LLMs. Apparently, the VST Bidule has a midi latch feature as does VCV rack. These will work on windows. `loopMIDI` combined with mido apparently gives you programmatic contorl on windows. I have not tried most of thse things.
8
+
9
+ ## Installation
10
+ pipx install linux-midi-latch
11
+
12
+ ## Usage
13
+ `linux-midi-latch --alsa`
14
+
15
+ Use `aconnect` to route midi traffic through linux-midi-latch.
16
+
17
+ There are --jack and --osc options. I have not tested them nor do I know how to use them, but mididings works with jthem.
18
+
19
+ ## My setup
20
+ I am using this with an impact gxp6 nektar on Ubuntu LTS with a yamaha sustain pedel using alsa midi and fluidsynth.
21
+
22
+ ## About me
23
+ I am @readwith. I make tools for reading and agency and research - with and without Obsidian and AI.
24
+ You can follow me https::/readwithai.substack.com/ and https://x.com/readwithai
25
+
26
+ I also mess around with music and fitness technology. I have a treadmill desk with a lot of musical instruments. See [r/musicaltreadmilldesk](https://www.reddit.com/r/musicaltreadmilldesk/) for this. If you like this sort of tool you may well want to follow me there.
27
+
File without changes
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ linux-midi-latch - MIDI note latch for Linux using mido.
4
+
5
+ Each note press toggles that note on/off (latch mode).
6
+
7
+ Sustain pedal modes:
8
+ default -- latch always on; first sustain touch switches to --sustain-pedal mode
9
+ --ignore-sustain -- latch always on, sustain passed through untouched
10
+ --sustain-pedal -- sustain pedal must be held for latch to be active
11
+ --sustain-toggles -- sustain pedal press toggles latch on/off
12
+ """
13
+
14
+ import argparse
15
+ import mido
16
+ import mido.backends.rtmidi # noqa
17
+
18
+ latched_notes = set()
19
+ latch_active = True
20
+ sustain_down = False
21
+ sustain_mode = None
22
+
23
+ def note_off_msg(channel, note):
24
+ return mido.Message('note_off', channel=channel, note=note, velocity=0)
25
+
26
+ def note_on_msg(channel, note, velocity):
27
+ return mido.Message('note_on', channel=channel, note=note, velocity=velocity)
28
+
29
+ def process_note(msg, outport):
30
+ if not latch_active:
31
+ outport.send(msg)
32
+ return
33
+
34
+ note = msg.note
35
+ channel = msg.channel
36
+
37
+ if msg.type == 'note_on' and msg.velocity > 0:
38
+ if note in latched_notes:
39
+ latched_notes.discard(note)
40
+ outport.send(note_off_msg(channel, note))
41
+ else:
42
+ latched_notes.add(note)
43
+ outport.send(note_on_msg(channel, note, msg.velocity))
44
+ # swallow note_off — latch holds the note
45
+
46
+
47
+ def process_sustain_default(msg, outport):
48
+ global sustain_mode, latch_active, sustain_down
49
+
50
+ pedal_down = msg.value >= 64
51
+
52
+ if sustain_mode == 'default':
53
+ print("Sustain pedal detected — switching to sustain-pedal mode")
54
+ sustain_mode = 'pedal'
55
+
56
+ sustain_down = pedal_down
57
+ latch_active = pedal_down
58
+ if pedal_down:
59
+ print("Pedal down — latch on")
60
+ else:
61
+ print("Pedal released — latch off (notes keep ringing)")
62
+
63
+
64
+ def process_sustain_pedal(msg, outport):
65
+ global latch_active, sustain_down
66
+
67
+ pedal_down = msg.value >= 64
68
+ sustain_down = pedal_down
69
+ latch_active = pedal_down
70
+
71
+ if pedal_down:
72
+ print("Pedal down — latch on")
73
+ else:
74
+ print("Pedal released — latch off (notes keep ringing)")
75
+
76
+
77
+ def process_sustain_toggles(msg, outport):
78
+ global latch_active
79
+
80
+ if msg.value >= 64:
81
+ latch_active = not latch_active
82
+ print(f"Latch {'on' if latch_active else 'off'}")
83
+
84
+
85
+ def main():
86
+ global sustain_mode
87
+
88
+ parser = argparse.ArgumentParser(
89
+ prog='linux-midi-latch',
90
+ description='MIDI note latch for Linux. Route MIDI through this with aconnect.',
91
+ epilog='example: linux-midi-latch --alsa',
92
+ )
93
+
94
+ backend_group = parser.add_mutually_exclusive_group(required=True)
95
+ backend_group.add_argument('--alsa', action='store_true', help='Use ALSA MIDI backend')
96
+ backend_group.add_argument('--jack', action='store_true', help='Use JACK MIDI backend')
97
+
98
+ sustain_group = parser.add_mutually_exclusive_group()
99
+ sustain_group.add_argument('--ignore-sustain', action='store_true',
100
+ help='Latch always on, sustain pedal passed through untouched')
101
+ sustain_group.add_argument('--sustain-pedal', action='store_true',
102
+ help='Sustain pedal must be held for latch to be active')
103
+ sustain_group.add_argument('--sustain-toggles', action='store_true',
104
+ help='Sustain pedal press toggles latch on/off')
105
+
106
+ args = parser.parse_args()
107
+
108
+ if args.ignore_sustain:
109
+ sustain_mode = 'ignore'
110
+ sustain_fn = None
111
+ print("Latch on. Sustain pedal ignored.")
112
+ elif args.sustain_pedal:
113
+ sustain_mode = 'pedal'
114
+ sustain_fn = process_sustain_pedal
115
+ print("Latch active while sustain pedal is held.")
116
+ elif args.sustain_toggles:
117
+ sustain_mode = 'toggles'
118
+ sustain_fn = process_sustain_toggles
119
+ print("Latch on. Sustain pedal toggles latch.")
120
+ else:
121
+ sustain_mode = 'default'
122
+ sustain_fn = process_sustain_default
123
+ print("Latch on. First sustain touch will switch to sustain-pedal mode.")
124
+
125
+ if args.alsa:
126
+ mido.set_backend('mido.backends.rtmidi/LINUX_ALSA')
127
+ else:
128
+ mido.set_backend('mido.backends.rtmidi/UNIX_JACK')
129
+
130
+ print(f"Backend: {'alsa' if args.alsa else 'jack'}. Use aconnect to route MIDI through linux-midi-latch.")
131
+
132
+ with mido.open_input('linux-midi-latch', virtual=True) as inport, \
133
+ mido.open_output('linux-midi-latch out', virtual=True) as outport:
134
+
135
+ print("Ready.")
136
+ for msg in inport:
137
+ if msg.type in ('note_on', 'note_off'):
138
+ process_note(msg, outport)
139
+ elif msg.type == 'control_change' and msg.control == 64:
140
+ if sustain_fn:
141
+ sustain_fn(msg, outport)
142
+ else:
143
+ outport.send(msg)
144
+ else:
145
+ outport.send(msg)
146
+
147
+
148
+ if __name__ == '__main__':
149
+ main()
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = [ "hatchling",]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "linux-midi-latch"
7
+ version = "1.0.0"
8
+ description = "MIDI note latch for Linux using mididings"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ dependencies = [ "mido", "python-rtmidi",]
12
+ keywords = [ "midi", "latch", "linux", "mididings", "music",]
13
+ classifiers = [ "Environment :: Console", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Topic :: Multimedia :: Sound/Audio :: MIDI",]
14
+
15
+ [project.license]
16
+ text = "MIT"
17
+
18
+ [project.scripts]
19
+ linux-midi-latch = "linux_midi_latch.main:main"
20
+
21
+ [project.urls]
22
+ Homepage = "https://github.com/talwrii/linux-midi-latch"
23
+ Repository = "https://github.com/talwrii/linux-midi-latch"
24
+
25
+ [tool.hatch.build.targets.wheel]
26
+ packages = [ "linux_midi_latch",]
@@ -0,0 +1,47 @@
1
+ #!/bin/bash
2
+ set -o errexit
3
+ set -o nounset
4
+ set -o pipefail
5
+
6
+ # SSH ControlMaster setup
7
+ CONTROL_PATH="/tmp/ssh-release-%r@%h:%p"
8
+
9
+ echo "🔐 Establishing SSH connection to GitHub..."
10
+ ssh -o ControlMaster=yes -o ControlPath="$CONTROL_PATH" -o ControlPersist=300 -fN git@github.com
11
+
12
+ # Use ControlMaster for git
13
+ export GIT_SSH_COMMAND="ssh -o ControlPath=$CONTROL_PATH"
14
+
15
+ # Fail if there are any modified files
16
+ if ! git diff-index --quiet HEAD --; then
17
+ echo "Error: You have uncommitted changes to tracked files"
18
+ git status --short
19
+ ssh -o ControlPath="$CONTROL_PATH" -O exit git@github.com 2>/dev/null || true
20
+ exit 1
21
+ fi
22
+
23
+ git push
24
+
25
+ if [ -f pyproject.toml ]; then
26
+ VERSION=$(cat pyproject.toml | toml2json | jq -rc '.project.version')
27
+ else
28
+ VERSION=$(python3 setup.py --version)
29
+ fi
30
+
31
+ git tag "$VERSION"
32
+ git push --tags
33
+
34
+ rm -rf dist *.egg-info build
35
+
36
+ if [ -f pyproject.toml ]; then
37
+ python3 -m build
38
+ else
39
+ python3 setup.py sdist
40
+ fi
41
+
42
+ twine upload -p $(cat token) dist/*
43
+
44
+ # Close ControlMaster
45
+ ssh -o ControlPath="$CONTROL_PATH" -O exit git@github.com 2>/dev/null || true
46
+
47
+ echo "✅ Released version $VERSION"