1
1
from ..output import OutputDevice
2
+ import numpy
2
3
import time
3
4
4
5
def get_cv_output_devices ():
@@ -16,9 +17,12 @@ class CVOutputDevice(OutputDevice):
16
17
17
18
def audio_callback (self , out_data , frames , time , status ):
18
19
for channel in range (self .channels ):
19
- value = self .channel_notes [channel ]
20
+ value = self .channel_cvs [channel ]
20
21
if value is None :
21
22
value = 0.0
23
+ if self .ping_flag [channel ]:
24
+ value = 10.0
25
+ self .ping_flag [channel ] = False
22
26
out_data [:, channel ] = value
23
27
24
28
def __init__ (self , device_name = None , sample_rate = 44100 ):
@@ -58,14 +62,20 @@ def __init__(self, device_name=None, sample_rate=44100):
58
62
59
63
# Expert Sleepers ES-8 supports entire -10V to +10V range
60
64
self .output_voltage_max = 10
65
+ # Number of channels available
61
66
self .channels = self .stream .channels
62
- self .channel_notes = [None ] * self .channels
63
- self .channel_map = [None ] * self .channels
67
+ # Channel output CV values
68
+ self .channel_cvs = [None ] * self .channels
69
+ # Channel MIDI to CV mappings
70
+ self .channel_map = {}
71
+ # Ping flag
72
+ self .ping_flag = [False ] * self .channels
64
73
65
74
print ("Started CV output with %d channels" % self .channels )
66
75
67
76
# TODO: Retrigger event possible?
68
- def set_channels (self , midi_channel = 0 , note_channel = None , velocity_channel = None , gate_channel = None ):
77
+ # TODO: Add polyphony handling and settings
78
+ def map_channels (self , midi_channel = 0 , note_channel = None , velocity_channel = None , gate_channel = None ):
69
79
"""
70
80
Distribute CV outputs from a single MIDI channel.
71
81
@@ -77,8 +87,8 @@ def set_channels(self, midi_channel=0, note_channel=None, velocity_channel=None,
77
87
velocity_channel (int): CV channel to output note velocity
78
88
gate_channel (int): CV channel to output current gate (10V when open)
79
89
"""
80
- if not all ((ch == None or ch >= 0 ) for ch in [midi_channel , note_channel , velocity_channel , gate_channel ]):
81
- print ("set_channels: All set channels need to be an integer greater than 0" )
90
+ if not all ((ch == None or ( ch >= 0 and ch <= self . channels ) ) for ch in [midi_channel , note_channel , velocity_channel , gate_channel ]):
91
+ print ("set_channels: All set channels need to be an integer greater than 0 and less than the channel max (%d)" % self . channels )
82
92
83
93
# Mappings in a list of [note, velocity, gate]
84
94
self .channel_map [midi_channel ] = [note_channel , velocity_channel , gate_channel ]
@@ -92,14 +102,29 @@ def reset_channel(self, midi_channel):
92
102
Args:
93
103
midi_channel (int): MIDI channel to erase CV channel pairings from
94
104
"""
95
- self .channel_map [midi_channel ] = None
105
+ # Set all outputs to 0
106
+ mappings = self .channel_map .get (midi_channel )
107
+ if (mappings ):
108
+ for ch in self .channel_map [midi_channel ]:
109
+ self ._set_channel_value (ch , None )
110
+ del self .channel_map [midi_channel ]
111
+ print ("MIDI channel %d mappings removed" % midi_channel )
112
+ else :
113
+ print ("No MIDI channel %d mappings found" % midi_channel )
96
114
97
115
def show_channels (self ):
98
116
"""
99
117
Show all currently assigned channels.
100
118
101
119
Display all channels that are currently assigned in a tree view
102
120
"""
121
+ # Loop through dictionary for outputs
122
+ for midi_channel in self .channel_map :
123
+ # Print MIDI title
124
+ print ("MIDI Channel %d" % midi_channel )
125
+ print ("\t \\ Note: ch%d" % midi_channel [0 ])
126
+ print ("\t \\ Velocity: ch%d" % midi_channel [0 ])
127
+ print ("\t \\ Gate: ch%d" % midi_channel [0 ])
103
128
104
129
def ping_channel (self , channel ):
105
130
"""
@@ -110,48 +135,50 @@ def ping_channel(self, channel):
110
135
Args:
111
136
channel (int): CV channel to output ping
112
137
"""
113
- self .note_on (channel = channel )
114
- time .sleep (0.1 )
115
- self .note_off (channel = channel )
138
+ self .ping_flag [channel ] = True
116
139
117
140
# TODO: Implement bipolar setting
118
141
def _note_index_to_amplitude (self , note , bipolar = False ):
119
142
# Reduce to -5V to 5V if bipolar
120
143
note_float = (note / (12 * self .output_voltage_max )) - (0.5 * bipolar )
121
144
if note_float < - 1.0 or note_float > 1.0 :
122
145
raise ValueError ("Note index %d is outside the voltage range supported by this device" % note )
123
- print ("note %d, float %f" % (note , note_float ))
124
- print (12 * self .output_voltage_max )
125
146
return note_float
126
147
148
+ def _set_channel_value (self , channel , cv ):
149
+ if cv and (cv < - 1.0 or cv > 1.0 ):
150
+ raise ValueError ("CV value %f is outside the voltage range supported by this device" % cv )
151
+ self .channel_cvs [channel ] = cv
152
+
127
153
def note_on (self , note = 60 , velocity = 64 , channel = None ):
128
154
note_float = self ._note_index_to_amplitude (note )
155
+ print ("Note On: %d, CV %f" % (note , note_float ))
129
156
# See if the specified MIDI channel exists
130
- channel_set = self .channel_map [ channel ]
131
- if (channel_set is not None ):
157
+ channel_set = self .channel_map . get ( channel )
158
+ if (channel_set ):
132
159
# Distribute outputs (note, velocity, gate)
133
160
output_cvs = [note_float , (velocity / 127 ), 1.0 ]
134
- for i in range ( len ( channel_set ) ):
135
- self .channel_notes [ channel_set [ i ]] = output_cvs [ i ]
161
+ for ch , cv in zip ( channel_set , output_cvs ):
162
+ self ._set_channel_value ( ch , cv )
136
163
# Otherwise select the next open channel
137
164
else :
138
- for index , channel_note in enumerate (self .channel_notes ):
165
+ for index , channel_note in enumerate (self .channel_cvs ):
139
166
if channel_note is None :
140
- self .channel_notes [ index ] = note_float
167
+ self ._set_channel_value ( index , None )
141
168
break
142
169
143
170
def note_off (self , note = 60 , channel = None ):
144
171
# See if the specified MIDI channel exists
145
- channel_set = self .channel_map [ channel ]
172
+ channel_set = self .channel_map . get ( channel )
146
173
if (channel_set is not None ):
147
174
# Turn all outputs off
148
- for i in range ( len ( channel_set )) :
149
- self .channel_notes [ channel_set [ i ]] = 0
175
+ for ch in channel_set :
176
+ self ._set_channel_value ( ch , 0 )
150
177
# Otherwise select the next open channel
151
178
note_float = self ._note_index_to_amplitude (note )
152
- for index , channel_note in enumerate (self .channel_notes ):
179
+ for index , channel_note in enumerate (self .channel_cvs ):
153
180
if channel_note is not None and channel_note == note_float :
154
- self .channel_notes [ index ] = None
181
+ self ._set_channel_value ( index , None )
155
182
156
183
def control (self , control , value , channel = 0 ):
157
184
pass
0 commit comments