Skip to content

Commit df9afe6

Browse files
committed
Refining mapping process, added pinging process and output printing
1 parent 6f9ee7b commit df9afe6

File tree

1 file changed

+50
-23
lines changed

1 file changed

+50
-23
lines changed

isobar/io/cv/output.py

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from ..output import OutputDevice
2+
import numpy
23
import time
34

45
def get_cv_output_devices():
@@ -16,9 +17,12 @@ class CVOutputDevice(OutputDevice):
1617

1718
def audio_callback(self, out_data, frames, time, status):
1819
for channel in range(self.channels):
19-
value = self.channel_notes[channel]
20+
value = self.channel_cvs[channel]
2021
if value is None:
2122
value = 0.0
23+
if self.ping_flag[channel]:
24+
value = 10.0
25+
self.ping_flag[channel] = False
2226
out_data[:, channel] = value
2327

2428
def __init__(self, device_name=None, sample_rate=44100):
@@ -58,14 +62,20 @@ def __init__(self, device_name=None, sample_rate=44100):
5862

5963
# Expert Sleepers ES-8 supports entire -10V to +10V range
6064
self.output_voltage_max = 10
65+
# Number of channels available
6166
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
6473

6574
print("Started CV output with %d channels" % self.channels)
6675

6776
# 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):
6979
"""
7080
Distribute CV outputs from a single MIDI channel.
7181
@@ -77,8 +87,8 @@ def set_channels(self, midi_channel=0, note_channel=None, velocity_channel=None,
7787
velocity_channel (int): CV channel to output note velocity
7888
gate_channel (int): CV channel to output current gate (10V when open)
7989
"""
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)
8292

8393
# Mappings in a list of [note, velocity, gate]
8494
self.channel_map[midi_channel] = [note_channel, velocity_channel, gate_channel]
@@ -92,14 +102,29 @@ def reset_channel(self, midi_channel):
92102
Args:
93103
midi_channel (int): MIDI channel to erase CV channel pairings from
94104
"""
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)
96114

97115
def show_channels(self):
98116
"""
99117
Show all currently assigned channels.
100118
101119
Display all channels that are currently assigned in a tree view
102120
"""
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])
103128

104129
def ping_channel(self, channel):
105130
"""
@@ -110,48 +135,50 @@ def ping_channel(self, channel):
110135
Args:
111136
channel (int): CV channel to output ping
112137
"""
113-
self.note_on(channel=channel)
114-
time.sleep(0.1)
115-
self.note_off(channel=channel)
138+
self.ping_flag[channel] = True
116139

117140
# TODO: Implement bipolar setting
118141
def _note_index_to_amplitude(self, note, bipolar=False):
119142
# Reduce to -5V to 5V if bipolar
120143
note_float = (note / (12 * self.output_voltage_max)) - (0.5 * bipolar)
121144
if note_float < -1.0 or note_float > 1.0:
122145
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)
125146
return note_float
126147

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+
127153
def note_on(self, note=60, velocity=64, channel=None):
128154
note_float = self._note_index_to_amplitude(note)
155+
print("Note On: %d, CV %f" % (note, note_float))
129156
# 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):
132159
# Distribute outputs (note, velocity, gate)
133160
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)
136163
# Otherwise select the next open channel
137164
else:
138-
for index, channel_note in enumerate(self.channel_notes):
165+
for index, channel_note in enumerate(self.channel_cvs):
139166
if channel_note is None:
140-
self.channel_notes[index] = note_float
167+
self._set_channel_value(index, None)
141168
break
142169

143170
def note_off(self, note=60, channel=None):
144171
# See if the specified MIDI channel exists
145-
channel_set = self.channel_map[channel]
172+
channel_set = self.channel_map.get(channel)
146173
if (channel_set is not None):
147174
# 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)
150177
# Otherwise select the next open channel
151178
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):
153180
if channel_note is not None and channel_note == note_float:
154-
self.channel_notes[index] = None
181+
self._set_channel_value(index, None)
155182

156183
def control(self, control, value, channel=0):
157184
pass

0 commit comments

Comments
 (0)