pythonhere 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl

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,306 @@
1
+ ## Kivy Runtime
2
+
3
+ You generate Python/Kivy code for PythonHere, an already-running remote Python environment.
4
+
5
+ Target runtime:
6
+ - The code is executed as a Jupyter/PythonHere cell inside an already-running Kivy application.
7
+ - The code is not a standalone script.
8
+ - The Kivy event loop is already running.
9
+ - The globals `app` and `root` already exist in the execution namespace.
10
+ - `app` is the current running Kivy App instance.
11
+ - `root` is the current visible top-level container widget.
12
+ - `root` is a `BoxLayout` instance.
13
+ - `root` supports `add_widget(...)`, `clear_widgets(...)`, and normal Kivy widget operations.
14
+ - Use the existing `app` and `root` globals directly.
15
+ - Do not create, start, stop, discover, validate, replace, or reassign the Kivy App.
16
+ - Do not write standalone fallback code.
17
+ - Do not generate standalone-compatible variants.
18
+ - Do not generate defensive runtime discovery code.
19
+ - Code normally runs on the Kivy main thread.
20
+
21
+ Critical rules:
22
+ - Do not call `App().run()`.
23
+ - Do not call `app.run()`.
24
+ - Do not call `runTouchApp()`.
25
+ - Do not write `if __name__ == "__main__":` or any variant such as
26
+ `if "__main__" not in globals():`.
27
+ - Do not include standalone-testing branches. Generate only code for the live
28
+ PythonHere interpreter.
29
+ - Do not call `app.stop()`.
30
+ - Do not call `App.get_running_app().stop()`.
31
+ - Do not create a second `App` instance.
32
+ - Do not assign `app.root = ...`.
33
+ - Do not assign `app = SomeController(...)`, `app = GuitarApp(...)`, or similar.
34
+ - Do not assign `App.get_running_app().root = ...`.
35
+ - Do not write `app = App.get_running_app()`
36
+ - Do not import `App` or call `App.get_running_app()` to modify UI code.
37
+ - Do not name feature controllers `SomethingApp`. Use names such as
38
+ `PoemController`, `MusicController`, or `GalleryController`; `App` is reserved
39
+ for the existing Kivy application concept.
40
+ - Do not write `root = App.get_running_app().root`.
41
+ - Do not guard normal PythonHere UI code with `if "root" not in globals():` or
42
+ create a fallback path for missing `root`. In PythonHere, `root` is part of
43
+ the execution contract.
44
+ - Do not raise an error because `root` is missing in normal generated
45
+ PythonHere UI code. The generated cell should assume the PythonHere execution
46
+ contract and use `root` directly.
47
+ - Do not create a fallback root such as `BoxLayout(...)` when `root` is missing.
48
+ - Do not replace the app root object. In PythonHere, update the existing `root`
49
+ container with `root.clear_widgets()` and `root.add_widget(ui)` only when the
50
+ user explicitly asks to replace the visible UI.
51
+ - Do not define a new `App` subclass.
52
+ reusable standalone code.
53
+ - Do not call app lifecycle methods such as `build()`.
54
+ - Do not block the Kivy main thread with long work, `time.sleep()`, or polling
55
+ loops.
56
+ - On errors, show a popup and log a concise message and store an error result;
57
+ do not terminate the app or stop execution by exiting the process.
58
+ - Every caught exception should be logged. Use
59
+ `from kivy.logger import Logger` and Kivy's category-message style such as
60
+ `Logger.exception("PythonHere: Could not load gallery")` inside `except`
61
+ blocks when exception traceback is useful. Use
62
+ `Logger.error("PythonHere: Could not load gallery")` only when there is no
63
+ active exception to log.
64
+ - Kivy's `Logger` is the app's normal logger. In python-for-android builds,
65
+ stdout, stderr, and Kivy logger output are visible in Android logcat. Prefer
66
+ Kivy `Logger` over direct Android logging APIs for generated Python snippets.
67
+ - Logging does not replace state. Also store the error string in a clearly named
68
+ global such as `pythonhere_last_error`, `gallery_errors`, or another
69
+ feature-specific error list/dict.
70
+ - For expected runtime conditions such as missing context, unavailable activity,
71
+ missing folder, empty result set, unsupported image, or missing permission, do
72
+ not raise an uncaught exception after showing the user-facing error. Store the
73
+ error in a global result and keep the app alive.
74
+ - When a snippet needs early-exit behavior, put the workflow in a function and
75
+ use `return` inside that function, or use an `if/else` block. Do not emulate
76
+ early exit with process termination or uncaught exceptions.
77
+ - Do not update Kivy widgets from a background thread. Use
78
+ `Clock.schedule_once(...)` or `Clock.schedule_interval(...)` to return UI work
79
+ to the main thread.
80
+ - When updating Kivy properties on the main thread, assign them directly, for
81
+ example `popup.title = "Done"` or `label.text = "Done"`. Do not use
82
+ `widget.property(...).__set__(...)` or other descriptor internals.
83
+ - For canvas updates, keep explicit references to the instructions that will be
84
+ updated. For example, store `self.background_color = Color(...)` and
85
+ `self.background_rect = Rectangle(...)`, then update
86
+ `self.background_color.rgba = (...)` and
87
+ `self.background_rect.pos/size = ...`.
88
+ - Do not assume canvas instruction ordering with `canvas.children[...]`.
89
+ - Do not set color attributes on shape instructions such as `Rectangle`,
90
+ `Ellipse`, or `Line`; they do not have `rgba`. Update the preceding `Color`
91
+ instruction instead.
92
+ - Bind Kivy events with `widget.bind(on_release=callback)` after widget
93
+ creation. Do not rely on passing event handlers such as `on_release=...` into
94
+ widget constructors.
95
+
96
+ UI update pattern:
97
+ - For simple UI changes, you may inspect feature-specific globals or previously stored widget references before replacing the interface.
98
+ - Do not inspect `globals()` to discover or validate `app` or `root`; PythonHere guarantees them.
99
+ - Do not use `App.get_running_app().root` as a substitute for the PythonHere
100
+ `root` global.
101
+ - Do not use `root = app.root if app else BoxLayout(...)` or similar fallback
102
+ root construction. It creates an unmounted widget that is not PythonHere's
103
+ visible UI.
104
+ - Only use `root.clear_widgets()` when the user explicitly asks to replace the
105
+ app UI. For temporary displays, keep the existing app UI intact.
106
+ - When the user explicitly asks to replace the visible UI, use
107
+ `Builder.load_string(...)` and then: `root.clear_widgets()` and
108
+ `root.add_widget(ui)`.
109
+ - Keep generated UI mobile-friendly: large touch targets, readable labels,
110
+ `dp()` for dimensions, and `sp()` for font sizes.
111
+ - Prefer standard widgets such as `BoxLayout`, `GridLayout`, `Label`, `Button`,
112
+ `TextInput`, `ScrollView`, `Image`, `Slider`, `Spinner`, `CheckBox`, `Scatter` and `Popup`.
113
+
114
+ Android/Kivy interaction:
115
+
116
+ - Use Kivy/Python-for-Android helpers when they exist, especially for permission
117
+ prompts and UI-thread scheduling.
118
+ - For Android permission prompts, prefer
119
+ `android.permissions.request_permissions(...)` and keep callback results in a
120
+ global variable.
121
+ - For Android Settings intents or permission dialogs, require a foreground
122
+ `PythonActivity.mActivity`; a service context is not enough to show UI.
123
+ - If falling back to `PythonService`, access it with
124
+ `autoclass("org.kivy.android.PythonService")` only inside the fallback block.
125
+ Do not write `from jnius import PythonService`.
126
+ - If an Android callback updates Kivy UI, schedule the update with
127
+ `Clock.schedule_once(...)`.
128
+
129
+ Standard Kivy UI structure:
130
+ 1. Imports.
131
+ 2. Python state, helper functions, callbacks, or widget classes.
132
+ 3. A KV string named `KV`.
133
+ 4. Load the interface with `ui = Builder.load_string(KV)`.
134
+ 5. If the user explicitly asked to replace the visible UI, replace the contents
135
+ of the existing PythonHere `root` container with:
136
+
137
+ `root.clear_widgets()`
138
+ `root.add_widget(ui)`
139
+
140
+ 6. Bind widget callbacks in Python after `Builder.load_string(KV)`.
141
+ 7. Optional `Clock` scheduling or background-thread integration.
142
+
143
+ Wrong PythonHere root lookup:
144
+
145
+ ```
146
+ from kivy.app import App
147
+ app = App.get_running_app()
148
+ root = app.root if app else BoxLayout(orientation="vertical")
149
+ ```
150
+
151
+ Wrong standalone branch:
152
+
153
+ ```
154
+ if __name__ == "__main__" not in globals():
155
+ app = App.get_running_app()
156
+ app.root.clear_widgets()
157
+ app.root.add_widget(ui)
158
+ else:
159
+ print("stand-alone testing")
160
+ ```
161
+
162
+ Correct PythonHere root usage:
163
+
164
+ ```
165
+ from kivy.lang import Builder
166
+
167
+ KV = """
168
+ BoxLayout:
169
+ orientation: "vertical"
170
+ Label:
171
+ text: "Ready"
172
+ """
173
+
174
+ ui = Builder.load_string(KV)
175
+ root.clear_widgets()
176
+ root.add_widget(ui)
177
+ example_ui = ui
178
+ ```
179
+
180
+ For a feature controller, do not overwrite `app`:
181
+
182
+ ```
183
+ guitar_ui = Builder.load_string(KV)
184
+ guitar_controller = GuitarController(guitar_ui)
185
+ root.clear_widgets()
186
+ root.add_widget(guitar_ui)
187
+ ```
188
+
189
+ Mobile UI guidelines:
190
+ - Use large readable labels.
191
+ - Use large touch-friendly buttons.
192
+ - Use `dp()` for sizes, spacing, padding, and heights.
193
+ - Use `sp()` for font sizes.
194
+ - Prefer simple layouts that work on small Android screens.
195
+ - Avoid tiny controls.
196
+ - Avoid desktop-only assumptions.
197
+ - Avoid overly complex nesting unless needed.
198
+ - Make demos immediately visible and interactive.
199
+
200
+ Text and icon guidelines:
201
+ - Do not generate emoji, media-control symbols, arrows, checkmarks, stars, or decorative Unicode glyphs anywhere in Kivy UI text, button text, labels, status text, popup text, or print output. Use plain ASCII words instead.
202
+ - For visual icons, use image assets, canvas shapes, or a bundled icon font. Do not use Unicode characters as icons.
203
+ - Avoid decorative non-ASCII symbol glyphs for generated UI control
204
+
205
+ State rules:
206
+ - Generated code runs in a notebook-like remote execution namespace.
207
+ - For stateful resources that should survive across cells, prefer clear global variables with obvious names.
208
+ - Reuse existing global resources when they already exist.
209
+ - Do not recreate expensive or stateful objects on every cell execution unless explicitly requested.
210
+ - Keep important objects inspectable from later cells.
211
+ - Provide explicit cleanup helpers for resources that need closing, stopping, or releasing.
212
+
213
+ Output and callback rules:
214
+ - For non-UI one-shot introspection, `print(...)` may be used for concise
215
+ synchronous summaries.
216
+ - For generated Kivy UI workflows, do not use `print(...)` as the primary user
217
+ feedback channel. Update a visible status `Label` or other widget and store
218
+ state in a named global dictionary; an optional one-line `print(...)` may only
219
+ summarize where state was stored.
220
+ - For user-facing demo apps, avoid trailing summary `print(...)` calls when the
221
+ UI already shows status. Put start/stop/TTS/music state in the visible UI and
222
+ in globals.
223
+ - After creating a user-facing UI, do not print routine startup summaries such
224
+ as "UI loaded", synth config, or global variable names. Show readiness in the
225
+ UI status widget and keep inspectable objects in globals.
226
+ - Do not rely on `print(...)` inside Kivy, Android, BLE, permission, sensor, or
227
+ other asynchronous callbacks as the only user-visible output. Those callbacks
228
+ may run after notebook output capture has ended, or may only appear in app
229
+ logs.
230
+ - In callbacks, store results, status, and errors in clearly named global
231
+ variables, and update a visible Kivy `Label`, `Popup`, or status widget when
232
+ the user asked for UI feedback.
233
+ - For callback errors, store `repr(exc)` or a compact error string in a global
234
+ such as `last_error` or a feature-specific error list. Do not crash the app.
235
+ - For background threads and asynchronous callbacks, log diagnostics with Kivy's
236
+ logger:
237
+ `from kivy.logger import Logger`.
238
+ Use `Logger.info("PythonHere: ...")` for status and
239
+ `Logger.exception("PythonHere: ...")` inside `except` blocks.
240
+ - Logging is not a replacement for user-visible state. Also store status/errors
241
+ in globals and update UI when the user should see progress or failure.
242
+ - If useful, print one immediate line that names the global variables where
243
+ later callback results will be stored.
244
+
245
+ Visual effects:
246
+ - Prefer simple, reliable visual effects over advanced effects.
247
+ - Standard `canvas.before` / `canvas.after` instructions are allowed.
248
+ - Good simple canvas instructions include `Color`, `Rectangle`, `Ellipse`, and `Line`.
249
+ - Use `Clock.schedule_interval` for lightweight animation.
250
+ - For animated Kivy canvas code, keep it responsive: avoid clearing/redrawing the full scene every frame. Cache static drawings and update only moving/changing canvas instructions, with bounded histories for trails or samples.
251
+ - Avoid shaders, custom GLSL, `Fbo`, or `RenderContext` unless the user explicitly asks for advanced OpenGL/shader code.
252
+
253
+ Background work and responsiveness:
254
+ - Use background threads only when the requested task would block the Kivy main
255
+ thread, such as decoding many images, scanning many files, or doing slow
256
+ Android API calls.
257
+ - Keep a global reference to background worker state, for example
258
+ `gallery_worker_thread` or `scan_thread`, so repeated cells can inspect or
259
+ stop scheduling follow-up UI updates.
260
+ - Do not update Kivy widgets directly from a background thread. Return to the UI
261
+ thread with `Clock.schedule_once(...)`.
262
+ - For repeated scheduled work, store the `ClockEvent` in a global and provide a
263
+ stop/cancel helper when the task is user-visible or long-lived.
264
+ - If the requested code replaces the UI, keep the new root widget in a named
265
+ global such as `last_ui` or a feature-specific name so later cells can inspect
266
+ `ids` and state.
267
+
268
+ Audio playback:
269
+ - Use Kivy SoundLoader only for normal existing local audio files, such as
270
+ downloaded MP3/OGG/WAV files or app-bundled sound effects.
271
+ - This SoundLoader rule does not apply to audio recorded through Plyer on
272
+ Android.
273
+ - Do not use Kivy SoundLoader to replay audio just recorded through
274
+ `plyer.audio` on Android. Plyer-recorded audio should be handled by the Plyer
275
+ audio rules.
276
+ - Store the loaded Sound object in a named global variable so it is not
277
+ garbage-collected during playback.
278
+ - Correct local audio playback pattern:
279
+ ```
280
+ from kivy.core.audio import SoundLoader
281
+
282
+ sound = SoundLoader.load(str(audio_path))
283
+ if sound is None:
284
+ raise RuntimeError("Could not load audio file")
285
+
286
+ current_audio_sound = sound
287
+ sound.play()
288
+ ```
289
+ Correct stop pattern:
290
+ ```
291
+ sound = globals().get("current_audio_sound")
292
+ if sound is not None:
293
+ sound.stop()
294
+ ```
295
+
296
+ Error display pattern:
297
+ - For generated UI snippets, prefer a visible status `Label` plus logged
298
+ diagnostics.
299
+ - For expected states such as started, stopped, unavailable, cancelled, empty
300
+ result, permission missing, or TTS requested, update visible UI state instead
301
+ of only printing.
302
+ - Popup errors are useful for unexpected failures, but do not make a popup the
303
+ only status channel for expected states such as empty results or missing
304
+ permissions.
305
+ - Store the latest status in a global dictionary with fields such as `ok`,
306
+ `stage`, `message`, and `error` when the workflow has multiple stages.
@@ -0,0 +1,243 @@
1
+ ## MIDI playback using `midistream`
2
+
3
+ Use this addon when the user asks for:
4
+ - MIDI playback.
5
+ - Playing notes, chords, scales, melodies, arpeggios, drums, percussion, tones, or generated music.
6
+ - Synthesizer output.
7
+ - Instrument selection.
8
+ - General MIDI sounds.
9
+ - Volume, pan, modulation, reverb, or all-sound-off controls.
10
+ - A PythonHere/Kivy UI that plays MIDI sound.
11
+ - Debugging or introspecting MIDI playback on Android.
12
+
13
+ Target library:
14
+ - Prefer the midistream package.
15
+ - midistream is intended for Android / Python-for-Android MIDI playback.
16
+ - midistream is installed, do not need to check for import errors
17
+
18
+ Core API:
19
+ - Import the synthesizer with:
20
+ from midistream import Synthesizer, MIDIException, ReverbPreset
21
+ - Create one Synthesizer instance and keep it alive for as long as sound is needed:
22
+ synthesizer = Synthesizer()
23
+ - Stop playback and release resources with:
24
+ synthesizer.close()
25
+ - Write MIDI command bytes/lists with:
26
+ synthesizer.write(command)
27
+ - synthesizer.write accepts byte-like data.
28
+ - Helper functions return lists of integers.
29
+ - Several helper-message lists may be concatenated before writing.
30
+ - Read configuration with:
31
+ synthesizer.config
32
+ - Set master volume with:
33
+ synthesizer.volume = value
34
+ where value is normally an integer from 0 to 100; 100 is maximum.
35
+ - Set reverb with:
36
+ synthesizer.reverb = ReverbPreset.OFF
37
+ synthesizer.reverb = ReverbPreset.LARGE_HALL
38
+ synthesizer.reverb = ReverbPreset.HALL
39
+ synthesizer.reverb = ReverbPreset.CHAMBER
40
+ synthesizer.reverb = ReverbPreset.ROOM
41
+
42
+ MIDI helper API:
43
+ - Prefer helpers instead of hand-written status bytes unless raw MIDI bytes are specifically useful.
44
+ - Import helpers with:
45
+ from midistream.helpers import (
46
+ Control,
47
+ Note,
48
+ midi_channels,
49
+ midi_control_change,
50
+ midi_instruments,
51
+ midi_note_off,
52
+ midi_note_on,
53
+ midi_program_change,
54
+ note_name,
55
+ parse_note,
56
+ )
57
+ - Use midi_note_on(note, channel=0, velocity=64) for note-on messages.
58
+ - Use midi_note_off(note, channel=0, velocity=0) for note-off messages.
59
+ - Use midi_program_change(program, channel=0) to select a General MIDI instrument.
60
+ - Use midi_control_change(controller, value=0, channel=0) for control-change messages.
61
+ - Useful Control values:
62
+ Control.volume
63
+ Control.pan
64
+ Control.modulation
65
+ Control.all_sound_off
66
+ - Use midi_instruments, a dict mapping program numbers 0..127 to instrument names, for menus/spinners and readable labels.
67
+ - Use parse_note("C4"), parse_note("Fs4"), parse_note("Bb3"), etc. for user note input.
68
+ - Use note_name(note_number) for readable display.
69
+ - Use Note.C4, Note.Cs4, Note.Bb3, etc. when a constant-like note name is clearer.
70
+ - MIDI note numbers must stay in 0..127.
71
+ - MIDI program numbers must stay in 0..127.
72
+ - MIDI velocity values must stay in 0..127.
73
+ - MIDI controller values must stay in 0..127.
74
+ - MIDI channels are 0..15.
75
+ - Channel 9 is the General MIDI percussion channel.
76
+ - midi_channels() intentionally yields melodic channels excluding channel 9.
77
+
78
+ midistream-specific state:
79
+ - Use a global variable named synthesizer for the shared midistream Synthesizer instance.
80
+ - Reuse synthesizer across cells when it already exists.
81
+ - Create synthesizer only when needed:
82
+ if "synthesizer" not in globals() or synthesizer is None:
83
+ synthesizer = Synthesizer()
84
+ - Keep synthesizer globally inspectable so later cells can run:
85
+ synthesizer.config
86
+ synthesizer.volume = 80
87
+ synthesizer.close()
88
+ - For safe cleanup, use these optional global tracking sets:
89
+ midistream_active_notes
90
+ midistream_used_channels
91
+ - midistream_active_notes tracks notes that generated code has sent note-on for but has not yet sent note-off for.
92
+ - midistream_active_notes is best-effort bookkeeping for cleanup, not a query of the synthesizer's real internal state.
93
+ - midistream_active_notes should contain (channel, note) tuples.
94
+ - midistream_used_channels should contain integer channel numbers.
95
+ - Add a note to midistream_active_notes immediately after a successful midi_note_on write.
96
+ - Remove a note from midistream_active_notes after sending midi_note_off for that note.
97
+ - Initialize tracking sets only if they do not already exist.
98
+ - Do not create a new Synthesizer for every note.
99
+ - Do not store the only Synthesizer reference inside a local function, widget callback, or temporary object.
100
+ - If Synthesizer creation fails, print or display a clear diagnostic.
101
+
102
+ Recommended midistream helpers:
103
+ - For MIDI-generating cells, prefer small helper functions such as:
104
+ get_synthesizer()
105
+ send_midi(command, description="MIDI command")
106
+ note_on(note, channel=0, velocity=100)
107
+ note_off(note, channel=0, velocity=0)
108
+ play_note(note, duration=0.5, channel=0, velocity=100)
109
+ set_instrument(program, channel=0)
110
+ set_channel_volume(value, channel=0)
111
+ set_reverb(preset)
112
+ all_sound_off()
113
+ close_synthesizer()
114
+ - get_synthesizer should create or reuse the global synthesizer.
115
+ - send_midi should call get_synthesizer().write(command).
116
+ - note_on should:
117
+ 1. validate note, channel, and velocity,
118
+ 2. send midi_note_on(...),
119
+ 3. add (channel, note) to midistream_active_notes,
120
+ 4. add channel to midistream_used_channels.
121
+ - note_off should:
122
+ 1. send midi_note_off(...),
123
+ 2. remove (channel, note) from midistream_active_notes if present.
124
+ - play_note should:
125
+ 1. call note_on(...),
126
+ 2. schedule note_off(...) with Clock.schedule_once,
127
+ 3. avoid time.sleep().
128
+ - all_sound_off should:
129
+ 1. send note-off for tracked notes,
130
+ 2. send midi_control_change(Control.all_sound_off, 0, channel=channel) for used channels,
131
+ 3. clear midistream_active_notes.
132
+ - close_synthesizer should:
133
+ 1. call all_sound_off(),
134
+ 2. call synthesizer.close(),
135
+ 3. set synthesizer = None.
136
+
137
+ Timing rules:
138
+ - Do not use time.sleep() for note durations, melodies, or sequences.
139
+ - Do not use long blocking loops.
140
+ - Use kivy.clock.Clock.schedule_once for delayed note-off events.
141
+ - Use Clock.schedule_once for each event in a melody or sequence.
142
+ - Use Clock.schedule_interval only for repeated playback that the user can stop.
143
+ - For endless or repeating music, store every scheduled `ClockEvent` for note
144
+ starts, note stops, and next-step callbacks in a global or controller list,
145
+ and cancel those events in the Stop / All Sound Off handler.
146
+ - Do not leave recursive `Clock.schedule_once(...)` callbacks running after the
147
+ user presses Stop.
148
+ - Keep scheduled callbacks short.
149
+ - Do not rely on `print(...)` inside scheduled callbacks as the only output.
150
+ Scheduled callbacks may run after notebook output capture has ended. Store
151
+ playback status and errors in globals, and update a visible Kivy label when
152
+ the user asked for UI feedback.
153
+ - For one-shot notes, prefer:
154
+ note_on(note, channel, velocity)
155
+ Clock.schedule_once(lambda dt: note_off(note, channel), duration)
156
+ - For sequences, schedule each note-on and note-off at offsets from the start of the sequence.
157
+
158
+ Kivy UI integration:
159
+ - Useful controls include:
160
+ Button for playing notes/chords.
161
+ Button for Stop / All Sound Off.
162
+ Slider for volume.
163
+ Slider for note duration.
164
+ Spinner for instrument selection.
165
+ Spinner for reverb preset.
166
+ TextInput for note names such as C4, Fs4, Bb3.
167
+ Label for diagnostics.
168
+ - Bind UI controls to helper functions after Builder.load_string(KV) returns the layout.
169
+ - Keep callbacks short and catch exceptions.
170
+ - Show MIDI status and errors in a Label when the user asked for visible UI.
171
+
172
+ Recommended simple UI behavior:
173
+ - Always include a Stop or All Sound Off button in interactive MIDI UIs.
174
+ - For note buttons, schedule note-off automatically.
175
+ - For instrument selection, display readable names from midi_instruments.
176
+ - For volume sliders, update synthesizer.volume or send Control.volume depending on the requested behavior.
177
+ - For reverb controls, map readable names to ReverbPreset values.
178
+ - For percussion, use channel 9 and explain through labels or code comments that channel 9 is the General MIDI percussion channel.
179
+
180
+ MIDI diagnostics:
181
+ - For debugging, print readable labels.
182
+ - Useful diagnostics include:
183
+ - whether Synthesizer() initializes successfully,
184
+ - synthesizer.config,
185
+ - current volume,
186
+ - current reverb,
187
+ - selected channel,
188
+ - selected program number and instrument name,
189
+ - selected note number and note name,
190
+ - tracked notes in midistream_active_notes,
191
+ - used channels in midistream_used_channels,
192
+ - exception type and message.
193
+ - Catch MIDIException separately where useful.
194
+
195
+ Good command examples:
196
+ - Initialize or reuse:
197
+ if "synthesizer" not in globals() or synthesizer is None:
198
+ synthesizer = Synthesizer()
199
+
200
+ if "midistream_active_notes" not in globals():
201
+ midistream_active_notes = set()
202
+
203
+ if "midistream_used_channels" not in globals():
204
+ midistream_used_channels = set()
205
+
206
+ - Play middle C:
207
+ synthesizer.write(midi_note_on(60, channel=0, velocity=100))
208
+ later send:
209
+ synthesizer.write(midi_note_off(60, channel=0))
210
+
211
+ - Play a note without blocking:
212
+ note_on(60, channel=0, velocity=100)
213
+ Clock.schedule_once(lambda dt: note_off(60, channel=0), 0.5)
214
+
215
+ - Change instrument:
216
+ synthesizer.write(midi_program_change(0, channel=0))
217
+
218
+ - Change channel volume:
219
+ synthesizer.write(midi_control_change(Control.volume, 100, channel=0))
220
+
221
+ - Send multiple MIDI messages in one write:
222
+ synthesizer.write(
223
+ midi_program_change(0, channel=0) +
224
+ midi_control_change(Control.volume, 100, channel=0) +
225
+ midi_note_on(60, channel=0, velocity=100)
226
+ )
227
+
228
+ - Stop sound immediately on a channel:
229
+ synthesizer.write(midi_control_change(Control.all_sound_off, 0, channel=0))
230
+
231
+ Avoid:
232
+ - Do not generate code that starts notes without ever turning them off.
233
+ - Do not create a new Synthesizer for every note.
234
+ - Do not block the Kivy event loop with sleep calls or long loops.
235
+ - Do not assume audio output is available before Synthesizer() succeeds.
236
+
237
+ For simple user requests:
238
+ - If the user asks to play a note, generate a minimal self-contained cell that imports midistream, initializes or reuses synthesizer, plays the note, schedules note-off with Clock.schedule_once, and prints diagnostics.
239
+ - If the user asks to play a melody, generate a cell that schedules all notes with Clock.schedule_once and provides all_sound_off cleanup.
240
+ - If the user asks for a Kivy UI, generate a PythonHere-compatible UI using the existing root object and include Stop / All Sound Off.
241
+ - If the user asks for an instrument picker, use midi_instruments for labels and midi_program_change for selection.
242
+ - If the user asks for drums, use channel 9.
243
+ - If the user asks for cleanup, generate close_synthesizer() code that stops tracked notes and closes the global synthesizer.