Skip to content

Commit

Permalink
Smooth volume changes
Browse files Browse the repository at this point in the history
Broadens the implementation of mimic absolute volume control. Smoothens
volume changes by progressing to target volume control via intermitting
steps.
  • Loading branch information
therealmuffin committed Sep 3, 2017
1 parent bf8e051 commit 3d18e94
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 129 deletions.
13 changes: 11 additions & 2 deletions CONFIG_MANUAL.md
Expand Up @@ -114,17 +114,26 @@ header - prefix added to every volume command code (thus post the header defined

curve - Volume curve can be linear or logarithmic. If volume control is relative, only linear is allowed [linear`*`|log]


###### additional discrete specific options

Synchronator can treat the input volume changes as a (dynamic) target and generate a smooth curve at the amplifier end towards that (dynamic) target. This functionality is triggered by setting the 'timeout' option.

mutation - mutation to the volume level for every step towards the (dynamic) volume target, set in hundreds of seconds. The lowest level of this setting is determined by the capabilities of the configured amplifier. For configurations with setting 'data-type' at value 'numerical', the minimum is one second (100). Above this minimum, the resulting volume level is rounded down to the nearest value possible for the configured amplifier [0-1000|100`*`]

timeout - sets a timeout in milliseconds between each volume command set to generate a smooth volume curve and prevent dataloss due to an overload of data sent to the amplifier [0-100|0`*`]

###### additional non-discrete specific options

Synchronator can mimic discrete volume control for non-discrete controlable amplifiers. To activate this functionality, the option 'range' needs to be set. To sync Synchronator's volume level with that of the amplifier, volume level is set to zero at initialisation. Sample configuration files that have this functionality enabled have a suffix of MAVC (Mimic Absolute Volume Control).
Synchronator can mimic discrete volume control for non-discrete controlable amplifiers. To activate this functionality, the option 'timeout' needs to be set. To sync Synchronator's volume level with that of the amplifier, volume level is set to zero at initialisation. Sample configuration files that have this functionality enabled have a suffix of MAVC (Mimic Absolute Volume Control).

range - number of steps to go from zero to maximum volume (may be higher than the actual value, not lower as that would render the initialisation process ineffective)

max - limits the maximum volume level to a value below that set in range [x < range|range`*`]

default - in addition to the description above, in this mode this function also triggers a volume reinitialisation.

timeout - sets a timeout in milliseconds between each volume command set to prevent dataloss due to an overload of data sent to the amplifier [0`*`]
timeout - sets a timeout in milliseconds between each volume command set to prevent dataloss due to an overload of data sent to the amplifier [0-100|0`*`]

double_zero_interval - Synchronator relies for this functionality on its ability to keep track of the current volume level. This process can be disrupted by user intervention at the amplifier end. By moving volume twice to zero within the set interval (in seconds), Synchronator reinitialises its volume level. Setting this value to zero, enables initialization everytime volume level reaches zero [2`*`]

Expand Down
4 changes: 2 additions & 2 deletions src/Makefile
Expand Up @@ -12,8 +12,8 @@ PROGRAM-PREFIX ?=
PROGRAM-SUFFIX ?=
output_devices=1

SRCS := synchronator.c volume.c volumeLinear.c volumeLog.c interfaces.c processData.c processAscii.c processNumeric.c verifyConfig.c mixerAlsa.c mimicAbsVol.c mods.c modVolumeResponseRange.c modVolumeResponseCondResize.c modCommandDynaudio.c modCommandDenonAVR.c volume_mapping.c
DEPS := config.mk common.h synchronator.h volume.h interfaces.h processData.h verifyConfig.h mixer.h mimicAbsVol.h mods.h volume_mapping.h
SRCS := synchronator.c volume.c volumeLinear.c volumeLog.c interfaces.c processData.c processAscii.c processNumeric.c verifyConfig.c mixerAlsa.c smoothVolume.c mods.c modVolumeResponseRange.c modVolumeResponseCondResize.c modCommandDynaudio.c modCommandDenonAVR.c volume_mapping.c
DEPS := config.mk common.h synchronator.h volume.h interfaces.h processData.h verifyConfig.h mixer.h smoothVolume.h mods.h volume_mapping.h

ifndef DISABLE_SERIAL
SRCS += interfaceSerial.c
Expand Down
11 changes: 10 additions & 1 deletion src/common.h
Expand Up @@ -26,7 +26,9 @@
/* Status variable, external volume volume level if (mimicked) discrete, if relative internal
volume level */
double volume_level_status;
int volumeMutationRange;

/* If different from 0, smoothVolume is activated */
int volume_timeout;

/* Multiplies non-discrete volume commands (not/partially implemented) */
int nd_vol_multiplier;
Expand Down Expand Up @@ -56,5 +58,12 @@
extern pthread_mutex_t lockProcess;
extern pthread_mutex_t lockConfig;
extern pthread_t mainThread;

#define VOLUME_UP 20
#define VOLUME_DOWN 10


#define CONFIG_REQUIRED -1
#define CONFIG_IGNORE -2

#endif
12 changes: 0 additions & 12 deletions src/mimicAbsVol.h

This file was deleted.

87 changes: 58 additions & 29 deletions src/processAscii.c
Expand Up @@ -33,7 +33,7 @@
#include "common.h"
#include "processData.h"
#include "verifyConfig.h"
#include "mimicAbsVol.h"
#include "smoothVolume.h"


typedef struct {
Expand All @@ -56,8 +56,10 @@ static void help(void);
static int init(void);
static void deinit(void);
static int sendVolumeCommand(long *volumeInternal);
static int sendSmoothVolumeCommand(double *volumeExternal);
static int replyVolumeCommand(long *volumeInternal);
static int compileVolumeCommand(long *volumeInternal, char serial_command[200]);
static void compileDescreteVolumeCommand(double *volumeExternal, char serial_command[200]);
static int setVolumeCommand(long *volumeInternal, char serial_command[200]);
static int sendDeviceCommand(char *category, char *action);
static int replyDeviceCommand(char *category, char *action);
static int compileDeviceCommand(char *header, char *action, char serial_command[200]);
Expand Down Expand Up @@ -135,16 +137,13 @@ static int init(void) {
ascii_data.volume_tail[0], ascii_data.command_tail[0]);
ascii_data.volumeMutationPositive = calloc(strlen(serial_command)+1, sizeof(char));
strcpy(ascii_data.volumeMutationPositive, serial_command);

if(common_data.volumeMutationRange) {
if(validateConfigInt(&config, "volume.max", &common_data.volume_max, -1, 1,
common_data.volumeMutationRange, common_data.volumeMutationRange) == EXIT_FAILURE)
return EXIT_FAILURE;

if(mimicAbsVol.init(ascii_data.volumeMutationNegative, ascii_data.volumeMutationPositive) == EXIT_FAILURE)
return EXIT_FAILURE;
}
}

if(common_data.volume_timeout) {
if(smoothVolume.init(&sendSmoothVolumeCommand, (char *)ascii_data.volumeMutationNegative, (char *)ascii_data.volumeMutationPositive) == EXIT_FAILURE)
return EXIT_FAILURE;
}

if(common_data.send_query && validateConfigString(&config, "query.trigger.[0]", &common_data.statusQuery, -1) == EXIT_FAILURE)
return EXIT_FAILURE;
if(common_data.send_query) common_data.statusQueryLength = strlen(common_data.statusQuery);
Expand Down Expand Up @@ -187,7 +186,7 @@ static int sendVolumeCommand(long *volumeInternal) {
return EXIT_SUCCESS;
}

if(compileVolumeCommand(volumeInternal, serial_command) == EXIT_FAILURE) {
if(setVolumeCommand(volumeInternal, serial_command) == EXIT_FAILURE) {
pthread_mutex_unlock(&lockProcess);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

Expand All @@ -212,13 +211,39 @@ static int sendVolumeCommand(long *volumeInternal) {
return EXIT_SUCCESS;
}

static int sendSmoothVolumeCommand(double *volumeExternal) {
char serial_command[200];

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
pthread_mutex_lock(&lockProcess);

compileDescreteVolumeCommand(volumeExternal, serial_command);

#ifdef TIME_DEFINED_TIMEOUT
clock_gettime(CLOCK_MONOTONIC , &common_data.timestampLastTX);
#else
common_data.volume_in_timeout = DEFAULT_PROCESS_TIMEOUT_IN;
#endif // #ifdef TIME_DEFINED_TIMEOUT

syslog(LOG_DEBUG, "Volume level mutation (int. initiated): ext. level: %.2f",
common_data.volume_level_status);

pthread_mutex_unlock(&lockProcess);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

if(common_data.interface->send(serial_command, strlen(serial_command)) == EXIT_FAILURE)
return EXIT_FAILURE;

return EXIT_SUCCESS;
}

static int replyVolumeCommand(long *volumeInternal) {
char serial_command[200];

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
pthread_mutex_lock(&lockProcess);

if(compileVolumeCommand(volumeInternal, serial_command) == EXIT_FAILURE) {
if(setVolumeCommand(volumeInternal, serial_command) == EXIT_FAILURE) {
pthread_mutex_unlock(&lockProcess);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

Expand All @@ -236,31 +261,35 @@ static int replyVolumeCommand(long *volumeInternal) {
return EXIT_SUCCESS;
}

static int compileVolumeCommand(long *volumeInternal, char serial_command[200]) {
static void compileDescreteVolumeCommand(double *volumeExternal, char serial_command[200]) {
snprintf(serial_command, 200, "%s%s%s%0*.*f%s%s",
ascii_data.command_header[0], ascii_data.volume_header[0],
ascii_data.event_delimiter[0], ascii_data.volume_length,
ascii_data.volume_precision, *volumeExternal, ascii_data.volume_tail[0],
ascii_data.command_tail[0]);
}

static int setVolumeCommand(long *volumeInternal, char serial_command[200]) {

if(*volumeInternal < 0 || *volumeInternal > 100) {
syslog(LOG_WARNING, "Value for command \"volume\" is not valid: %ld", *volumeInternal);

return EXIT_FAILURE;
}

if(common_data.discrete_volume) {
if(common_data.discrete_volume || common_data.volume_timeout) {
double volumeExternal;
common_data.volume->convertInternal2External(volumeInternal, &volumeExternal);
snprintf(serial_command, 200, "%s%s%s%0*.*f%s%s",
ascii_data.command_header[0], ascii_data.volume_header[0],
ascii_data.event_delimiter[0], ascii_data.volume_length,
ascii_data.volume_precision, volumeExternal, ascii_data.volume_tail[0],
ascii_data.command_tail[0]);

common_data.volume_level_status = volumeExternal;
}
else if(common_data.volumeMutationRange) {
double volumeExternal;
common_data.volume->convertInternal2External(volumeInternal, &volumeExternal);
mimicAbsVol.process(volumeExternal);
common_data.volume_level_status = volumeExternal;
return EXIT_FAILURE; // to prevent an attempt to serial_command in calling function

if(common_data.volume_timeout) {
smoothVolume.process(&volumeExternal);
return EXIT_FAILURE;
}
else {
compileDescreteVolumeCommand(&volumeExternal, serial_command);
}
}
else {
if(common_data.volume_level_status == *volumeInternal)
Expand Down Expand Up @@ -571,8 +600,8 @@ static int strip_raw_input(unsigned char *device_status_message, ssize_t bytes_r
static void deinit(void) {
if(common_data.mod->command != NULL)
common_data.mod->command->deinit();
if(!common_data.discrete_volume && common_data.volumeMutationRange)
mimicAbsVol.deinit();
if(common_data.volume_timeout)
smoothVolume.deinit();

if(ascii_data.volumeMutationNegative != NULL)
free(ascii_data.volumeMutationNegative);
Expand Down

0 comments on commit 3d18e94

Please sign in to comment.