This note describes a method to output Eurorack CV (control voltage) signals from synthio using the PCM510x I2S DAC.
Rather than employing CV-like object controls such as Envelope and LFO to only adjust the parameters of other synthio objects, it would be useful to also control physically external devices such as the CV inputs of Eurorack modules. To do that we'll need to configure the synthio.Note object to ignore its typical behavior as an oscillator. Oh, and it would be handy to have an I2S DAC on-hand with a DC-coupled output that's capable of positive and negative output voltage that can be connected to a Eurorack module.
Here's the test setup:
- Create a Note.waveform (wave shape table) object containing the maximum wave value (16-bit signed). This is an array filled with a single value that acts like a fixed DC voltage.
- Set the Note wave shape oscillator frequency to an arbitrary value such as 440Hz. The frequency value is unimportant since the oscillator waveform output will simply be a fixed value.
- Define a synthio.LFO object to output the LFO signal. If outputting an ADSR envelope is desired, define a synthio.Envelope object.
- Create a synthio.Note object where the amplitude parameter is controlled by the ADSR envelope or LFO.
- "Press" the note to output the ADSR envelope or LFO signal from the I2S DAC.
Instead of the I2S DAC, CV output signals can be created in this manner using audiopwmio and audioio to use PWM or analog DAC output pins. Boards like the QT PY RP2040 and Grand Central M4 Express could be used for PWM or analog DAC outputs. Keep in mind that, unlike the 0 volt baseline of the I2S DAC, the baseline of a PWM or analog DAC signal is biased to approximately +1.65 volts.
Extend the CV Range with CircuitPython 9.0.0-beta.2
The current stable release of CircuitPython (8.x.x) will not permit the output of CV signals lower than the baseline voltage (0 volts for the I2S DAC, +1.65 volts for PWM and analog DAC). However, CircuitPython 9.0.0-beta.2 incorporates a fix that allows a negative amplitude parameter value so that the CV output can reach below the baseline voltage. An added benefit of the fix is that a negative amplitude value can also be used to invert the phase of a signal, just like when describing the gain of an operational amplifier; negative values are used to represent the gain of an inverting amplifier.
CircuitPython Test Code
# SPDX-FileCopyrightText: 2023, 2024 JG for Cedar Grove Maker Studios # SPDX-License-Identifier: MIT import time import ulab.numpy as np import board import audiobusio # import audioio # import audiopwmio import audiomixer import synthio SAMPLE_RATE = 44100 SAMPLE_SIZE = 256 loudness = 1 VOLUME = int(loudness * 32760) # 0-32767 (signed 16-bit) # waveforms, envelopes and synth setup sine = np.array( np.sin(np.linspace(0, 4 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME, dtype=np.int16, ) dc_max = np.array([VOLUME for i in range(SAMPLE_SIZE)], dtype=np.int16) dc_min = np.array([-VOLUME for i in range(SAMPLE_SIZE)], dtype=np.int16) lfo = synthio.LFO(rate=0.5, waveform=sine) amp_env0 = synthio.Envelope( attack_time=0.25, decay_time=1, release_time=0.2, attack_level=1, sustain_level=0.5 ) synth = synthio.Synthesizer(sample_rate=SAMPLE_RATE) # ADSR envelope test (uncomment to test envelope output) # note_0 = synthio.Note(frequency=440, envelope=amp_env0, waveform=dc_max) # LFO test (uncomment to test LFO output) note_0 = synthio.Note(frequency=440, amplitude=lfo, waveform=dc_max) # Instantiate output path (UM FeatherS2 pin assignments) # PCM510x I2S DAC audio = audiobusio.I2SOut(bit_clock=board.D12, word_select=board.D9, data=board.D6) # Analog DAC (board-dependent pin assignment) # audio = audioio.AudioOut(board.A0) # PWM pin (board-dependent pin assignment) # audio = audiopwmio(board.D13) mixer = audiomixer.Mixer( voice_count=4, sample_rate=SAMPLE_RATE, channel_count=1, bits_per_sample=16, samples_signed=True, buffer_size=2048, ) audio.play(mixer) mixer.voice[0].play(synth) mixer.voice[0].level = 0.75 print("Test CV Output") while True: print("press note_0", note_0) synth.press(note_0) time.sleep(10) # 10s for LFO, 1s for ADSR envelope print("release note_0") synth.release(note_0) time.sleep(1)
I2S DAC Output
The tests were successful and produced viable and useful CV outputs for controlling external Eurorack modules, albeit at a slightly lower input voltage value. Increasing the voltage can be easily accommodated by most modules or by passing the CV signal through a simple DC amplifier module. We're good to go!
Attribution: Patch Symbols from PATCH & TWEAK by Kim Bjørn and Chris Meyer, published by Bjooks, are licensed under Creative Commons CC BY-ND 4.0