overdub

ctpatch

martin-stone/ctpatch · ★ 8

Read and write patches in Python.

README from GitHub, MIT

ctpatch.py

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))