Minor Python module installation tweak
[jack_mixer.git] / src / _jack_mixer.pyx
blob32eb50ce0f41e57af6af3d05a4f361f034d1e6d6
2 # cython: language_level=3
4 """Python bindings for jack_mixer.c and scale.c using Cython."""
6 __all__ = ("Scale", "MidiBehaviour", "Mixer")
8 import enum
10 from _jack_mixer cimport *
13 cdef void midi_change_callback_func(void *userdata) with gil:
14 """Wrapper for a Python callback function for MIDI input."""
15 channel = <object> userdata
16 channel._midi_change_callback()
19 class MidiBehaviour(enum.IntEnum):
20 """MIDI control behaviour.
22 `JUMP_TO_VALUE`
24 Received MIDI control messages affect mixer directly.
26 `PICK_UP`
28 Received MIDI control messages have to match up with current
29 mixer value first (within a small margin), before further
30 changes take effect.
31 """
32 JUMP_TO_VALUE = 0
33 PICK_UP = 1
36 cdef class Scale:
37 """Mixer level scale representation.
39 Wraps `jack_mixer_scale_t` struct.
40 """
42 cdef jack_mixer_scale_t _scale
44 def __cinit__(self):
45 self._scale = scale_create()
47 def __dealloc__(self):
48 if self._scale:
49 scale_destroy(self._scale)
51 cpdef bool add_threshold(self, float db, float scale_value):
52 """Add scale treshold."""
53 return scale_add_threshold(self._scale, db, scale_value)
55 cpdef void remove_thresholds(self):
56 """Remove scale threshold."""
57 scale_remove_thresholds(self._scale)
59 cpdef void calculate_coefficients(self):
60 """Calculate scale coefficents."""
61 scale_calculate_coefficients(self._scale)
63 cpdef double db_to_scale(self, double db):
64 """Return scale value responding to given dB value."""
65 return scale_db_to_scale(self._scale, db)
67 cpdef double scale_to_db(self, double scale_value):
68 """Return dB value responding to given scale value."""
69 return scale_scale_to_db(self._scale, scale_value)
72 cdef class Mixer:
73 """Jack Mixer representation.
75 Wraps `jack_mixer_t` struct.
76 """
78 cdef jack_mixer_t _mixer
79 cdef bool _stereo
81 def __cinit__(self, name, stereo=True):
82 self._stereo = stereo
83 self._mixer = mixer_create(name.encode('utf-8'), stereo)
85 def __dealloc__(self):
86 if self._mixer:
87 mixer_destroy(self._mixer)
89 def destroy(self):
90 """Close mixer Jack client and destroy mixer instance.
92 The instance must not be used anymore after calling this
93 method.
94 """
95 if self._mixer:
96 mixer_destroy(self._mixer)
98 @property
99 def channels_count(self):
100 """Number of mixer channels."""
101 return mixer_get_channels_count(self._mixer)
103 @property
104 def client_name(self):
105 """Jack client name of mixer."""
106 return mixer_get_client_name(self._mixer).decode('utf-8')
108 @property
109 def last_midi_cc(self):
110 """Last received MIDI control change message."""
111 return mixer_get_last_midi_cc(self._mixer)
113 @last_midi_cc.setter
114 def last_midi_cc(self, int channel):
115 mixer_set_last_midi_cc(self._mixer, channel)
117 @property
118 def midi_behavior_mode(self):
119 """MIDI control change behaviour mode.
121 See `MidiBehaviour` enum for more information.
123 return MidiBehaviour(mixer_get_midi_behavior_mode(self._mixer))
125 @midi_behavior_mode.setter
126 def midi_behavior_mode(self, mode):
127 mixer_set_midi_behavior_mode(self._mixer,
128 mode.value if isinstance(mode, MidiBehaviour) else mode)
130 cpdef add_channel(self, channel_name, stereo=None):
131 """Add a stereo or mono input channel with given name to the mixer.
133 Returns a `Channel` instance when successfull or `None` if channel
134 creation failed.
136 cdef jack_mixer_channel_t chan_ptr
137 if stereo is None:
138 stereo = self._stereo
140 chan_ptr = mixer_add_channel(self._mixer, channel_name.encode('utf-8'), stereo)
141 if chan_ptr == NULL:
142 return None
144 return Channel.new(chan_ptr)
146 cpdef add_output_channel(self, channel_name, stereo=None, system=False):
147 """Add a stereo or mono output channel with given name to the mixer.
149 Returns a `OutputChannel` instance when successfull or `None` if
150 channel creation failed.
152 cdef jack_mixer_output_channel_t chan_ptr
154 if stereo is None:
155 stereo = self._stereo
157 chan_ptr = mixer_add_output_channel(self._mixer, channel_name.encode('utf-8'), stereo,
158 system)
160 if chan_ptr == NULL:
161 return None
163 return OutputChannel.new(chan_ptr)
166 cdef class Channel:
167 """Jack Mixer (input) channel representation.
169 Wraps `jack_mixer_channel_t` struct.
172 cdef jack_mixer_channel_t _channel
173 cdef object _midi_change_callback
175 def __init__(self):
176 raise TypeError("Channel instances can only be created via Mixer.add_channel().")
178 @staticmethod
179 cdef Channel new(jack_mixer_channel_t chan_ptr):
180 """Create a new Channel instance.
182 A pointer to an initialized `jack_mixer_channel_t` struct must be
183 passed in.
185 This should not be called directly but only via `Mixer.add_channel()`.
187 cdef Channel channel = Channel.__new__(Channel)
188 channel._channel = chan_ptr
189 return channel
191 @property
192 def abspeak(self):
193 """Absolute peak of channel meter.
195 Set to `None` to reset the absolute peak to -inf.
196 Trying to set it to any other value will raise a `ValueError`.
198 return channel_abspeak_read(self._channel)
200 @abspeak.setter
201 def abspeak(self, reset):
202 if reset is not None:
203 raise ValueError("abspeak can only be reset (set to None)")
204 channel_abspeak_reset(self._channel)
206 @property
207 def balance(self):
208 """Channel balance property."""
209 return channel_balance_read(self._channel)
211 @balance.setter
212 def balance(self, double bal):
213 channel_balance_write(self._channel, bal)
215 @property
216 def is_stereo(self):
217 """Is channel stereo or mono?"""
218 return channel_is_stereo(self._channel)
220 @property
221 def kmeter(self):
222 """Read channel kmeter.
224 If channel is stereo, return a four-item tupel with
225 ``(peak_left, peak_right, rms_left, rms_right)`` value.
226 If channel is mono, return a tow-item tupel with ``(peak, rms)`` value.
228 cdef double peak_left, peak_right, left_rms, right_rms
230 if channel_is_stereo(self._channel):
231 channel_stereo_kmeter_read(
232 self._channel, &peak_left, &peak_right, &left_rms, &right_rms)
233 return (peak_left, peak_right, left_rms, right_rms)
234 else:
235 channel_mono_kmeter_read(self._channel, &peak_left, &left_rms)
236 return (peak_left, left_rms)
238 @property
239 def meter(self):
240 """Read channel meter.
242 If channel is stereo, return a two-item tupel with (left, right) value.
243 If channel is mono, return a tupel with the value as the only item.
245 cdef double left, right
247 if channel_is_stereo(self._channel):
248 channel_stereo_meter_read(self._channel, &left, &right)
249 return (left, right)
250 else:
251 channel_mono_meter_read(self._channel, &left)
252 return (left,)
254 @property
255 def midi_change_callback(self):
256 """Function to be called when a channel property is changed via MIDI.
258 The callback function takes no arguments.
260 Assign `None` to remove any existing callback.
262 return self._midi_change_callback
264 @midi_change_callback.setter
265 def midi_change_callback(self, callback):
266 self._midi_change_callback = callback
267 if callback is None:
268 channel_set_midi_change_callback(self._channel, NULL, NULL)
269 else:
270 channel_set_midi_change_callback(self._channel,
271 &midi_change_callback_func,
272 <void *>self)
274 @property
275 def name(self):
276 """Channel name property."""
277 return channel_get_name(self._channel).decode('utf-8')
279 @name.setter
280 def name(self, newname):
281 channel_rename(self._channel, newname.encode('utf-8'))
283 @property
284 def out_mute(self):
285 """Channel solo status property."""
286 return channel_is_out_muted(self._channel)
288 @out_mute.setter
289 def out_mute(self, bool value):
290 if value:
291 channel_out_mute(self._channel)
292 else:
293 channel_out_unmute(self._channel)
295 @property
296 def solo(self):
297 """Channel solo status property."""
298 return channel_is_soloed(self._channel)
300 @solo.setter
301 def solo(self, bool value):
302 if value:
303 channel_solo(self._channel)
304 else:
305 channel_unsolo(self._channel)
307 @property
308 def midi_in_got_events(self):
309 """Did channel receive any MIDI events assigned to one of its controls?
311 Reading this property also resets it to False.
313 return channel_get_midi_in_got_events(self._channel)
315 @property
316 def midi_scale(self):
317 """MIDI scale used by channel."""
318 raise AttributeError("midi_scale can only be set.")
320 @midi_scale.setter
321 def midi_scale(self, Scale scale):
322 channel_set_midi_scale(self._channel, scale._scale)
324 @property
325 def volume(self):
326 """Channel volume property."""
327 return channel_volume_read(self._channel)
329 @volume.setter
330 def volume(self, double vol):
331 channel_volume_write(self._channel, vol)
333 @property
334 def balance_midi_cc(self):
335 """MIDI CC assigned to control channel balance."""
336 return channel_get_balance_midi_cc(self._channel)
338 @balance_midi_cc.setter
339 def balance_midi_cc(self, int cc):
340 channel_set_balance_midi_cc(self._channel, cc)
342 @property
343 def mute_midi_cc(self):
344 """MIDI CC assigned to control channel mute status."""
345 return channel_get_mute_midi_cc(self._channel)
347 @mute_midi_cc.setter
348 def mute_midi_cc(self, int cc):
349 channel_set_mute_midi_cc(self._channel, cc)
351 @property
352 def solo_midi_cc(self):
353 """MIDI CC assigned to control channel solo status."""
354 return channel_get_solo_midi_cc(self._channel)
356 @solo_midi_cc.setter
357 def solo_midi_cc(self, int cc):
358 channel_set_solo_midi_cc(self._channel, cc)
360 @property
361 def volume_midi_cc(self):
362 """MIDI CC assigned to control channel volume."""
363 return channel_get_volume_midi_cc(self._channel)
365 @volume_midi_cc.setter
366 def volume_midi_cc(self, int cc):
367 channel_set_volume_midi_cc(self._channel, cc)
369 def autoset_balance_midi_cc(self):
370 """Auto assign MIDI CC for channel balance."""
371 channel_autoset_balance_midi_cc(self._channel)
373 def autoset_mute_midi_cc(self):
374 """Auto assign MIDI CC for channel mute status."""
375 channel_autoset_mute_midi_cc(self._channel)
377 def autoset_solo_midi_cc(self):
378 """Auto assign MIDI CC for channel solo status."""
379 channel_autoset_solo_midi_cc(self._channel)
381 def autoset_volume_midi_cc(self):
382 """Auto assign MIDI CC for channel volume."""
383 channel_autoset_volume_midi_cc(self._channel)
385 def remove(self):
386 """Remove channel."""
387 remove_channel(self._channel)
389 def set_midi_cc_balance_picked_up(self, bool status):
390 """Set whether balance value is out-of-sync with MIDI control."""
391 channel_set_midi_cc_balance_picked_up(self._channel, status)
393 def set_midi_cc_volume_picked_up(self, bool status):
394 """Set whether volume value is out-of-sync with MIDI control."""
395 channel_set_midi_cc_volume_picked_up(self._channel, status)
399 cdef class OutputChannel(Channel):
400 """Jack Mixer output channel representation.
402 Wraps `jack_mixer_output_channel_t` struct.
404 Inherits from `Channel` class.
407 cdef jack_mixer_output_channel_t _output_channel
409 def __init__(self):
410 raise TypeError("OutputChannel instances can only be created via "
411 "Mixer.add_output_channel().")
413 @staticmethod
414 cdef OutputChannel new(jack_mixer_output_channel_t chan_ptr):
415 """Create a new OutputChannel instance.
417 A pointer to an initialzed `jack_mixer_output_channel_t` struct must
418 be passed in.
420 This should not be called directly but only via
421 `Mixer.add_output_channel()`.
423 cdef OutputChannel channel = OutputChannel.__new__(OutputChannel)
424 channel._output_channel = chan_ptr
425 channel._channel = <jack_mixer_channel_t> chan_ptr
426 return channel
428 @property
429 def prefader(self):
430 return output_channel_is_prefader(self._output_channel)
432 @prefader.setter
433 def prefader(self, bool pfl):
434 output_channel_set_prefader(self._output_channel, pfl)
436 def is_in_prefader(self, Channel channel):
437 """Is a channel set as prefader?"""
438 return output_channel_is_in_prefader(self._output_channel, channel._channel)
440 def set_in_prefader(self, Channel channel, bool value):
441 """Set a channel as prefader."""
442 output_channel_set_in_prefader(self._output_channel, channel._channel, value)
444 def is_muted(self, Channel channel):
445 """Is a channel set as muted?"""
446 return output_channel_is_muted(self._output_channel, channel._channel)
448 def set_muted(self, Channel channel, bool value):
449 """Set a channel as muted."""
450 output_channel_set_muted(self._output_channel, channel._channel, value)
452 def is_solo(self, Channel channel):
453 """Is a channel set as solo?"""
454 return output_channel_is_solo(self._output_channel, channel._channel)
456 def set_solo(self, Channel channel, bool value):
457 """Set a channel as solo."""
458 output_channel_set_solo(self._output_channel, channel._channel, value)
460 def remove(self):
461 """Remove output channel."""
462 remove_output_channel(self._output_channel)