DSPreamp, the CamillaDSP based amp

Some might remember my proof of concept using CDSP as a preamp. My initial tests sparked this DIY Amp project. I thought it would take some time but not this long! I wasn't even sure it would work but since its playing right now I thought I would share this build and my experiences from it. It will take several posts to cover it all so I thought I break it down into the following topics:
  • Background
  • Concept and Design
  • Hardware Selection
  • Fabrication
  • RPi 2040 Pico and Controls
  • HiFiBerry DAC+ ADC and UR23
  • RPi Zero 2 W and CamillaDSP
  • Final Thoughts
The background is already more or less covered by my proof of concept post. I will post the rest in this thread as soon as I get the time to put each piece together. Until then, a couple of images of the final amplifier, front, back and besides my custom built TD-115.

IMG_2855.jpg
IMG_2854.jpg
IMG_2858.jpg
 
Lets start with concept and design. I got many vintage preamps and integrated amplifiers. All got really cool filters and functions, like variable loudness control etc. The all offer different things none got all. Lots of switches, knobs and options lead to “analysis paralysis” so I thought it would be great with something clean and simple on the outside but capable with sane defaults on the inside.

So how little could I getaway with using CamillaDSP? Volume control, digital but with perfect channel separation, a logarithmic curve ranging from -80 to 0 dB attenuation with 12 o'clock @ -20 dB and automatic adaptive loudness. A great start using only one bog standard potentiometer.

Baxandall tone controls, bass and treble ranging from -10 to 10 dB in 0.5 dB increments, that's another two potentiometers.

A mono filter and input controls, and for inputs? ADC for my turntable, TOSLINK for a CD player and AirPlay for streaming. Not much to fiddle with, just enough.

As for design, I just finished my custom TD-115. I used European Oak for the plinth and acrylics for details. I had some material leftover so oak it is. I have made enclosures and faceplates in wood before but I was never fully satisfied so I searched the net for some inspiration.

1740323689181.png


I came across this one and liked the concave grooves because they breakup the faceplate making it a bit more interesting. As for knobs, I was initially aiming for solid aluminium but found a set of solid black knobs from an 80s amp I scavenged parts from. Black knobs against the oak could be cool, so I went with it.

As for size, my TD-115 is quite wide so I wanted something more compact than 43 cm wide. Something more like my Quad 34/405-2 combo. So that would be the inspiration for the overall look.

My initial thought was to take the easy route and buy some cheap Economica enclosures from Modushop but the ones I wanted was out of order. Making enclosures from scratch suck but I was out of options at the time.

Next question, switches? I could not make up my mind, everything available did not look unique so I decided rot make my own switches from scratch too (and that would take time…).

And that was more or less it, time to pick some hardware…
 
I pretty much had everything I needed for the amp in the part bin. A small toroidal transformer, Rod Elliotts P05 Mini +15-0-15V PSU and P06 RIAA preamp. TPA3251 and TPA3255 2 channel class D amplifier modules. RPi Zero 2 W with a HiFiBerry DAC+ ADC. RPi 2040 Pico, relay modules, some micros switches, potentiometers, Neutrik connectors etc. I also had a Connex SMPS600RS but its only 28V.

The HiFiBerry DAC+ ADC takes care of analog in from a turntable but I also wanted digital in for a CD-player. Stacking HiFiBerry boards is a no go and the RPi Zero 2 W only got one USB-data-port so I had to give up OTG, use the USB port in host mode and get a Hifime UR23 SPDIF Optical to USB converter. I also bought a Meanwell RS-15-5 and LRS-150-36 SMPS to power RPis and a TPA3251 board.

I had most of the material for the enclosures in the garage, European Oak, black acrylics, rubber feet etc. The only thing lacking was some aluminium sheet metal, preferably 2.5 mm. I could not source 2.5 mm so I ended up ordering 3 mm in 30x30 cm pieces.

So to sum it up:
 
Thanks, PMental. Lets continue with the fabrication. I initially planned for two enclosures, one for a pre-amp and another for a power-amp. Long story short, I ended up using one for all power supplies and the other for the rest. Both enclosures are built the same, the bottom is made up of a 30x30 cm 3 mm aluminum sheet with a 90 degree bend making a 27 cm bottom plane and a 7 cm back plane. The top is made out of 25 x 45 cm 3 mm cast acrylic sheet. Ventilation holes was first routed with a CNC and later bent with a acrylic heat bender to fit like a clam over the bottom. The front panel is made out of 15 mm European Oak, sawed to size on the table saw and then routed into shape with a CNC. Pieces fit together like this:

dspreamp.png


What about shielding? Wood and acrylic inside is covered with aluminum tape. Everything is modular and screwed together.

The hardest part was making custom buttons from scratch. I had some clear acrylic dowels rods and thought I could use them as back lit push buttons. Fabrication turned out quite complicated and the end result looked surprisingly boring.

I came up with the idea of something like a piano key instead. Fabrication was easier but it was quite hard to get the right tactile feel. My initial solution was using small rubber bands. It looked something like this (black and white pieces 3D-printed, the white piece serve as a diffuser for the LED and as an end-stop for the acrylic dowels rod):

IMG_2686.jpg
IMG_2687.jpg
IMG_2688.jpg
IMG_2689.jpg


Rubber bands worked perfectly but I guess MTBF would be quite bad so I redesigned the buttons (too many times) and the final version got springs instead of rubber bands and will probably last forever...

These buttons are also modular and can be taken apart (including the micro switch press fitted into the base that screws to the front-panel). It proved valuable during development because I did not have to reprint all the pieces every time I redesigned a piece.
 
Next up is the RPi 2040 Pico and controls. The concept is the same as for my rpi-sidekick but the implementation is different. I rewrote the code to make it more compact, robust and efficient (e.g. replaced the text based serial protocol between the Pico and the Zero with a more powerful binary protocol). The idea is to use a cheap micro controller (like the 2040) as the interface between analog controls on the front panel and CamillaDSP (running on another mashine). The setup is quite straight forward and works surprisingly well:

RPi2040Pico.png


The Pico and the Zero share the same PSU and common ground. Potentiometers interface through the Picos 3 ADC, switches through digital GPIO and GPIO with PWM is used to drive status LEDs. PWM is used for three different states of the LEDs, fully on, dimmed and off. There is also GPIO communication between the Pico and the Zero, used for the gpio-poweroff and gpio-shutdown overlays. The processes dspreamp.ino in the Pico and dspreamp.py in the Zero communicate over a serial line. GPIO is also used to control external relays, e.g. to switch RIAA and power amp power supplies (but can also be used for muting relays etc.).

Dspreamp.ino implements a state machine with three different states, STANDBY, ON and OFF. It boots-up in the STANDBY state. This is a semi-state since it can not enter the ON until dspreamp.py is connected. The power LED is dimly blinking in this state. The power LED will change into a solid dim light as soon as dspreamp.py is connected. The power switch can now be used to move to the ON state. Another push on the power switch will move it back to the STANDBY state. It will also move to the STANDBY state if dspreamp.py is disconnected. The OFF state is use to do a controlled power down of Linux on the Zero. The state change is initiated by long pressing the power button for more than 5 seconds. The gpio-shutdown will then signal shutdown to the Zero and the power button will quick flash while Linus is powering down. The gpio-poweroff will signal as soon as the Zero is safely powered off and the power button will shange into a slow flash. The 5V power can no be safely shot off with a power switch on the back of the DSPreamp.

The states also controls a relay in the power enclosure. Only the Pico and Zero are powered in the STANDBY state. The two other PSU (36V and +15-0-15V) are only powered in the ON state. This is to keep power draw to a minimum in STANDBY (and that is also the reason to use a RPi Zero insted of a RPi 4 or 5). The Zero is ideling at around 100 mA and is under 500 mA under full load. Always keeping the Pico and Zero powered makes control of the DSPreamp instantaneous insted of having to wait for Linux boot-up and shut-down etc.

A micro controller is perfect for all kinds of logics like smoothing and hysteresis of potentiometers, state based de-bouncing switches, PWM dimming of LEDs, timers (e.g. mute control), state machines and serial communication. The Pico keeps the state of all controls and transmits them to the Zero on connect and on any state change. I was worried that it would be hard to correlate states in two machines but is was easier than I thought using logical state machines and separation of responsibilites. I feel this setup is pretty rock solid and I have been running it now for a couple of weeks without any issues, but only time will tell, keeping my fingers crossed...

Ps. I made a custom PCB for the Pico acting as a generic breakout board with space for external power connectors, diodes, common ground, GPIO pins, limiting resistors etc. The board is used as a junktion board for all the controls on the front panel.
 
Last edited:
Nice!

SE machines are great. If one model them by a matrix with all states on the y-axis and all possible events on the x-axis and define all transitions and state changes by securing that all positions in the matrix are taken care of - however crazy they might be - one ends up with a bullet proof machine.

//
 
Now the most existing part, CamillaDSP & Co. My first idea was to run it on a RPi 4B and control its power from the Pico. I was exploring custom Linux builds with buildroot and other lightweight Linux distros to cut the boot-up time. It worked and I managed to cut it down to around 3 seconds. The problem was 64-bit support and manually maintaining it over time. This is when I started to consider to replace the RPi 4B with a RPi Zero and keep it powered with the Pico.

The Zero 2 W is almost as fast as the RPi 3B so I thought it might be powerful enough. My major concerns was the restricted amount of memory and running it 24/7/365. I therefor made some changes to the bog standard Raspberry Pi OS Lite (64-bit) installation. I disabled Bluetooth and some other cruft. I replaced NetworkManager with IWD (using systemd as DNS resolver), replaced sshd with dropbear and configured journald with a volatile storage only. It resulted in this during full load (playback through shairport-sync):

Code:
top - 10:21:04 up 10 min,  1 user,  load average: 0.27, 0.15, 0.06
Tasks: 124 total,   1 running, 123 sleeping,   0 stopped,   0 zombie
%Cpu(s):  2.1 us,  1.2 sy,  0.0 ni, 96.6 id,  0.0 wa,  0.0 hi,  0.2 si,  0.0 st
MiB Mem :    464.8 total,    280.8 free,    134.1 used,    100.8 buff/cache 
MiB Swap:    200.0 total,    200.0 free,      0.0 used.    330.8 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    574 shairpo+  20   0 1109600  20180  15360 S   9.9   4.2   0:54.06 shairport-sync
    568 root      20   0  549276   6656   5504 S   9.6   1.4   0:57.08 camilladsp

So from a hardware perspective it looks something like this:

hw.png


And from a software perspective, something like this:

sw.png


CamillaDSP is started as a service by systemd with the argument -w, so it waits for a configuration. Systemd is also running dspreamp.py as a service and it will connect to the Pico over serial on startup. No configuration files are used, dspreamp.py has the configuration, parsed as an object in memory and this configuration object is updated on state change. The configuration is sent to CamillaDSP over the websocket when an input is selected and on state change as long as any input is selected. CamillaDSP is stopped as soon as an input is deselected and when another state than ON is entered.

The RPi Zero 2 W only got one USB data port. One option is to use it OTG. Another is to use it in host mode and connect something like UR23 to get a digital source. I plan to use it with a CD player in the future but I am using it right now with a Belkin SOUNDFORM CONNECT Audio Adapter with AirPlay 2. This setup is solid @ 48kHz sample rate and AsyncPoly rate adjust. Shairport-Sync is outputting to ALSA Loopback and is another solid setup @ 41.1kHz and Synchronous rate adjust. The turntable inputs to the RIAA preamp and is sampled by the ADC @ 96kHz with no rate adjust (ADC and DAC uses the same master clock). CamillaDSP is running @ 96kHz and everything seems to work, what started out as a long-shot turned out better than expected.
 
Last edited:
  • Like
Reactions: PMental
Final Thoughts - It took way longer than I thought but it was fun and the result also turned out better than I anticipated. Most importantly, how does it sound? The first word that comes to mind is - clean. I am right now listening to jazz on vinyl and I am amazed by how clean it sound. This is with no tone controls applied. Sound is great "out of the box" but it is nice to have tone controls for some material that need slight adjustments. The same goes for the mono control.

My speakers are built to be used with a Linkwitz transform. It is a quite simple circuit to build but its nice to be able to just edit a text file to tune it or to change values for another set of speakers. To me this is the power of the DSP, flexibility, no need for harware redesign and rebuild. Perfect when trying things out, tweaking and establishing final settings. Having a DSP built on Open Source like CamillaDSP is preferable to me instead of relying on proprietary tech like Analog Devices SigmaDSP. I really like the concept of SigmaStudio but I hate the dependencies on .NET and Microsoft Windows. Impossible to maintain over time for someone like me, a MacOS and Linux user.

The volume control is amazing! Some might argue against a software volume control but I recommend reading TechTalk: What is software volume control and is it a bad thing? I would also recommend reading Alexander Thomas excellent Programming Volume Controls Notice to Programmers of Audio Software and Hardware and S. Andrea Sundaram eye opening What’s Wrong with Digital Volume Controls? The takeaway for me is that CamillaDSP enables me to use a cheap one gang linear potentiometer and turn it into a volume control with a perfect logarithmic taper, perfect channel tracking and a programmable automatic adaptive loudness. It is by far the best volume control I have ever had (including my hand built stepped attenuators). You only have to make sure your input sources are as close to dBFS as possible if you want to maximize resolution. I think the volume control by itself motivates running CamillaDSP as the preamp.

Next up is noise, or lack of it. I usually have no problem with "noise" but there might be faint noise if I put my ear close to the speaker elements. This build is dead silent, totally black! That was not the case when I powered it up for the first time. It was my first build with power supplies in a separate enclosure and using an umbilical cable. The noise was excruciating and I had to pretty much rewire it all and rethink my grounding scheme.

I love being able to ssh into my amplifier, checking status, debug and fine-tune. Having a sound system built on Open Source was what drove me in the first place and I love the result. I can not thank all developers contributing to Open Source projects enough. I always try to use Open Source software if possible and used KiCAD to design the RPi Pico junction board, FreeCAD for CAD and CAM fabricating enclosures. I used Klipper and PrusaSlicer for 3D-printing. Arduino IDE to program the RPi Pico and Geany making the dspreamp.py program. And finally Linux, ALSA, Shairport-Sync and CamillaDSP for the realisation of DSPreamp. All Open Source, no vendor lock-in or dependencies to proprietary tech like Analog Devices SigmaDSP or Microsoft Windows.

I did not know what to expect when I started out but I wholeheartedly recommend this kind of solution after experiencing the result. It turned out way better than I expected. Time to enjoy the music... 🎶
 
Last edited:
A first little setback discovered yesterday when I was trying to select the UR23 as source, no sound! I got sound again after pulling it out and reinserting it again, but that can't be the proper solution, right? I found multiple entries like "kernel: retire_capture_urb: 492 callbacks suppressed" in the journal and a stalled CDSP as a result...

First though, cut USB power to UR23 when it is not selected as a source. Long story short, seems like RPi Zero 2 W is unable to cut power with a utility like uhubctl, but it seems like it is unbinding and rebinding if I try.

The quick solution I implemented in dspreamp.py (for now) is to reset the device with a command like "usb_modeswitch -v 0x262a -p 0x12c1 -R -Q" each time UR23 is selected as the input source. I does not fix the root cause but it prevents CDSP from stalling.

I gladly welcome any other ideas 🙂
 
A quick update regarding my UR23 and the "retire_capture_urb" kernel log. I have tried to get to the root cause of this. I have read the kernel source, the kernel mailing list, debugged usb audio and alsa without finding any leads... But I found traces of this error all over Internet, mostly old forum posts and I started to get the feel that it might be power related. Some claimed RF interference other that changing the PSU made the problem go away... Thats when it hit me! I built the PSU box first and I then sat the RS-15-5 to 5.0V with no load. And I got an umbilical cable and all but I never checked back after cabling and under load.😳

Today I first measured voltage on the USB port, 4.62V, I then measured the 5V power rail feeding the RPi Zero and Nano, 4.63V, its amazing they have been running more or less flawlessly for weeks! It should be, according to USB spec, 5.0V +/- 5% (i.e. 4.75V to 5.25V), right? So I have now upped the voltage to 5.15V on the 5V rail, I can see it sag a bit under boot (down to 4.98) but is leveling out around 5.15V idling. The USB port is now also measuring 5.15V and I hope it will make the UR23 run more stable and prevent it from generating "retire_capture_urb". Only time will tell...