-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
257 lines (212 loc) · 8.22 KB
/
main.py
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
import os.path
import traceback
from rtmidi import MidiIn, MidiOut
from rtmidi.midiutil import open_midiinput, open_midioutput
import configs
import convert
import ws_server
from configs import SlideMode, CONFIGS
from handler import MidiInputHandler
from mapping import Mapping, MapParsingError
from split import SplitData
import tkinter.filedialog as filedialog
import tkinter as tk
root = tk.Tk()
root.withdraw()
def select_splits():
while True:
split_pos_strs = input('enter channel split position(s) or leave blank for 1 output channel only: ').split(' ')
if len(split_pos_strs) > 15:
print('Too many split positions! Maximum 15 split positions. (16 channels)')
continue
splitpos = []
prev_s = 0
problem = False
for s in split_pos_strs:
try:
pos = convert.notename_to_midinum(s)
if pos > prev_s:
splitpos.append(pos)
else:
print('Split positions must be in ascending order!')
problem = True
break
except Exception:
print(f'{s} is not a valid note name')
problem = True
break
if not problem:
break
splitpos.append(128)
CONFIGS.SPLITS = SplitData() # reset splits
prev_split_pos = 0
for ch, s in enumerate(splitpos):
while True:
i = input(f'enter midi output offset for split range channel {ch + 1} '
f'(range {convert.midinum_to_12edo_name(prev_split_pos)} - '
f'{convert.midinum_to_12edo_name(s - 1)}): ')
try:
offset = int(i)
CONFIGS.SPLITS.add_split(splitpos[ch], offset)
except ValueError:
print('enter a valid number!')
continue
break
prev_split_pos = s
def select_mapping(search_default=False):
print('')
print('SEABOARD MAPPING SELECTION')
print('')
root.deiconify()
if search_default:
if os.path.isfile('mappings/default.sbmap'):
try:
CONFIGS.MAPPING = Mapping('mappings/default.sbmap')
print('default.sbmap mapping found. Using default mapping.')
return
except MapParsingError as e:
print(e)
print('Unable to parse default map')
except Exception:
print('unknown error:')
traceback.print_exc()
while True:
path = filedialog.askopenfilename(
title='Choose seaboard map file',
initialdir='./',
filetypes=(('Seaboard map files', '*.sbmap'),)
)
if not os.path.isfile(path):
print(f"{path} doesn't exist!")
try:
CONFIGS.MAPPING = Mapping(path)
if CONFIGS.AUTO_SPLIT is not None:
CONFIGS.AUTO_SPLIT = SplitData(CONFIGS.MAPPING) # Update autosplit to new mapping
break
except MapParsingError as e:
print(e)
except Exception:
print('unknown error:')
traceback.print_exc()
root.withdraw()
def select_pitch_bend_range():
while True:
try:
i = input('Enter pitch bend +/- range as per Equator/VST/ROLI Dashboard: ').strip()
pb = int(i)
if pb <= 1:
print('Pitch bend range has to be 1 or more')
continue
CONFIGS.PITCH_BEND_RANGE = pb
return
except Exception:
pass
def intable(s):
"""Check if a string can be converted to an int"""
try:
int(s)
return True
except ValueError:
return False
if __name__ == '__main__':
print('microtonal seaboard retuner v0.5.1')
has_read_configs = configs.read_configs()
if not has_read_configs:
select_mapping(search_default=True)
print('')
print('MIDI IN/OUT DEVICE SELECTION')
print('')
print('Select the Seaboard MIDI input device:')
seaboard: MidiIn = open_midiinput()[0]
print('Select the virtual MIDI output port that gets sent to the DAW/VST/Program:')
virtual_port: MidiOut = open_midioutput()[0]
if not has_read_configs:
print('')
select_pitch_bend_range()
print('')
print(f'Starting microtonal message forwarding in '
f'{"MPE" if CONFIGS.MPE_MODE else "MIDI"} mode...')
ws_server.start_ws_server()
seaboard.set_callback(MidiInputHandler(virtual_port))
print("""
supported commands:
mpe set to mpe mode (default)
midi set to midi mode
slide <n>|prs|rel|abs|bip set slide message forwarding mode.
<n>: fixed slide value (choose from 0-127)
prs: map to press dimension
rel: emulate relative slide mode
abs: emulate absolute slide mode
bip: (default) emulate bipolar mode
split set split points (for midi mode)
autosplit toggle auto-split for Pianoteq (for midi mode)
map select new .sbmap file
pb change pitch bend amount
sus toggles sustain pedal polarity
save saves all current settings (not automatic)
exit exit the program""")
while True:
s = input('>> ').strip().lower()
if s == 'exit':
print('closing port connections')
del virtual_port
del seaboard
exit(0)
if s == 'save':
configs.save_configs()
elif s == 'mpe':
print('MPE mode active')
CONFIGS.MPE_MODE = True
elif s == 'midi':
print('MIDI mode active')
CONFIGS.MPE_MODE = False
elif s.startswith('slide'):
if 'none' in s:
CONFIGS.SLIDE_MODE = SlideMode.FIXED
CONFIGS.SLIDE_FIXED_N = 64
print('Defaulting slide dimension (cc74) to 64')
elif 'prs' in s or 'press' in s:
CONFIGS.SLIDE_MODE = SlideMode.PRESS
print('Forwarding press dimension to cc74')
elif 'rel' in s or 'relative' in s:
CONFIGS.SLIDE_MODE = SlideMode.RELATIVE
print('Relative slide mode activated')
elif 'abs' in s or 'absolute' in s:
CONFIGS.SLIDE_MODE = SlideMode.ABSOLUTE
print('Absolute slide mode activated')
elif 'bip' in s or 'bipolar' in s:
CONFIGS.SLIDE_MODE = SlideMode.BIPOLAR
print('Bipolar slide mode activated')
else:
try:
n = int(s[5:])
if 0 > n > 127:
print('Default slide value must be an integer from 0-127 inclusive')
continue
CONFIGS.SLIDE_MODE = SlideMode.FIXED
CONFIGS.SLIDE_FIXED_N = n
continue
except Exception:
print("""Unsupported slide mode. Valid slide modes are:
<n>: fixed slide value (choose from 0-127)
prs: map to press dimension
rel: emulate relative slide mode
abs: emulate absolute slide mode
bip: (default) emulate bipolar mode
""")
pass
elif s == 'split':
select_splits()
elif s == 'autosplit':
if CONFIGS.AUTO_SPLIT is not None:
CONFIGS.AUTO_SPLIT = None
else:
CONFIGS.AUTO_SPLIT = SplitData(CONFIGS.MAPPING)
print(f'Auto Split: {"on" if CONFIGS.AUTO_SPLIT is not None else "off"}')
elif s == 'map':
select_mapping()
elif s == 'pb':
select_pitch_bend_range()
elif s == 'sus':
CONFIGS.TOGGLE_SUSTAIN = not CONFIGS.TOGGLE_SUSTAIN
print(f'Invert sustain: {"on" if CONFIGS.TOGGLE_SUSTAIN else "off"}')