In Python, read, modify and write .syx patch files for the Novation Circuit Tracks.
Requires Python version >= 3.10
See example.py for an example of reading, modifying, and writing patches, and sending patches to the device.
The Python type annotations make editor autocomplete quite effective, but you can also see the structure of the loaded patch object with Python's "pretty print"...
import ctpatch
from pprint import pprint
patch = ctpatch.read_syx("example.syx")
pprint(patch)
This produces the following output ...
PatchSysex(header=Header(sysex=240,
mfr_id=b'\x00 )',
prod_type=1,
prod_num=100),
command=ReplaceCurrentPatchCommand(command_id=0,
location=0,
_reserved=0),
meta=Meta(_name=b'saw dst ',
category=2,
genre=0,
_reserved=b'\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00'),
voice=Voice(polyphony_mode=<PolyphonyMode.MONO_AG: 1>,
portamento_rate=0,
pre_glide=64,
keyboard_octave=62),
oscillators=[Osc(wave=<OscWaveform.SAWTOOTH: 2>,
wave_interpolate=127,
pulse_width_index=0,
virtual_sync_depth=0,
density=0,
density_detune=0,
semitones=64,
cents=64,
pitch_bend=76),
Osc(wave=<OscWaveform.SAWTOOTH: 2>,
wave_interpolate=127,
pulse_width_index=64,
virtual_sync_depth=0,
density=0,
density_detune=0,
semitones=64,
cents=64,
pitch_bend=76)],
mixer=Mixer(osc1_level=127,
osc2_level=0,
ring_mod_level12=0,
noise_level=0,
pre_fx_level=64,
post_fx_level=64),
filter=Filter(routing=0,
drive=0,
drive_type=<DistortionType.DIODE: 0>,
type=<FilterType.LOW_PASS_24DB: 1>,
frequency=38,
track=0,
resonance=0,
q_normalise=64,
env2_to_freq=64),
envelopes=[Envelope(velocity_or_delay=92,
attack=0,
decay=93,
sustain=0,
release=40),
Envelope(velocity_or_delay=64,
attack=0,
decay=57,
sustain=0,
release=45),
Envelope(velocity_or_delay=0,
attack=10,
decay=70,
sustain=64,
release=40)],
lfos=[Lfo(waveform=<LfoWaveform.TRIANGLE: 1>,
phase_offset=0,
slew_rate=0,
delay=0,
delay_sync=0,
rate=0,
rate_sync=0,
flags=LfoFlags(one_shot=0, key_sync=0, common_sync=0, delay_trigger=0, fade_mode=0)),
Lfo(waveform=<LfoWaveform.SINE: 0>,
phase_offset=0,
slew_rate=0,
delay=0,
delay_sync=0,
rate=68,
rate_sync=0,
flags=LfoFlags(one_shot=0, key_sync=0, common_sync=0, delay_trigger=0, fade_mode=0))],
fx=Fx(distortion_level=0,
_fx_reserved1=0,
chorus_level=0,
_fx_reserved2=0,
_fx_reserved3=0,
equaliser_bass_frequency=64,
equaliser_bass_level=64,
equaliser_mid_frequency=64,
equaliser_mid_level=64,
equaliser_treble_frequency=125,
equaliser_treble_level=64,
_fx_reserved45678=b'\x00\x00\x00\x00\x00',
distortion_type=<DistortionType.DIODE: 0>,
distortion_compensation=100,
chorus_type=1,
chorus_rate=20,
chorus_rate_sync=0,
chorus_feedback=74,
chorus_mod_depth=64,
chorus_delay=64),
mod_matrix=[ModMatrix(source1=<ModMatrixSource.LFO_1_PLUS: 6>,
source2=<ModMatrixSource.DIRECT: 0>,
depth=64,
destination=<ModMatrixDestination.FILTER_FREQUENCY: 12>),
ModMatrix(source1=<ModMatrixSource.VELOCITY: 4>,
source2=<ModMatrixSource.DIRECT: 0>,
depth=78,
destination=<ModMatrixDestination.FILTER_RESONANCE: 13>),
...etc.
],
macro_knobs=[MacroKnob(position=0,
ranges=[MacroKnobRange(destination=<MacroKnobDestination.LFO1_RATE: 39>,
start_pos=0,
end_pos=127,
depth=127),
MacroKnobRange(destination=<MacroKnobDestination.NO_DESTINATION: 0>,
start_pos=0,
end_pos=127,
depth=64),
...etc.
],
footer=Footer(eox=247))