mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-11-09 16:55:42 +01:00
alsamixer: use cubic scale for volume bars
Instead of mapping the raw volume values linearly to the screen, use a mapping where the bar height is proportional to the audible volume, i.e., where the amplitude is the cube of the bar height. Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
This commit is contained in:
parent
70a01748d5
commit
34bb514b5f
5 changed files with 260 additions and 113 deletions
|
@ -15,6 +15,7 @@ alsamixer_SOURCES = card_select.c card_select.h \
|
|||
proc_files.c proc_files.h \
|
||||
textbox.c textbox.h \
|
||||
utils.c utils.h \
|
||||
volume_mapping.c volume_mapping.h \
|
||||
widget.c widget.h
|
||||
man_MANS = alsamixer.1
|
||||
EXTRA_DIST = alsamixer.1
|
||||
|
|
|
@ -17,10 +17,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define _C99_SOURCE /* lrint() */
|
||||
#include "aconfig.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <math.h>
|
||||
#include CURSESINC
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "gettext_curses.h"
|
||||
|
@ -28,6 +30,7 @@
|
|||
#include "mem.h"
|
||||
#include "colors.h"
|
||||
#include "widget.h"
|
||||
#include "volume_mapping.h"
|
||||
#include "mixer_widget.h"
|
||||
#include "mixer_controls.h"
|
||||
#include "mixer_display.h"
|
||||
|
@ -390,24 +393,14 @@ static void display_string_centered_in_control(int y, int col, const char *s, in
|
|||
display_string_in_field(y, x, s, width, ALIGN_CENTER);
|
||||
}
|
||||
|
||||
static long clamp(long value, long min, long max)
|
||||
{
|
||||
if (value < min)
|
||||
return min;
|
||||
if (value > max)
|
||||
return max;
|
||||
return value;
|
||||
}
|
||||
|
||||
static void display_control(unsigned int control_index)
|
||||
{
|
||||
struct control *control;
|
||||
int col;
|
||||
int i, c;
|
||||
int left, frame_left;
|
||||
int bar_height, value;
|
||||
long volumes[2];
|
||||
long min, max;
|
||||
int bar_height;
|
||||
double volumes[2];
|
||||
int switches[2];
|
||||
unsigned int index;
|
||||
const char *s;
|
||||
|
@ -452,35 +445,22 @@ static void display_control(unsigned int control_index)
|
|||
waddch(mixer_widget.window, ACS_LRCORNER);
|
||||
}
|
||||
if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
|
||||
int (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
|
||||
double (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t);
|
||||
|
||||
if (control->flags & TYPE_PVOLUME)
|
||||
get_vol_func = snd_mixer_selem_get_playback_volume;
|
||||
get_vol_func = get_normalized_playback_volume;
|
||||
else
|
||||
get_vol_func = snd_mixer_selem_get_capture_volume;
|
||||
err = get_vol_func(control->elem, control->volume_channels[0], &volumes[0]);
|
||||
if (err >= 0 && (control->flags & HAS_VOLUME_1))
|
||||
err = get_vol_func(control->elem, control->volume_channels[1], &volumes[1]);
|
||||
get_vol_func = get_normalized_capture_volume;
|
||||
volumes[0] = get_vol_func(control->elem, control->volume_channels[0]);
|
||||
if (control->flags & HAS_VOLUME_1)
|
||||
volumes[1] = get_vol_func(control->elem, control->volume_channels[1]);
|
||||
else
|
||||
volumes[1] = volumes[0];
|
||||
if (err < 0)
|
||||
return;
|
||||
if (control->flags & TYPE_PVOLUME)
|
||||
err = snd_mixer_selem_get_playback_volume_range(control->elem, &min, &max);
|
||||
else
|
||||
err = snd_mixer_selem_get_capture_volume_range(control->elem, &min, &max);
|
||||
if (err < 0)
|
||||
return;
|
||||
if (min >= max)
|
||||
max = min + 1;
|
||||
volumes[0] = clamp(volumes[0], min, max);
|
||||
volumes[1] = clamp(volumes[1], min, max);
|
||||
|
||||
if (control->flags & IS_ACTIVE)
|
||||
wattrset(mixer_widget.window, 0);
|
||||
for (c = 0; c < 2; c++) {
|
||||
bar_height = ((volumes[c] - min) * volume_height +
|
||||
max - min - 1) / (max - min);
|
||||
bar_height = lrint(volumes[c] * volume_height);
|
||||
for (i = 0; i < volume_height; ++i) {
|
||||
chtype ch;
|
||||
if (i + 1 > bar_height)
|
||||
|
@ -505,19 +485,18 @@ static void display_control(unsigned int control_index)
|
|||
}
|
||||
if (control->flags & IS_ACTIVE)
|
||||
wattrset(mixer_widget.window, attr_mixer_active);
|
||||
value = ((volumes[0] - min) * 100 + (max - min) / 2) / (max - min);
|
||||
if (!(control->flags & HAS_VOLUME_1)) {
|
||||
sprintf(buf, "%d", value);
|
||||
sprintf(buf, "%d", lrint(volumes[0] * 100));
|
||||
display_string_in_field(values_y, frame_left - 2, buf, 8, ALIGN_CENTER);
|
||||
} else {
|
||||
mvwprintw(mixer_widget.window, values_y, frame_left - 2, "%3d", value);
|
||||
mvwprintw(mixer_widget.window, values_y, frame_left - 2,
|
||||
"%3d", lrint(volumes[0] * 100));
|
||||
if (control->flags & IS_ACTIVE)
|
||||
wattrset(mixer_widget.window, attr_ctl_frame);
|
||||
waddstr(mixer_widget.window, "<>");
|
||||
if (control->flags & IS_ACTIVE)
|
||||
wattrset(mixer_widget.window, attr_mixer_active);
|
||||
value = ((volumes[1] - min) * 100 + (max - min) / 2) / (max - min);
|
||||
wprintw(mixer_widget.window, "%-3d", value);
|
||||
wprintw(mixer_widget.window, "%-3d", lrint(volumes[1] * 100));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "textbox.h"
|
||||
#include "proc_files.h"
|
||||
#include "card_select.h"
|
||||
#include "volume_mapping.h"
|
||||
#include "mixer_controls.h"
|
||||
#include "mixer_display.h"
|
||||
#include "mixer_widget.h"
|
||||
|
@ -295,82 +296,57 @@ static void change_enum_relative(struct control *control, int delta)
|
|||
|
||||
static void change_volume_to_percent(struct control *control, int value, unsigned int channels)
|
||||
{
|
||||
int (*get_range_func)(snd_mixer_elem_t *, long *, long *);
|
||||
int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long);
|
||||
long min, max;
|
||||
int err;
|
||||
int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int);
|
||||
|
||||
if (!(control->flags & HAS_VOLUME_1))
|
||||
channels = LEFT;
|
||||
if (control->flags & TYPE_PVOLUME) {
|
||||
get_range_func = snd_mixer_selem_get_playback_volume_range;
|
||||
set_func = snd_mixer_selem_set_playback_volume;
|
||||
} else {
|
||||
get_range_func = snd_mixer_selem_get_capture_volume_range;
|
||||
set_func = snd_mixer_selem_set_capture_volume;
|
||||
}
|
||||
err = get_range_func(control->elem, &min, &max);
|
||||
if (err < 0)
|
||||
return;
|
||||
if (control->flags & TYPE_PVOLUME)
|
||||
set_func = set_normalized_playback_volume;
|
||||
else
|
||||
set_func = set_normalized_capture_volume;
|
||||
if (channels & LEFT)
|
||||
set_func(control->elem, control->volume_channels[0], min + (max - min) * value / 100);
|
||||
set_func(control->elem, control->volume_channels[0], value / 100.0, 0);
|
||||
if (channels & RIGHT)
|
||||
set_func(control->elem, control->volume_channels[1], min + (max - min) * value / 100);
|
||||
set_func(control->elem, control->volume_channels[1], value / 100.0, 0);
|
||||
}
|
||||
|
||||
static void change_volume_relative(struct control *control, long delta, unsigned int channels)
|
||||
static double clamp_volume(double v)
|
||||
{
|
||||
int (*get_range_func)(snd_mixer_elem_t *, long *, long *);
|
||||
int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
|
||||
int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long);
|
||||
long min, max;
|
||||
long left, right;
|
||||
long value;
|
||||
int err;
|
||||
if (v < 0)
|
||||
return 0;
|
||||
if (v > 1)
|
||||
return 1;
|
||||
return v;
|
||||
}
|
||||
|
||||
static void change_volume_relative(struct control *control, int delta, unsigned int channels)
|
||||
{
|
||||
double (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t);
|
||||
int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int);
|
||||
double left, right;
|
||||
int dir;
|
||||
|
||||
if (!(control->flags & HAS_VOLUME_1))
|
||||
channels = LEFT;
|
||||
if (control->flags & TYPE_PVOLUME) {
|
||||
get_range_func = snd_mixer_selem_get_playback_volume_range;
|
||||
get_func = snd_mixer_selem_get_playback_volume;
|
||||
set_func = snd_mixer_selem_set_playback_volume;
|
||||
get_func = get_normalized_playback_volume;
|
||||
set_func = set_normalized_playback_volume;
|
||||
} else {
|
||||
get_range_func = snd_mixer_selem_get_capture_volume_range;
|
||||
get_func = snd_mixer_selem_get_capture_volume;
|
||||
set_func = snd_mixer_selem_set_capture_volume;
|
||||
get_func = get_normalized_capture_volume;
|
||||
set_func = set_normalized_capture_volume;
|
||||
}
|
||||
err = get_range_func(control->elem, &min, &max);
|
||||
if (err < 0)
|
||||
return;
|
||||
if (channels & LEFT)
|
||||
left = get_func(control->elem, control->volume_channels[0]);
|
||||
if (channels & RIGHT)
|
||||
right = get_func(control->elem, control->volume_channels[1]);
|
||||
dir = delta > 0 ? 1 : -1;
|
||||
if (channels & LEFT) {
|
||||
err = get_func(control->elem, control->volume_channels[0], &left);
|
||||
if (err < 0)
|
||||
return;
|
||||
left = clamp_volume(left + delta / 100.0);
|
||||
set_func(control->elem, control->volume_channels[0], left, dir);
|
||||
}
|
||||
if (channels & RIGHT) {
|
||||
err = get_func(control->elem, control->volume_channels[1], &right);
|
||||
if (err < 0)
|
||||
return;
|
||||
}
|
||||
if (max - min > 100)
|
||||
delta = (delta * (max - min) + (delta > 0 ? 99 : -99)) / 100;
|
||||
if (channels & LEFT) {
|
||||
value = left + delta;
|
||||
if (value < min)
|
||||
value = min;
|
||||
else if (value > max)
|
||||
value = max;
|
||||
if (value != left)
|
||||
set_func(control->elem, control->volume_channels[0], value);
|
||||
}
|
||||
if (channels & RIGHT) {
|
||||
value = right + delta;
|
||||
if (value < min)
|
||||
value = min;
|
||||
else if (value > max)
|
||||
value = max;
|
||||
if (value != right)
|
||||
set_func(control->elem, control->volume_channels[1], value);
|
||||
right = clamp_volume(right + delta / 100.0);
|
||||
set_func(control->elem, control->volume_channels[1], right, dir);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -460,34 +436,26 @@ static void toggle_capture(unsigned int channels)
|
|||
static void balance_volumes(void)
|
||||
{
|
||||
struct control *control;
|
||||
long left, right;
|
||||
double left, right;
|
||||
int err;
|
||||
|
||||
control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME);
|
||||
if (!control || !(control->flags & HAS_VOLUME_1))
|
||||
return;
|
||||
if (control->flags & TYPE_PVOLUME) {
|
||||
err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[0], &left);
|
||||
if (err < 0)
|
||||
return;
|
||||
err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[1], &right);
|
||||
if (err < 0)
|
||||
return;
|
||||
left = get_normalized_playback_volume(control->elem, control->volume_channels[0]);
|
||||
right = get_normalized_playback_volume(control->elem, control->volume_channels[1]);
|
||||
} else {
|
||||
err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[0], &left);
|
||||
if (err < 0)
|
||||
return;
|
||||
err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[1], &right);
|
||||
if (err < 0)
|
||||
return;
|
||||
left = get_normalized_capture_volume(control->elem, control->volume_channels[0]);
|
||||
right = get_normalized_capture_volume(control->elem, control->volume_channels[1]);
|
||||
}
|
||||
left = (left + right) / 2;
|
||||
if (control->flags & TYPE_PVOLUME) {
|
||||
snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[0], left);
|
||||
snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[1], left);
|
||||
set_normalized_playback_volume(control->elem, control->volume_channels[0], left, 0);
|
||||
set_normalized_playback_volume(control->elem, control->volume_channels[1], left, 0);
|
||||
} else {
|
||||
snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[0], left);
|
||||
snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[1], left);
|
||||
set_normalized_capture_volume(control->elem, control->volume_channels[0], left, 0);
|
||||
set_normalized_capture_volume(control->elem, control->volume_channels[1], left, 0);
|
||||
}
|
||||
display_controls();
|
||||
}
|
||||
|
|
180
alsamixer/volume_mapping.c
Normal file
180
alsamixer/volume_mapping.c
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright (c) 2010 Clemens Ladisch <clemens@ladisch.de>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The functions in this file map the value ranges of ALSA mixer controls onto
|
||||
* the interval 0..1.
|
||||
*
|
||||
* The mapping is designed so that the position in the interval is proportional
|
||||
* to the volume as a human ear would perceive it (i.e., the position is the
|
||||
* cubic root of the linear sample multiplication factor). For controls with
|
||||
* a small range (24 dB or less), the mapping is linear in the dB values so
|
||||
* that each step has the same size visually. Only for controls without dB
|
||||
* information, a linear mapping of the hardware volume register values is used
|
||||
* (this is the same algorithm as used in the old alsamixer).
|
||||
*
|
||||
* When setting the volume, 'dir' is the rounding direction:
|
||||
* -1/0/1 = down/nearest/up.
|
||||
*/
|
||||
|
||||
#define _ISOC99_SOURCE /* lrint() */
|
||||
#define _GNU_SOURCE /* exp10() */
|
||||
#include "aconfig.h"
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include "volume_mapping.h"
|
||||
|
||||
#define MAX_LINEAR_DB_SCALE 24
|
||||
|
||||
static inline bool use_linear_dB_scale(long dBmin, long dBmax)
|
||||
{
|
||||
return dBmax - dBmin <= MAX_LINEAR_DB_SCALE * 100;
|
||||
}
|
||||
|
||||
static long lrint_dir(double x, int dir)
|
||||
{
|
||||
if (dir > 0)
|
||||
return lrint(ceil(x));
|
||||
else if (dir < 0)
|
||||
return lrint(floor(x));
|
||||
else
|
||||
return lrint(x);
|
||||
}
|
||||
|
||||
enum ctl_dir { PLAYBACK, CAPTURE };
|
||||
|
||||
static int (* const get_dB_range[2])(snd_mixer_elem_t *, long *, long *) = {
|
||||
snd_mixer_selem_get_playback_dB_range,
|
||||
snd_mixer_selem_get_capture_dB_range,
|
||||
};
|
||||
static int (* const get_raw_range[2])(snd_mixer_elem_t *, long *, long *) = {
|
||||
snd_mixer_selem_get_playback_volume_range,
|
||||
snd_mixer_selem_get_capture_volume_range,
|
||||
};
|
||||
static int (* const get_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
|
||||
snd_mixer_selem_get_playback_dB,
|
||||
snd_mixer_selem_get_capture_dB,
|
||||
};
|
||||
static int (* const get_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
|
||||
snd_mixer_selem_get_playback_volume,
|
||||
snd_mixer_selem_get_capture_volume,
|
||||
};
|
||||
static int (* const set_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long, int) = {
|
||||
snd_mixer_selem_set_playback_dB,
|
||||
snd_mixer_selem_set_capture_dB,
|
||||
};
|
||||
static int (* const set_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long) = {
|
||||
snd_mixer_selem_set_playback_volume,
|
||||
snd_mixer_selem_set_capture_volume,
|
||||
};
|
||||
|
||||
static double get_normalized_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel,
|
||||
enum ctl_dir ctl_dir)
|
||||
{
|
||||
long min, max, value;
|
||||
double normalized, min_norm;
|
||||
int err;
|
||||
|
||||
err = get_dB_range[ctl_dir](elem, &min, &max);
|
||||
if (err < 0 || min >= max) {
|
||||
err = get_raw_range[ctl_dir](elem, &min, &max);
|
||||
if (err < 0 || min == max)
|
||||
return 0;
|
||||
|
||||
err = get_raw[ctl_dir](elem, channel, &value);
|
||||
if (err < 0)
|
||||
return 0;
|
||||
|
||||
return (value - min) / (double)(max - min);
|
||||
}
|
||||
|
||||
err = get_dB[ctl_dir](elem, channel, &value);
|
||||
if (err < 0)
|
||||
return 0;
|
||||
|
||||
if (use_linear_dB_scale(min, max))
|
||||
return (value - min) / (double)(max - min);
|
||||
|
||||
normalized = exp10((value - max) / 6000.0);
|
||||
if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
|
||||
min_norm = exp10((min - max) / 6000.0);
|
||||
normalized = (normalized - min_norm) / (1 - min_norm);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
static int set_normalized_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel,
|
||||
double volume,
|
||||
int dir,
|
||||
enum ctl_dir ctl_dir)
|
||||
{
|
||||
long min, max, value;
|
||||
double min_norm;
|
||||
int err;
|
||||
|
||||
err = get_dB_range[ctl_dir](elem, &min, &max);
|
||||
if (err < 0 || min >= max) {
|
||||
err = get_raw_range[ctl_dir](elem, &min, &max);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
value = lrint_dir(volume * (max - min), dir) + min;
|
||||
return set_raw[ctl_dir](elem, channel, value);
|
||||
}
|
||||
|
||||
if (use_linear_dB_scale(min, max)) {
|
||||
value = lrint_dir(volume * (max - min), dir) + min;
|
||||
return set_dB[ctl_dir](elem, channel, value, dir);
|
||||
}
|
||||
|
||||
if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
|
||||
min_norm = exp10((min - max) / 6000.0);
|
||||
volume = volume * (1 - min_norm) + min_norm;
|
||||
}
|
||||
value = lrint_dir(6000.0 * log10(volume), dir) + max;
|
||||
return set_dB[ctl_dir](elem, channel, value, dir);
|
||||
}
|
||||
|
||||
double get_normalized_playback_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel)
|
||||
{
|
||||
return get_normalized_volume(elem, channel, PLAYBACK);
|
||||
}
|
||||
|
||||
double get_normalized_capture_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel)
|
||||
{
|
||||
return get_normalized_volume(elem, channel, CAPTURE);
|
||||
}
|
||||
|
||||
int set_normalized_playback_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel,
|
||||
double volume,
|
||||
int dir)
|
||||
{
|
||||
return set_normalized_volume(elem, channel, volume, dir, PLAYBACK);
|
||||
}
|
||||
|
||||
int set_normalized_capture_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel,
|
||||
double volume,
|
||||
int dir)
|
||||
{
|
||||
return set_normalized_volume(elem, channel, volume, dir, CAPTURE);
|
||||
}
|
19
alsamixer/volume_mapping.h
Normal file
19
alsamixer/volume_mapping.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#ifndef VOLUME_MAPPING_H_INCLUDED
|
||||
#define VOLUME_MAPPING_H_INCLUDED
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
double get_normalized_playback_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel);
|
||||
double get_normalized_capture_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel);
|
||||
int set_normalized_playback_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel,
|
||||
double volume,
|
||||
int dir);
|
||||
int set_normalized_capture_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel,
|
||||
double volume,
|
||||
int dir);
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue