Skip to content

Commit ce26e44

Browse files
committed
v0.6.0
1 parent b0e0589 commit ce26e44

22 files changed

+614
-137
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
### v0.6
4+
5+
- Update to Python 3.12, updated dependencies.
6+
- Fixed Windows build batch script.
7+
- Add per-key velocity curve configuration.
8+
39
### v0.3.0
410

511
- Added config persistence with the `save` command.

README.md

Lines changed: 42 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
- Absolute
2020
- Link slide to press (channel pressure)
2121
- Bipolar
22-
- Invert sustain pedal (because I couldn't find this feature in Equator lmao)
22+
- Invert sustain pedal (because I couldn't find this feature in Equator)
2323

2424
## Quick Start
2525

@@ -188,6 +188,41 @@ As such, if you're using two instances of Keyscape, it is crucial
188188
that you turn off the sustain pedal noise on either one of the VSTs,
189189
otherwise you'd get double the sustain pedal noise.
190190

191+
### Key split data example
192+
193+
For example, this is
194+
[line 92 of the default 31 edo mapping](https://github.com/euwbah/microtonal-seaboard/blob/master/mappings/default.sbmap#L92):
195+
196+
`A4 30 -38.7097 -1 50 0.0000 0 73 38.7097 1 98 0.0000 0 128 38.7097 1`
197+
198+
The first value of the line, `A4` is the note name of which this
199+
split data pertains to. The split information only applies to this
200+
one key. Capitalisation does not matter.
201+
202+
After this, the values are presented in groups of threes.
203+
204+
The value `30` represents that the following tuning data only applies
205+
when the Slide value (CC74) is below 30 (exclusive). This is the area
206+
right at the bottom of the white keys on the seaboard.
207+
208+
The next value `-38.7097` represents the cent offset of this note
209+
with respect to the 12 edo equivalent on the same key.
210+
In 31 edo, this is the note A-down. The cent offset is used to
211+
calculate the amount of pitch bend to send when in MPE mode.
212+
213+
The final value of the triple is `-1`, and this says that the
214+
output of this key when in MIDI mode is 1 note below A4 (that is, Ab4).
215+
216+
Looking at the next 3 values, `50 0.0000 0`, tells us that for the
217+
Slide values 30-49 (inclusive), which is the middle section of the
218+
white key, we will apply a tuning offset of 0 cents in MPE mode,
219+
and an output of A4 in MIDI mode.
220+
In 31 edo, this is the note A-natural.
221+
222+
The next 3 values, `73 38.7097 1` gives us the note A-up, and will
223+
be applied for Slide values 50-72 (inclusive), which is the part of
224+
the white key right below where the black keys begin.
225+
191226
### Microtuning on Pianoteq
192227

193228
Pianoteq offers [multi-channel keyboard mappings](https://forum.modartt.com/viewtopic.php?id=4307)
@@ -196,15 +231,15 @@ full range.
196231

197232
To make use of this in the seaboard mapper:
198233

199-
1. enter `autosplit` in the console to turn auto split mode on
234+
1. Enter the `autosplit` command in the console to turn auto split mode on
200235

201236
2. Under Pianoteq's microtuning screen, there's a button on the top right
202237
to change the **keyboard mapping**. Click on it and you will see an option
203238
titled '**Extended layout for up to 16*128 notes**'. Open the drop down and select **MIDI channel 5**
204-
239+
205240
![img.png](imgs/extended-layout-pianoteq.png)
206241

207-
- This will cause the notes received on each subsequent MIDI channel
242+
- This will cause the notes received on each subsequent MIDI channel
208243
to sound one octave higher than the previous MIDI channel.
209244
- The checked midi channel (5) denotes the MIDI channel that will not have any octave/equave transposition at all.
210245
- E.g. if MIDI channel 5 is selected as the main channel,
@@ -258,91 +293,11 @@ Initial Strike will yield a Slide value of 0, and sliding
258293
all the way to either to top or bottom will yield the max Slide value of
259294
127.
260295

261-
## Seaboard Map `.sbmap` file format
262-
263-
The mapper loads `.sbmap` files to assign a mapping to the seaboard.
264-
If `mappings/default.sbmap` exists, it will load that mapping on
265-
startup.
266-
267-
Each line of the .sbmap file can either be a descriptive comment,
268-
a comment, or key split data.
269-
270-
Mappings are meant to be generated algorithmically with a script.
271-
Take a look at https://github.com/euwbah/microtonal-seaboard/blob/master/mapping_generator/edo31.py
272-
for an example.
273-
274-
### `/ descriptive comment`
275-
276-
Lines that begin with `/` will be printed in the console
277-
when the mapping is loaded.
278-
279-
### `# comment`
296+
## Invert sustain pedal
280297

281-
Lines that begin with `#` will be ignored entirely
298+
The `sus` command toggle the sustain pedal polarity (so you don't have to reach for the physical switch on yours)
282299

283-
### Key split data
284-
285-
This line will describe how to split one key.
286-
287-
The values of the key split data are to be separated by spaces
288-
or tabs, and are presented in the following format:
289-
290-
`<note> <p1> <c1> <s1> (<p2> <c2> <s2> ... <pn> <cn> <sn>)`
291-
292-
- `note`: 12edo note name of the key that this split applies to
293-
- `pn`: a cc74 (slide) value representing the exclusive upper bounds
294-
of the nth vertical split point
295-
- `cn`: (for MPE mode) the cents offset of the output note with respect to the
296-
key's original tuning in 12 edo.
297-
- `sn`: (for MIDI mode) the output MIDI note represented as number of
298-
steps from the note A4.
299-
300-
Take note of the following constraints:
301-
302-
1. Key split data lines must have at least one split point.
303-
2. The split points must be presented in order of increasing
304-
cc74 (Slide) values
305-
3. The final split point must always be 128 representing the
306-
maxima of the cc74 value range.
307-
4. You can leave out notes in the mapping file. Not all
308-
of them have to be mapped in order for the program to work.
309-
The left-out notes will default to the standard behavior
310-
and tuning.
311-
312-
#### Key split data example
313-
314-
For example, this is
315-
[line 92 of the default 31 edo mapping](https://github.com/euwbah/microtonal-seaboard/blob/master/mappings/default.sbmap#L92):
316-
317-
`A4 30 -38.7097 -1 50 0.0000 0 73 38.7097 1 98 0.0000 0 128 38.7097 1`
318-
319-
The first value of the line, `A4` is the note name of which this
320-
split data pertains to. The split information only applies to this
321-
one key. Capitalisation does not matter.
322-
323-
After this, the values are presented in groups of threes.
324-
325-
The value `30` represents that the following tuning data only applies
326-
when the Slide value (CC74) is below 30 (exclusive). This is the area
327-
right at the bottom of the white keys on the seaboard.
328-
329-
The next value `-38.7097` represents the cent offset of this note
330-
with respect to the 12 edo equivalent on the same key.
331-
In 31 edo, this is the note A-down. The cent offset is used to
332-
calculate the amount of pitch bend to send when in MPE mode.
333-
334-
The final value of the triple is `-1`, and this says that the
335-
output of this key when in MIDI mode is 1 note below A4 (that is, Ab4).
336-
337-
Looking at the next 3 values, `50 0.0000 0`, tells us that for the
338-
Slide values 30-49 (inclusive), which is the middle section of the
339-
white key, we will apply a tuning offset of 0 cents in MPE mode,
340-
and an output of A4 in MIDI mode.
341-
In 31 edo, this is the note A-natural.
342-
343-
The next 3 values, `73 38.7097 1` gives us the note A-up, and will
344-
be applied for Slide values 50-72 (inclusive), which is the part of
345-
the white key right below where the black keys begin.
300+
##
346301

347302
### Huh?
348303

__pycache__/configs.cpython-312.pyc

345 Bytes
Binary file not shown.

__pycache__/handler.cpython-312.pyc

683 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.

__pycache__/mapping.cpython-312.pyc

187 Bytes
Binary file not shown.

__pycache__/velcurve.cpython-312.pyc

8.63 KB
Binary file not shown.

__pycache__/ws_server.cpython-312.pyc

-34 Bytes
Binary file not shown.

build.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ pip install -r requirements.txt
99
pyinstaller microtonal-seaboard.spec &&^
1010
cd dist/ &&^
1111
tar -a -c -f microtonal-seaboard-windows.zip ../mappings microtonal-seaboard.exe
12-
deactivate
12+
deactivate

config.dill

179 Bytes
Binary file not shown.

configs.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from mapping import Mapping
88
from split import SplitData
9+
from velcurve import VelocityCurves
910

1011

1112
class SlideMode(Enum):
@@ -34,6 +35,8 @@ class CONFIGS:
3435
PITCH_BEND_RANGE: int = 24
3536
MAPPING: Mapping
3637
TOGGLE_SUSTAIN: bool = False
38+
VELOCITY_CURVES: VelocityCurves = VelocityCurves()
39+
DEBUG: bool = False
3740

3841

3942
def read_configs() -> bool:
@@ -57,6 +60,8 @@ def read_configs() -> bool:
5760
CONFIGS.PITCH_BEND_RANGE = c['PITCH_BEND_RANGE']
5861
CONFIGS.MAPPING = c['MAPPING']
5962
CONFIGS.TOGGLE_SUSTAIN = c['TOGGLE_SUSTAIN']
63+
CONFIGS.VELOCITY_CURVES = c['VELOCITY_CURVES']
64+
CONFIGS.DEBUG = c['DEBUG']
6065

6166
except Exception:
6267
print('failed to load saved configurations. Delete config.dill.')
@@ -77,6 +82,8 @@ def save_configs():
7782
'AUTO_SPLIT': CONFIGS.AUTO_SPLIT,
7883
'PITCH_BEND_RANGE': CONFIGS.PITCH_BEND_RANGE,
7984
'MAPPING': CONFIGS.MAPPING,
80-
'TOGGLE_SUSTAIN': CONFIGS.TOGGLE_SUSTAIN
85+
'TOGGLE_SUSTAIN': CONFIGS.TOGGLE_SUSTAIN,
86+
'VELOCITY_CURVES': CONFIGS.VELOCITY_CURVES,
87+
'DEBUG': CONFIGS.DEBUG
8188
}
8289
dill.dump(c, f)

handler.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ class MidiInputHandler():
2020
def __init__(self, out_port: MidiOut):
2121
self.out_port = out_port
2222
self._wallclock = time.time()
23+
self.octave_offset = 4
24+
"""
25+
The lowest note on the Rise 49 is C with octave one less than this.
26+
27+
CC 06 (Data Entry MSB) gives the octave offset when it is updated.
28+
"""
2329

2430
def __call__(self, event, data=None):
2531
mapping = CONFIGS.MAPPING
@@ -31,20 +37,25 @@ def __call__(self, event, data=None):
3137
def tune_and_send_note(note, vel, cc74):
3238

3339
edosteps_from_a4 = mapping.calc_notes_from_a4(note, cc74)
40+
scaled_vel = CONFIGS.VELOCITY_CURVES.get_velocity(note, vel, self.octave_offset, cc74, CONFIGS.DEBUG)
41+
42+
pitchbend = None
3443

3544
if CONFIGS.MPE_MODE:
3645
pitchbend = mapping.calc_pitchbend(note, cc74)
3746
# pitch bend has to go before the note on event
3847
# otherwise equator might not register it.
3948
self.send_pitch_bend(channel, pitchbend)
40-
self.send_note_on(channel, note, vel)
49+
self.send_note_on(channel, note, scaled_vel)
4150

4251
if CONFIGS.SLIDE_MODE == SlideMode.FIXED:
4352
self.send_cc(channel, 74, CONFIGS.SLIDE_FIXED_N)
53+
# NOTE: the vel parameter is raw, before applying velocity curve
4454
tracker.register_on(note, vel, channel, note, channel, edosteps_from_a4, pitchbend)
4555

4656
# pitch bend has to go after note on event
4757
# otherwise strobe 2 complete disregards it
58+
# because of this we have to send it twice
4859
self.send_pitch_bend(channel, pitchbend)
4960
else:
5061
if CONFIGS.AUTO_SPLIT is not None:
@@ -58,7 +69,7 @@ def tune_and_send_note(note, vel, cc74):
5869
send_note = max(0, min(127, send_note))
5970
print('Midi note out of range! Consider using mutliple vst instances in different octaves '
6071
'and split ranges with pitch offsets when in MIDI mode.')
61-
self.send_note_on(send_ch, send_note, vel)
72+
self.send_note_on(send_ch, send_note, scaled_vel)
6273

6374
# if a note overrides another active note in the same input channel,
6475
# stop that note. Prevents ghosts that hang around.
@@ -74,9 +85,13 @@ def do_later():
7485
if CONFIGS.SLIDE_MODE == SlideMode.FIXED:
7586
threading.Thread(target=do_later).start()
7687

88+
# NOTE: the vel parameter is raw, before applying velocity curve
7789
tracker.register_on(note, vel, channel, send_note, send_ch, edosteps_from_a4)
7890

79-
ws_server.send_note_on(edosteps_from_a4, vel)
91+
ws_server.send_note_on(edosteps_from_a4, scaled_vel)
92+
93+
if CONFIGS.DEBUG:
94+
print(f'recv: (note {note}, vel {vel}, cc74 {cc74}), sent: (note {edosteps_from_a4}, {f"pb {pitchbend}, " if CONFIGS.MPE_MODE else ""}vel {scaled_vel})')
8095

8196
if msg_type == midi.NOTE_ON:
8297
note, vel = message[1:3]
@@ -149,6 +164,9 @@ def send_preempt_defaults():
149164
sustain_value = value if not CONFIGS.TOGGLE_SUSTAIN else 127 - value
150165
self.send_cc(c, cc, sustain_value)
151166
ws_server.send_cc(cc, sustain_value)
167+
elif cc == 6: # Data Entry MSB, octave switch
168+
self.octave_offset = value
169+
self.send_cc(c, cc, value)
152170
else:
153171
self.send_cc(c, cc, value)
154172

keytracker.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ def __init__(self):
1717
"""
1818
Race conditions can cause a note on event to be handled before its corresponding
1919
cc74 message. Das not gud.
20-
This flag is set to True upon note off, and will only be set to false when a
20+
This flag is set to True upon note off, and will only be set to false when a
2121
cc74 message has been registered.
22-
22+
2323
If a cc74 message is registered before a note on event, no problem.
2424
If a cc74 message is only registered after a note on event, the cc74 message
2525
has to act as if the note was not yet on, and the appropriate pitch bends and
@@ -34,36 +34,38 @@ def __init__(self):
3434

3535
self.on_velocity_received = 0
3636
"""
37-
Stores the on velocity that was received in the event that the note cannot be
37+
Stores the raw on velocity that was received in the event that the note cannot be
3838
immediately forwarded as it is pending a cc74 message.
39+
40+
This is the raw value before velocity curve is applied.
3941
"""
4042

4143
self.midi_note_sent = 0
4244
"""
4345
Represents the midi note number that was sent as a result
4446
of the input on this current channel.
45-
47+
4648
In MIDI mode, this note number does not correlate to the
4749
input note that was received on this channel
48-
50+
4951
This is used to turn off the appropriate midi note
5052
should there be a 'on' event on two notes on the same channel
5153
on the input when in MIDI mode.
52-
54+
5355
This prevents hanging notes in MIDI mode when there are more than
5456
16 simultaneous input notes.
5557
"""
5658

5759
self.channel_sent = 0
5860
"""
5961
Represents the channel that this note was sent to.
60-
62+
6163
In MIDI mode, the channel of the input note does not
6264
correlate to the channel of the output note.
63-
65+
6466
This keeps track of the channel the note was sent on
6567
so that the appropriate midi note can be turned off.
66-
68+
6769
This prevents hanging notes in MIDI mode when there are more than
6870
16 simultaneous input notes.
6971
"""

0 commit comments

Comments
 (0)