Hello Henrik,
I need to tell You I am realy happy with Youre DSP Software and did an realy nice Test this day and was realy happy with that result.
I was able to connect 2x Motu UL mk5 to one RPI 5 on USB.
And config the Alsa Device with the Help of that Link:
https://medium.com/home-wireless/how-to-run-camilladsp-with-multiple-dacs-9672a4639cf3
To one big 36 Output Channel Device. with an fixed Delay of 0,462us between this two Motu Devices without Drift!
Look here Please:
https://www.audiosciencereview.com/...-a-whole-lot-easier-and-cheaper.48233/page-17
Now i am much closer to my Full aktiv 7.1 DSP Setup, for that i need minimum of 16 Analogue Outs.
My Plan is to use the frist Motu UL for my Frontspeaker (2x2Way + 2x SUB) Center (1x2way) and Sub (2 Channel Out)
Second Motu for Surround L+R (2x 2Way) and Back surround L+R (2x2Way)
Robert
I need to tell You I am realy happy with Youre DSP Software and did an realy nice Test this day and was realy happy with that result.
I was able to connect 2x Motu UL mk5 to one RPI 5 on USB.
And config the Alsa Device with the Help of that Link:
https://medium.com/home-wireless/how-to-run-camilladsp-with-multiple-dacs-9672a4639cf3
To one big 36 Output Channel Device. with an fixed Delay of 0,462us between this two Motu Devices without Drift!
Look here Please:
https://www.audiosciencereview.com/...-a-whole-lot-easier-and-cheaper.48233/page-17
Now i am much closer to my Full aktiv 7.1 DSP Setup, for that i need minimum of 16 Analogue Outs.
My Plan is to use the frist Motu UL for my Frontspeaker (2x2Way + 2x SUB) Center (1x2way) and Sub (2 Channel Out)
Second Motu for Surround L+R (2x 2Way) and Back surround L+R (2x2Way)
Robert
Last edited:
I use Camilla DSP with Moode Audio on a Raspberry Pi4. I noticed that when using Camilla DSP, the output volume is lower in general. There is no gain attenuation anywhere in the pipeline so curious why this is the case. I only have a flat dither and a volume filter in the pipeline configuration. Even compared to the input volume, the output volume seems a few DBs lower (screen shot). I usually have to have my volume knob on my integrated amp at 4'o clock when using Moode + CamillaDSP whereas if i use the MPD (with Camilla DSP off) i can listen at 1'o clock setting. @HenrikEnquist thoughts on why this might be the case.
Attachments
@varunach This is totally normal. Any filter in your pipeline will produce some degree of overall decreased gain. This is obvious for a filter with negative gain, but you also need to include gain compensation if using a filter with positive gain, otherwise you run the risk of clipping the signal.
Yes, show the pipeline. You probably have a sum of filters that actually introduce a net attenuation.
//
//
Yes, That reflects the software volume setting on MoOde. I noticed it immediately on posting. I then raised the setting to 100% on MoOde and the pipeline page showed -0.0 db as expected. The difference (apparent attenuation) between using the CDSP configuration and without remains the same. The volume heard goes up and down chamginge the MoOde setting, but it's always quieter when the V2-ProtoDAC.yml config is invoked.
Here's the yaml file.
YAML:
description: ProtoDAC TDA1387 X8 Non-oversampling DAC. Invert +/- signal polarity on both channels and apply Flat dither to
16 bit samples.
devices:
adjust_period: null
capture:
channels: 2
extra_samples: null
filename: /dev/stdin
format: S24LE
read_bytes: null
skip_bytes: null
type: File
capture_samplerate: null
chunksize: 4096
enable_rate_adjust: null
playback:
channels: 2
device: hw:0,0
format: S24LE
type: Alsa
queuelimit: 1
rate_measure_interval: null
samplerate: 44100
silence_threshold: null
silence_timeout: null
stop_on_rate_change: null
target_level: null
volume_ramp_time: 150
filters:
Dither:
description: null
parameters:
amplitude: 2
bits: 16
type: Flat
type: Dither
Master gain:
description: null
parameters:
gain: 0
inverted: false
mute: false
scale: dB
type: Gain
mixers:
Stereo:
channels:
in: 2
out: 2
description: null
mapping:
- dest: 0
mute: false
sources:
- channel: 0
gain: 0
inverted: true
mute: false
scale: dB
- dest: 1
mute: false
sources:
- channel: 1
gain: 0
inverted: true
mute: false
scale: dB
pipeline:
- bypassed: null
channel: 0
description: null
names:
- Master gain
- Dither
type: Filter
- bypassed: null
channel: 1
description: null
names:
- Master gain
- Dither
type: Filter
processors: null
title: ProtoDAC TDA1387 X8
Is the DAC supported by a volume control on the source player? I'm just reaching... you are surely the exert here...
//
//
The ProtoDAC is a "passive mode" DAC i.e. it has no control interface (I2C), no on-board clocks, it just accepts an I2S data stream. Here are the pieces in the audio and volume chains.
Here's the audio chain up to the DAC:
The volume chain can be configured one of two ways.
1. MPD software volume
In this configuration CamillaDSP volume is set to 0dB in the moode routine that sets MPD volume type to "software".
2. CamillaDSP volume
In this configuration MPD volume is set to "null" (fake volume) which causes MPD to output 0dB but still emit volume change events that are then picked up by a daemon (mpd2cdspvolume) that proxies the MPD volume level N (0 -100) to CamillaDSP volume in dB.
Here's the mpd2cdspvolume daemon
Here's the audio chain up to the DAC:
Code:
---------------------------- Raspberry Pi ----------------------------- ----- Device ------
MPD -> alsa_cdsp -> CamillaDSP -> ALSA -> i2s-dac/pcm1794 driver -> I2S ==> ProtoDAC TDA1387 X8
The volume chain can be configured one of two ways.
1. MPD software volume
In this configuration CamillaDSP volume is set to 0dB in the moode routine that sets MPD volume type to "software".
Code:
MPD -> software volume -> alsa_cdsp -> CamillaDSP volume (0dB) -> ...
2. CamillaDSP volume
In this configuration MPD volume is set to "null" (fake volume) which causes MPD to output 0dB but still emit volume change events that are then picked up by a daemon (mpd2cdspvolume) that proxies the MPD volume level N (0 -100) to CamillaDSP volume in dB.
Code:
MPD -> fake volume (0dB)-> alsa_cdsp -> CamillaDSP volume (-120dB - 0dB) -> ...
| |
+------> N% -> mpd2cdspvolume -> NdB -----+
Here's the mpd2cdspvolume daemon
Code:
pi@moode900:~ $ cat /etc/mpd2cdspvolume.config
# Configuration file of mpd2cdspvolume service
[default]
dynamic_range = 60
volume_offset = 0
Code:
pi@moode900:~ $ cat /lib/systemd/system/mpd2cdspvolume.service
[Unit]
Description=Synchronize MPD volume to CamillaDSP
After=network-online.target
[Service]
Type=simple
User=mpd
ExecStart=/usr/local/bin/mpd2cdspvolume --pid_file /var/run/mpd2cdspvol/mpd2cdspvol.pid --volume_state_file /var/lib/cdsp/statefile.yml --config /etc/mpd2cdspvolume.config
[Install]
WantedBy=multi-user.target
Python:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Script for updating the CamillaDSP volume on MPD volume changes.
#
#
# The MIT License
#
# Copyright (c) 2023 bitkeeper @ github
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import os
from typing import Callable, Optional
import argparse
import time
import signal
import logging
import time
import yaml
import configparser
from pathlib import Path
from math import log10, exp, log
from mpd import MPDClient, ConnectionError
import camilladsp
VERSION = "1.0.0"
def lin_vol_curve(perc: int, dynamic_range: float= 60.0) -> float:
'''
Generates from a percentage a dBA, based on a curve with a dynamic_range.
Curve calculations coming from: https://www.dr-lex.be/info-stuff/volumecontrols.html
@perc (int) : linair value between 0-100
@dynamic_range (float) : dynamic range of the curve
return (float): Value in dBA
'''
x = perc/100.0
y = pow(10, dynamic_range/20)
a = 1/y
b = log(y)
y=a*exp(b*(x))
if x < .1:
y = x*10*a*exp(0.1*b)
if y == 0:
y = 0.000001
return 20* log10(y)
class MPDMixerMonitor:
""" Monitors MPD for mixer changes and callback when so
callback receives as argument the volume in dbs.
"""
def __init__(self, host: str= "127.0.0.1", port: int = 6600, callback: Callable= None, dynamic_range: Optional[int] = None, volume_offset : Optional[float]= None):
self._host = host
self._port = port
self._callback = callback
self._client = MPDClient() # create client object
self._client.timeout = 10 # network timeout in seconds (floats allowed), default: None
self._client.idletimeout = None # timeout for fetching the result of the idle command is handled seperately, default: None
self._kill_now = False
self._volume = None # last synced volume
""" Indicates if signal to close app is received"""
self._dynamic_range: int = dynamic_range if dynamic_range else 30
self._volume_offset: float = volume_offset if volume_offset else 0
logging.info('dynamic_range = %d dB', self._dynamic_range)
logging.info('volume_offset = -%d dB', abs(self._volume_offset))
def exit_gracefully(self, signum, frame):
logging.info('close the shop')
global kill_now
self._kill_now = True
self._client.close()
def _handle_mpd_status(self, status: dict):
"""
@return False when the volume was the same as the previous one, else True
"""
if 'volume' in status:
volume = float(status['volume'])
if volume != self._volume:
volume_db = lin_vol_curve(volume, self._dynamic_range) - abs(self._volume_offset)
logging.info('vol update = %d : %.2f dB', volume, volume_db)
if self._callback:
if self._callback(volume_db) == False and (self._volume == 0 or volume == 0):
# when unmute fails, give cdsp a little more time to start
time.sleep(0.4)
self._callback(volume_db)
self._volume = volume
else:
return False
return True
def run_monitor(self):
while self._kill_now is False:
try:
changed = self._client.idle('mixer')
if 'mixer' in changed:
status= self._client.status()
# make sure that it is in sync with the latest state of the volume, by repeating untill we get the same volume
while self._handle_mpd_status(status):
status= self._client.status()
except (ConnectionError, ConnectionRefusedError, ConnectionResetError):
while self._kill_now is False:
try:
self._client.connect(self._host, self._port)
self._handle_mpd_status(self._client.status())
self._volume = None
break
except ConnectionRefusedError:
logging.info('couldn\'t connect to MPD, retrying')
time.sleep(1)
self._client.disconnect()
class CamillaDSPVolumeUpdater:
"""Updates CamillaDSP volume
When cdsp isn't running and a volume state file for alsa_cdsp is provided that one is updated
"""
CDSP_STATE_TEMPLATE = {
'config_path': '/usr/share/camilladsp/working_config.yml',
'mute': [ False,False, False,False,False],
'volume': [ -6.0, -6.0, -6.0, -6.0, -6.0]
}
""" Used as default statefile value, when not present or invalid"""
def __init__(self, volume_state_file: Optional[Path] = None, host: str='127.0.0.1', port:int=1234):
self._volume_state_file: Optional[Path]= volume_state_file
self._cdsp = camilladsp.CamillaClient(host, port)
if volume_state_file:
logging.info('volume state file: "%s"', volume_state_file )
def check_cdsp_statefile(self) -> bool:
""" check if it exists and is valid. If not create a valid one"""
try:
if self._volume_state_file and self._volume_state_file.is_file() is False:
logging.info('Create statefile %s',self._volume_state_file)
cdsp.update_cdsp_statefile(0, False)
elif self._volume_state_file.is_file() is True:
cdsp_state = yaml.load(self._volume_state_file.read_text(), Loader=yaml.Loader)
if isinstance(cdsp_state, dict) is False:
logging.info('Statefile %s content not valid recreate it',self._volume_state_file)
cdsp.update_cdsp_statefile(0, False)
except FileNotFoundError as e:
logging.error('Couldn\'t create state file "%s", prob basedir doesn\'t exists.', self._volume_state_file)
return False
except PermissionError as e:
logging.error('Couldn\'t write state to "%s", prob incorrect owner rights of dir.', self._volume_state_file)
return False
return True
def update_cdsp_volume(self, volume_db: float):
try:
if self._cdsp.is_connected() is False:
self._cdsp.connect()
self._cdsp.volume.set_main(volume_db)
time.sleep(0.2)
cdsp_actual_volume = self._cdsp.volume.main()
logging.info('volume set to %.2f [readback = %.2f] dB', volume_db, cdsp_actual_volume)
# correct issue when volume is not the required one (issue with cdsp 2.0)
if abs(cdsp_actual_volume-volume_db) > .2:
# logging.info('volume incorrect !')
self._cdsp.volume.set_main(volume_db)
return True
except (ConnectionRefusedError, IOError) as e:
logging.info('no cdsp')
self.update_cdsp_statefile(volume_db)
return False
def update_cdsp_statefile(self, main_volume: float=-6.0, main_mute:bool = False):
""" Update statefile from camilladsp. Used for CamillaDSP 2.x and higher."""
logging.info('update volume state file : %.2f dB, mute: %d', main_volume ,main_mute)
cdsp_state = dict(CamillaDSPVolumeUpdater.CDSP_STATE_TEMPLATE)
if self._volume_state_file:
try:
if self._volume_state_file.exists():
data = yaml.load(self._volume_state_file.read_text(), Loader=yaml.Loader)
if isinstance(data, dict):
cdsp_state = data
else:
logging.warning('no valid state file content, overwrite it')
else:
logging.info('no state file present, create one')
cdsp_state['volume'][0] = main_volume
cdsp_state['mute'][0] = main_mute
self._volume_state_file.write_text(yaml.dump(cdsp_state, indent=8, explicit_start=True))
except FileNotFoundError as e:
logging.error('Couldn\'t create state file "%s", prob basedir doesn\'t exists.', self._volume_state_file)
except PermissionError as e:
logging.error('Couldn\'t write state to "%s", prob incorrect owner rights of dir.', self._volume_state_file)
def get_cmdline_arguments():
parser = argparse.ArgumentParser(description = 'Synchronize MPD volume to CamillaDSP')
parser.add_argument('-V', '--version', action='version', version='%(prog)s {}'.format(VERSION))
parser.add_argument('-v', '--verbose', action='store_true',
help = 'Show debug output.')
parser.add_argument('--mpd_host', default = '127.0.0.1',
help = 'Host running MPD. (default: 127.0.0.1)')
parser.add_argument('--mpd_port', default = 6600, type=int,
help = 'Port user by MPD. (default: 6600)')
parser.add_argument('--cdsp_host', default = '127.0.0.1',
help = 'Host running CamillaDSP. (default: 127.0.0.1)')
parser.add_argument('--cdsp_port', default = 1234, type=int,
help = 'Port used by CamillaDSP. (default: 1234)')
parser.add_argument('-s', '--volume_state_file', type=Path, default = None,
help = 'File where to store the volume state. (default: None)')
parser.add_argument('-p', '--pid_file', type=Path, default = None,
help = 'Write PID of process to this file. (default: None)')
parser.add_argument('-c', '--config', type=Path,
help = 'Load config from a file (default: None)')
args = parser.parse_args()
return args
def get_config(config_file: Path):
dynamic_range = None
volume_offset = None
if config_file and config_file.is_file():
config = configparser.ConfigParser()
config.read(config_file)
if 'default' in config and 'dynamic_range' in config['default']:
dynamic_range = int(config['default']['dynamic_range'])
if 'default' in config and 'volume_offset' in config['default']:
volume_offset = float(config['default']['volume_offset'])
return dynamic_range, volume_offset
if __name__ == "__main__":
args = get_cmdline_arguments()
if args.verbose:
logging.basicConfig(level=logging.INFO)
if not hasattr(camilladsp, 'CamillaClient'):
logging.error('No or wrong version of Python package camilladsp installed, requires version 2 or higher.')
exit(1)
logging.info('start-up mpd2cdspvolume')
dynamic_range : Optional[int]= None
volume_offset : Optional[float]= None
config_file = args.config
if config_file and config_file.is_file() is False:
logging.error('Supplied config file "%s" can\'t be read.', config_file)
exit(1)
elif config_file:
logging.info('config file: "%s"', config_file )
dynamic_range, volume_offset = get_config(config_file)
pid_file=args.pid_file
if pid_file:
logging.info('pid file: "%s"', pid_file )
try:
pid_file.write_text('{}'.format(os.getpid()))
except FileNotFoundError as e:
logging.error('Couldn\'t write PID file "%s", prob basedir doesn\'t exists.', pid_file)
exit(1)
except PermissionError as e:
logging.error('Couldn\'t write PID file to "%s", prob incorrect owner rights of dir.', pid_file)
exit(1)
state_file = args.volume_state_file
cdsp = CamillaDSPVolumeUpdater(state_file, host = args.cdsp_host, port = args.cdsp_port)
if cdsp.check_cdsp_statefile() is False:
exit(1)
monitor = MPDMixerMonitor(host = args.mpd_host, port = args.mpd_port, callback = cdsp.update_cdsp_volume, dynamic_range=dynamic_range, volume_offset=volume_offset)
signal.signal(signal.SIGINT, monitor.exit_gracefully)
signal.signal(signal.SIGTERM, monitor.exit_gracefully)
monitor.run_monitor()
if pid_file and pid_file.exists():
pid_file.unlink()
Hi folks,
I’ve been running Camilla DSP on a raspberry pi for a few years now and although its been great I have an issue which I wonder if anyone has some insight on.
I have a SPIDF input on my PI from my TV which then feeds into Camilla and then from there feeds out my DAC (OKTO). When the TV is off Camilla goes into a paused state and then I have to turn both the DAC off and turn the TV on and off again to get Camilla to work.
Is there a work around for this?
I’ve been running Camilla DSP on a raspberry pi for a few years now and although its been great I have an issue which I wonder if anyone has some insight on.
I have a SPIDF input on my PI from my TV which then feeds into Camilla and then from there feeds out my DAC (OKTO). When the TV is off Camilla goes into a paused state and then I have to turn both the DAC off and turn the TV on and off again to get Camilla to work.
Is there a work around for this?
Not sure if this is related given CDSP with your TV off goes to pause mode as i should when input is missing. Anyway I had a similar problem when DAC where powered of CDSP had to manually be restarted. Back then Henrid sorted me out by recommending not to run CDSP with the -w (wait) argument. Then it will recover from error state automatically when DAC is powered on again.I have a SPIDF input on my PI from my TV which then feeds into Camilla and then from there feeds out my DAC (OKTO). When the TV is off Camilla goes into a paused state and then I have to turn both the DAC off and turn the TV on and off again to get Camilla to work.
Ok got it 🙂 You have an extra Volume filter in the pipeline, reacting to fader Aux1.@TNT, @HenrikEnquist here are my settings in Moode/Camilla DSP
In v2.0 there is always a volume control, no need to add one as a filter. The Aux1 fader will be set to -6 dB by the mpd2cdspvolume daemon:
The Aux faders are not (yet) exposed in the UI, only the Main one is. Which means this extra volume control is stuck at -6 dB.CDSP_STATE_TEMPLATE = { 'config_path': '/usr/share/camilladsp/working_config.yml', 'mute': [ False,False, False,False,False], 'volume': [ -6.0, -6.0, -6.0, -6.0, -6.0] }
The solution is simply to get rid of the Volume filter 🙂
What device or hat do you use to receive the spdif signal on the pi?I have a SPIDF input on my PI from my TV
Ok thanks for the advice. It’s been so long since I’ve looked at CDSP, where should I look for the -w (Wait)? Is it in a config somewhere?Not sure if this is related given CDSP with your TV off goes to pause mode as i should when input is missing. Anyway I had a similar problem when DAC where powered of CDSP had to manually be restarted. Back then Henrid sorted me out by recommending not to run CDSP with the -w (wait) argument. Then it will recover from error state automatically when DAC is powered on again.
- Home
- Source & Line
- PC Based
- CamillaDSP - Cross-platform IIR and FIR engine for crossovers, room correction etc