This is a Morse code practice oscillator for Fruit Jam with super clean sine wave tones and a 3d-printable straight key. The key uses a Kailh Cherry MX compatible keyswitch for reliable switching and flexures to make a precision hinge that works well when printed in PLA. Set screw adjustment lets you shorten the keyswitch travel. The audio output chain uses an audiofilter.Filter to bandpass filter the output of a synthio.Synthesizer for smooth note attacks and releases without keyclick.
[Update: You can now use Button #3 to generate tones if you'd rather not build the 3d printed mx straight key (2026-01-07)]
Parts
For the 3.5mm audio patch cord, you can use a stereo TRS or TRRS aux cable. Mono TS 3.5mm patch cords for modular synths also work.
For the 3.5mm panel mount TRS jack, I recommend buying a spare or two in case you run into any difficulties during assembly (lost or cross-threaded nut, etc).
The wire can be be whatever you have laying around so long as it's stranded and thin. You'll probably have a difficult time if you try solid hookup wire (too stiff).
Updating CircuitPython
As I write this (January 6, 2026), CircuitPython 10.0.3 is the most recent build for Fruit Jam. To keep your board up to date, you can use the "DOWNLOAD .UF2 NOW" buttons on the download page of circuitpython.org:
- Fruit Jam Download page
To install the UF2 File:
- Connect your board to a computer with a USB data cable (charge-only cables won't work! )
- Press and hold the board's boot button (Button 1 on Fruit Jam)
- Press and release the reset button
- Release the boot button
- When the removable drive named RP2350 appears, copy the UF2 file onto it. After the copy finishes, you should see the RP2350 drive disappear and soon after that a new CIRCUITPY drive should appear.
Project Code & Libraries
You can view the code at the samblenny/fruit-jam-code-practice-oscillator GitHub repository. To download a zip archive project bundle with the code and all the necessary libraries, use the "Download Project Bundle" button:
Install Project Bundle
To copy the project bundle files to your CIRCUITPY drive:
- Download the project bundle .zip file using the "Download Project Bundle" button above.
- Expand the zip file by opening it, or use unzip in a Terminal. The zip archive should expand to a folder. When you open the folder, it should contain a README.txt file and a CircuitPython 10.x folder.
- Open the CircuitPython 10.x folder and copy all of its contents to your CIRCUITPY drive.
To learn more about copying libraries to your CIRCUITPY drive, check out the CircuitPython Libraries section of the Welcome to CircuitPython! learn guide.
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: Copyright 2026 Sam Blenny
#
# See NOTES.md for documentation links
#
from audiobusio import I2SOut
import audiofilters
from board import I2C, I2S_BCLK, I2S_DIN, I2S_MCLK, I2S_WS, A4, BUTTON3
import digitalio
from micropython import const
from pwmio import PWMOut
import synthio
import time
import ulab.numpy as np
from adafruit_tlv320 import TLV320DAC3100
# Morse Code Options
MORSE_HZ = const(622.25)
# DAC/Synth/Filter sample rate
SAMPLE_RATE = const(8000)
# I2S MCLK clock frequency
MCLK_HZ = const(15_000_000)
def configure_dac(i2c, sample_rate, mclk_hz):
# Configure TLV320DAC (this requires a separate 15 MHz PWMOut to MCLK)
# 1. Initialize DAC (this includes a soft reset and sets minimum volumes)
dac = TLV320DAC3100(i2c)
# 2. Configure headphone/speaker routing and volumes (order matters here)
dac.speaker_output = False
dac.headphone_output = True
#
# dac_volume (digital gain) range is -63.5 dB (soft) to 24 dB (loud).
# headphone_volume (analog amp) range is -78.3 dB (soft) to 0 dB (loud).
# - For samples that are normalized to near full scale loudness, keep
# dac_volume below 0 to avoid DSP filter clipping
# - Use dac_volume=-3, headphone_volume=0 for line level
# - Try headphone_volume=-24 for headphones (adjust as needed)
#
dac.dac_volume = -3 # Keep this below 0 to avoid DSP filter clipping
dac.headphone_volume = 0 # CAUTION! Line level. Too loud for headphones!
# dac.headphone_volume = -24 # Use this for headphones
# 3. Configure the right PLL and CODEC settings for our sample rate
dac.configure_clocks(sample_rate=sample_rate, mclk_freq=MCLK_HZ)
# 4. Wait for power-on volume ramp-up to finish
time.sleep(0.35)
return dac
def run():
# Set up I2C and I2S buses
i2c = I2C()
audio = I2SOut(bit_clock=I2S_BCLK, word_select=I2S_WS, data=I2S_DIN)
# Set up 15 MHz MCLK PWM clock output for less hiss and distortion
mclk_pwm = PWMOut(I2S_MCLK, frequency=MCLK_HZ, duty_cycle=2**15)
# Initialize DAC for 8 kHz sample rate
dac = configure_dac(i2c, SAMPLE_RATE, MCLK_HZ)
# Build synthio sinewave patch with bandpass filter
sinewave = np.array(
np.sin(np.linspace(0, 2*np.pi, 640, endpoint=False)) * 30000,
dtype=np.int16)
synth = synthio.Synthesizer(sample_rate=SAMPLE_RATE, channel_count=1,
envelope=None, waveform=sinewave)
bandpass = synthio.Biquad(synthio.FilterMode.BAND_PASS, MORSE_HZ, Q=6.5)
bp_filter = audiofilters.Filter(filter=bandpass, buffer_size=256,
sample_rate=SAMPLE_RATE)
audio.play(bp_filter)
bp_filter.play(synth)
time.sleep(0.5) # let volume stabilize
# Configure paddle input on tip (Left) of TRRS Jack plugged into GPIO port
# === DANGER!!! ===
# Plug the breakout board into the GPIO port with Sleeve in GND and Ring in
# in A5. DO NOT accidentally plug Sleeve into 3V, which is next to GND!
# =================
tip = digitalio.DigitalInOut(A4)
tip.switch_to_input(digitalio.Pull.UP)
# Also take input from Button #3
b3 = digitalio.DigitalInOut(BUTTON3)
b3.switch_to_input(digitalio.Pull.UP)
# Cache function references (go faster)
sleep = time.sleep
press = synth.press
release = synth.release
# Play sinewave on Fruit Jam DAC when Tip (Left, A4) of TRRS jack is
# connected to GND by the Morse code key
print("""
Code practice oscillator is ready.
- CAUTION: Default volume is LINE LEVEL
- For headphones, edit code.py to set `dac.headphone_volume = -24`
- To play tones: press Button #3 or connect A4 to GND with mx straight key
- For mx straight key STL file and build instructions, see Playground guide at
https://adafruit-playground.com/u/SamBlenny/pages/fruit-jam-code-practice-oscillator
""")
note = synthio.Note(frequency=MORSE_HZ)
prev_pressed = False
while True:
# Both Button #3 and the straight key input pin are active low, meaning
# their value property is True when not pressed and False when pressed.
# So, to get a value that's True when either button is pressed, take
# the logical AND of the active low signals, then invert that with NOT.
pressed = not (tip.value and b3.value)
if pressed != prev_pressed:
prev_pressed = pressed
if pressed:
press(note)
else:
release(note)
# 1 ms debounce delay
sleep(0.001)
run()
3D Printing
The Blender and STL files are available on the GitHub release page or from the download buttons below. The MX straight key frame uses flexure springs which are fine to print in PLA. The horizontal round holes have a teardrop top, so they should print fine without supports. I used the standard Bambu Studio 0.2mm layer height preset modified to calibrate my X-Y hole compensation, slow down the maximum acceleration, and increase the walls from 2 to 3 (but 2 probably would have been fine). I recommend drying your filament well to avoid stringing.
Wiring
If you're new to soldering, consider reading the Adafruit Guide to Excellent Soldering. You'll need to prepare the TRRS breakout board for plugging into the Fruit Jam GPIO header and to solder wires between the mx straight key's TRS jack and keyswitch.
Fruit Jam + TRRS Jack Breakout Wiring Diagram
Fruit Jam GPIO Port +-----+-----+ TRRS Jack | 5V | 5V | Breakout | 3V | 3V | +----------------+ | G | GND |----| Sleeve | <- Sleeve: GND | RST | A1 |----| Right +-------+| | PR | A2 |----| RSw + +-+ | SCK | A3 |----| LSw + +-+ | MI | A4 |----| Left +-------+| <- Left: Tip (input w/ pullup) | MO | A5 |----| Ring | | D+ | D6 | +----------------+ | D- | D7 | | UF | D8 | | SDA | D9 | | SCL | 10 | | G | GND | | 3V | 3V | | 5V | 5V | +-----+-----+
TRS Headphone Jack Cherry MX Compatible Key Switch
||
/--+||+--\
+----------+ / \
| \/-----|--- 3 Right ============
+~~~~+ | black | |
|---------------|----- 1 Sleeve ----------+ +
+~~~~+ | |
| /\--|--- 2 Left (Tip) ------------+
+----------| yellow
Soldering Procedure
Once you've prepared your parts and soldering materials...
- Get a good light and examine the TRS panel mount jack to identify which pin is which. The long center pin is probably marked as "1" in itty bitty barely readable text. It should be connected to the sleeve. The two short pins should be marked 2 and 3, with 2 connected to the tip (left), and 3 connected to the ring (right). It might be helpful to connect an audio cable and verify the connections with a multimeter in continuity test mode. If your jack's wiring doesn't match the wiring diagram shown here, you will need to adjust the rest of these instructions to match your wiring.
- Assemble the jack and keyswitch in the the 3d printed frame. Threading the nut onto the panel mount jack can be challenging. Take your time to align the nut gently (don't get it cross threaded!). Once you have the threads lined up, tighten the nut. The keyswitch pins should go on the end furthest from the TRS jack.
- Cut black and yellow wires to reach from the TRS jack pins to the keyswitch pins. Leave a little extra slack to give you maneuvering room to install the keyswitch after you've finished soldering.
- Remove the TRS jack and keyswitch from the 3d printed frame so you can clamp them for soldering.
- Strip about 2-3 mm of insulation from each of the 4 wire ends.
- Tin the wire ends with solder.
- Tin the keyswitch and TRS jack pins with solder.
- Solder the black wire from one keyswitch pin to TRS jack pin 1 (sleeve)
- Solder the yellow wire from the other keyswitch pin to TRS jack pin 2 (tip, left).
- Solder the header strip onto the TRRS jack breakout board.
- Put the panel mount TRS jack through the hole in the frame as shown in the photo above. This part can be challenging. Take your time to align the nut gently (don't get it cross threaded!). Once you have the threads lined up, tighten the nut.
- Twist the wires a few turns so they don't flop around so much.
- Install the keyswitch by pressing down from the top or sliding it in sideways.
- Carefully route the wires through the wire notch.
- Screw the M5x8mm set screw into the hole in the top of the 3d printed lever. The exact depth doesn't matter for now. You will do the final adjustment later once you have the code running so you can hear when the switch activates.
- With the Fruit Jam disconnected from power, carefully plug the TRRS jack breakout's header pins into the Fruit Jam GPIO port. Make sure to put Sleeve in GND and Ring in A5.
- After you've double checked that the TRRS Jack Breakout's pins are aligned properly in the Fruit Jam GPIO header, connect the mx straight key's TRS jack to the Fruit Jam's TRRS jack with your 3.5mm audio cable.
- Connect the Fruit Jam's headphone jack to a mixer, powered speaker, or other audio device that takes a LINE LEVEL input.
- Connect your Fruit Jam to your computer with a USB data cable (power-only cables won't work!)
Adjustment procedure:
- Make sure the code is running (check the serial terminal) and that your audio connections are good.
- Coarse Adjustment: Use an Allen wrench (hex key) to screw in the set screw until you hear a tone, then back the screw out until the tone stops. (if you bottom out the switch plunger but don't hear anything, check your audio connections)
- Fine Adjustment: With the wrench removed, test the feel of the key. Make small adjustments (maybe about 45* at a time) until it feels right.
This page (Fruit Jam Code Practice Oscillator) was last updated on January 07, 2026.