Firmware

ctpatch

Hype41/100

Python module for reading, updating and writing Novation Circuit Tracks patches

ctpatch is custom firmware for the Novation Circuit Tracks, by @martin-stone. Open source under MIT, 8 stars on GitHub.

forNovation Circuit Tracks·by@martin-stone

Unclaimed. Is this yours? Sign in with GitHub as @martin-stone to claim it.

Downloads
0
Stars
8
Forks
0
Commits
7
Last commit
Get the source

No release files yet. Build from source.

MIT· martin-stone/ctpatch·downloads come from the creator’s GitHub releases

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