1/*
2 * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26#define USE_ERROR
27//#define USE_TRACE
28
29#include "Ports.h"
30#include "PLATFORM_API_LinuxOS_ALSA_CommonUtils.h"
31#include <alsa/asoundlib.h>
32
33#if USE_PORTS == TRUE
34
35#define MAX_ELEMS (300)
36#define MAX_CONTROLS (MAX_ELEMS * 4)
37
38#define CHANNELS_MONO (SND_MIXER_SCHN_LAST + 1)
39#define CHANNELS_STEREO (SND_MIXER_SCHN_LAST + 2)
40
41typedef struct {
42    snd_mixer_elem_t* elem;
43    INT32 portType; /* one of PORT_XXX_xx */
44    char* controlType; /* one of CONTROL_TYPE_xx */
45    /* Values: either SND_MIXER_SCHN_FRONT_xx, CHANNELS_MONO or CHANNELS_STEREO.
46       For SND_MIXER_SCHN_FRONT_xx, exactly this channel is set/retrieved directly.
47       For CHANNELS_MONO, ALSA channel SND_MIXER_SCHN_MONO is set/retrieved directly.
48       For CHANNELS_STEREO, ALSA channels SND_MIXER_SCHN_FRONT_LEFT and SND_MIXER_SCHN_FRONT_RIGHT
49       are set after a calculation that takes balance into account. Retrieved? Average of both
50       channels? (Using a cached value is not a good idea since the value in the HW may have been
51       altered.) */
52    INT32 channel;
53} PortControl;
54
55
56typedef struct tag_PortMixer {
57    snd_mixer_t* mixer_handle;
58    /* Number of array elements used in elems and types. */
59    int numElems;
60    snd_mixer_elem_t** elems;
61    /* Array of port types (PORT_SRC_UNKNOWN etc.). Indices are the same as in elems. */
62    INT32* types;
63    /* Number of array elements used in controls. */
64    int numControls;
65    PortControl* controls;
66} PortMixer;
67
68
69///// implemented functions of Ports.h
70
71INT32 PORT_GetPortMixerCount() {
72    INT32 mixerCount;
73    int card;
74    char devname[16];
75    int err;
76    snd_ctl_t *handle;
77    snd_ctl_card_info_t* info;
78
79    TRACE0("> PORT_GetPortMixerCount\n");
80
81    initAlsaSupport();
82
83    snd_ctl_card_info_malloc(&info);
84    card = -1;
85    mixerCount = 0;
86    if (snd_card_next(&card) >= 0) {
87        while (card >= 0) {
88            sprintf(devname, ALSA_HARDWARE_CARD, card);
89            TRACE1("PORT_GetPortMixerCount: Opening alsa device \"%s\"...\n", devname);
90            err = snd_ctl_open(&handle, devname, 0);
91            if (err < 0) {
92                ERROR2("ERROR: snd_ctl_open, card=%d: %s\n", card, snd_strerror(err));
93            } else {
94                mixerCount++;
95                snd_ctl_close(handle);
96            }
97            if (snd_card_next(&card) < 0) {
98                break;
99            }
100        }
101    }
102    snd_ctl_card_info_free(info);
103    TRACE0("< PORT_GetPortMixerCount\n");
104    return mixerCount;
105}
106
107
108INT32 PORT_GetPortMixerDescription(INT32 mixerIndex, PortMixerDescription* description) {
109    snd_ctl_t* handle;
110    snd_ctl_card_info_t* card_info;
111    char devname[16];
112    int err;
113    char buffer[100];
114
115    TRACE0("> PORT_GetPortMixerDescription\n");
116    snd_ctl_card_info_malloc(&card_info);
117
118    sprintf(devname, ALSA_HARDWARE_CARD, (int) mixerIndex);
119    TRACE1("Opening alsa device \"%s\"...\n", devname);
120    err = snd_ctl_open(&handle, devname, 0);
121    if (err < 0) {
122        ERROR2("ERROR: snd_ctl_open, card=%d: %s\n", (int) mixerIndex, snd_strerror(err));
123        return FALSE;
124    }
125    err = snd_ctl_card_info(handle, card_info);
126    if (err < 0) {
127        ERROR2("ERROR: snd_ctl_card_info, card=%d: %s\n", (int) mixerIndex, snd_strerror(err));
128    }
129    strncpy(description->name, snd_ctl_card_info_get_id(card_info), PORT_STRING_LENGTH - 1);
130    sprintf(buffer, " [%s]", devname);
131    strncat(description->name, buffer, PORT_STRING_LENGTH - 1 - strlen(description->name));
132    strncpy(description->vendor, "ALSA (http://www.alsa-project.org)", PORT_STRING_LENGTH - 1);
133    strncpy(description->description, snd_ctl_card_info_get_name(card_info), PORT_STRING_LENGTH - 1);
134    strncat(description->description, ", ", PORT_STRING_LENGTH - 1 - strlen(description->description));
135    strncat(description->description, snd_ctl_card_info_get_mixername(card_info), PORT_STRING_LENGTH - 1 - strlen(description->description));
136    getALSAVersion(description->version, PORT_STRING_LENGTH - 1);
137
138    snd_ctl_close(handle);
139    snd_ctl_card_info_free(card_info);
140    TRACE0("< PORT_GetPortMixerDescription\n");
141    return TRUE;
142}
143
144
145void* PORT_Open(INT32 mixerIndex) {
146    char devname[16];
147    snd_mixer_t* mixer_handle;
148    int err;
149    PortMixer* handle;
150
151    TRACE0("> PORT_Open\n");
152    sprintf(devname, ALSA_HARDWARE_CARD, (int) mixerIndex);
153    if ((err = snd_mixer_open(&mixer_handle, 0)) < 0) {
154        ERROR2("Mixer %s open error: %s", devname, snd_strerror(err));
155        return NULL;
156    }
157    if ((err = snd_mixer_attach(mixer_handle, devname)) < 0) {
158        ERROR2("Mixer attach %s error: %s", devname, snd_strerror(err));
159        snd_mixer_close(mixer_handle);
160        return NULL;
161    }
162    if ((err = snd_mixer_selem_register(mixer_handle, NULL, NULL)) < 0) {
163        ERROR1("Mixer register error: %s", snd_strerror(err));
164        snd_mixer_close(mixer_handle);
165        return NULL;
166    }
167    err = snd_mixer_load(mixer_handle);
168    if (err < 0) {
169        ERROR2("Mixer %s load error: %s", devname, snd_strerror(err));
170        snd_mixer_close(mixer_handle);
171        return NULL;
172    }
173    handle = (PortMixer*) calloc(1, sizeof(PortMixer));
174    if (handle == NULL) {
175        ERROR0("malloc() failed.");
176        snd_mixer_close(mixer_handle);
177        return NULL;
178    }
179    handle->numElems = 0;
180    handle->elems = (snd_mixer_elem_t**) calloc(MAX_ELEMS, sizeof(snd_mixer_elem_t*));
181    if (handle->elems == NULL) {
182        ERROR0("malloc() failed.");
183        snd_mixer_close(mixer_handle);
184        free(handle);
185        return NULL;
186    }
187    handle->types = (INT32*) calloc(MAX_ELEMS, sizeof(INT32));
188    if (handle->types == NULL) {
189        ERROR0("malloc() failed.");
190        snd_mixer_close(mixer_handle);
191        free(handle->elems);
192        free(handle);
193        return NULL;
194    }
195    handle->controls = (PortControl*) calloc(MAX_CONTROLS, sizeof(PortControl));
196    if (handle->controls == NULL) {
197        ERROR0("malloc() failed.");
198        snd_mixer_close(mixer_handle);
199        free(handle->elems);
200        free(handle->types);
201        free(handle);
202        return NULL;
203    }
204    handle->mixer_handle = mixer_handle;
205    // necessary to initialize data structures
206    PORT_GetPortCount(handle);
207    TRACE0("< PORT_Open\n");
208    return handle;
209}
210
211
212void PORT_Close(void* id) {
213    TRACE0("> PORT_Close\n");
214    if (id != NULL) {
215        PortMixer* handle = (PortMixer*) id;
216        if (handle->mixer_handle != NULL) {
217            snd_mixer_close(handle->mixer_handle);
218        }
219        if (handle->elems != NULL) {
220            free(handle->elems);
221        }
222        if (handle->types != NULL) {
223            free(handle->types);
224        }
225        if (handle->controls != NULL) {
226            free(handle->controls);
227        }
228        free(handle);
229    }
230    TRACE0("< PORT_Close\n");
231}
232
233
234
235INT32 PORT_GetPortCount(void* id) {
236    PortMixer* portMixer;
237    snd_mixer_elem_t *elem;
238
239    TRACE0("> PORT_GetPortCount\n");
240    if (id == NULL) {
241        // $$mp: Should become a descriptive error code (invalid handle).
242        return -1;
243    }
244    portMixer = (PortMixer*) id;
245    if (portMixer->numElems == 0) {
246        for (elem = snd_mixer_first_elem(portMixer->mixer_handle); elem; elem = snd_mixer_elem_next(elem)) {
247            if (!snd_mixer_selem_is_active(elem))
248                continue;
249            TRACE2("Simple mixer control '%s',%i\n",
250                   snd_mixer_selem_get_name(elem),
251                   snd_mixer_selem_get_index(elem));
252            if (snd_mixer_selem_has_playback_volume(elem)) {
253                portMixer->elems[portMixer->numElems] = elem;
254                portMixer->types[portMixer->numElems] = PORT_DST_UNKNOWN;
255                portMixer->numElems++;
256            }
257            // to prevent buffer overflow
258            if (portMixer->numElems >= MAX_ELEMS) {
259                break;
260            }
261            /* If an element has both playback an capture volume, it is put into the arrays
262               twice. */
263            if (snd_mixer_selem_has_capture_volume(elem)) {
264                portMixer->elems[portMixer->numElems] = elem;
265                portMixer->types[portMixer->numElems] = PORT_SRC_UNKNOWN;
266                portMixer->numElems++;
267            }
268            // to prevent buffer overflow
269            if (portMixer->numElems >= MAX_ELEMS) {
270                break;
271            }
272        }
273    }
274    TRACE0("< PORT_GetPortCount\n");
275    return portMixer->numElems;
276}
277
278
279INT32 PORT_GetPortType(void* id, INT32 portIndex) {
280    PortMixer* portMixer;
281    INT32 type;
282    TRACE0("> PORT_GetPortType\n");
283    if (id == NULL) {
284        // $$mp: Should become a descriptive error code (invalid handle).
285        return -1;
286    }
287    portMixer = (PortMixer*) id;
288    if (portIndex < 0 || portIndex >= portMixer->numElems) {
289        // $$mp: Should become a descriptive error code (index out of bounds).
290        return -1;
291    }
292    type = portMixer->types[portIndex];
293    TRACE0("< PORT_GetPortType\n");
294    return type;
295}
296
297
298INT32 PORT_GetPortName(void* id, INT32 portIndex, char* name, INT32 len) {
299    PortMixer* portMixer;
300    const char* nam;
301
302    TRACE0("> PORT_GetPortName\n");
303    if (id == NULL) {
304        // $$mp: Should become a descriptive error code (invalid handle).
305        return -1;
306    }
307    portMixer = (PortMixer*) id;
308    if (portIndex < 0 || portIndex >= portMixer->numElems) {
309        // $$mp: Should become a descriptive error code (index out of bounds).
310        return -1;
311    }
312    nam = snd_mixer_selem_get_name(portMixer->elems[portIndex]);
313    strncpy(name, nam, len - 1);
314    name[len - 1] = 0;
315    TRACE0("< PORT_GetPortName\n");
316    return TRUE;
317}
318
319
320static int isPlaybackFunction(INT32 portType) {
321        return (portType & PORT_DST_MASK);
322}
323
324
325/* Sets portControl to a pointer to the next free array element in the PortControl (pointer)
326   array of the passed portMixer. Returns TRUE if successful. May return FALSE if there is no
327   free slot. In this case, portControl is not altered */
328static int getControlSlot(PortMixer* portMixer, PortControl** portControl) {
329    if (portMixer->numControls >= MAX_CONTROLS) {
330        return FALSE;
331    } else {
332        *portControl = &(portMixer->controls[portMixer->numControls]);
333        portMixer->numControls++;
334        return TRUE;
335    }
336}
337
338
339/* Protect against illegal min-max values, preventing divisions by zero.
340 */
341inline static long getRange(long min, long max) {
342    if (max > min) {
343        return max - min;
344    } else {
345        return 1;
346    }
347}
348
349
350/* Idea: we may specify that if unit is an empty string, the values are linear and if unit is "dB",
351   the values are logarithmic.
352*/
353static void* createVolumeControl(PortControlCreator* creator,
354                                 PortControl* portControl,
355                                 snd_mixer_elem_t* elem, int isPlayback) {
356    void* control;
357    float precision;
358    long min, max;
359
360    if (isPlayback) {
361        snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
362    } else {
363        snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
364    }
365    /* $$mp: The volume values retrieved with the ALSA API are strongly supposed to be logarithmic.
366       So the following calculation is wrong. However, there is no correct calculation, since
367       for equal-distant logarithmic steps, the precision expressed in linear varies over the
368       scale. */
369    precision = 1.0F / getRange(min, max);
370    control = (creator->newFloatControl)(creator, portControl, CONTROL_TYPE_VOLUME, 0.0F, +1.0F, precision, "");
371    return control;
372}
373
374
375void PORT_GetControls(void* id, INT32 portIndex, PortControlCreator* creator) {
376    PortMixer* portMixer;
377    snd_mixer_elem_t* elem;
378    void* control;
379    PortControl* portControl;
380    void* controls[10];
381    int numControls;
382    char* portName;
383    int isPlayback = 0;
384    int isMono;
385    int isStereo;
386    char* type;
387    snd_mixer_selem_channel_id_t channel;
388    memset(controls, 0, sizeof(controls));
389
390    TRACE0("> PORT_GetControls\n");
391    if (id == NULL) {
392        ERROR0("Invalid handle!");
393        // $$mp: an error code should be returned.
394        return;
395    }
396    portMixer = (PortMixer*) id;
397    if (portIndex < 0 || portIndex >= portMixer->numElems) {
398        ERROR0("Port index out of range!");
399        // $$mp: an error code should be returned.
400        return;
401    }
402    numControls = 0;
403    elem = portMixer->elems[portIndex];
404    if (snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem)) {
405        /* Since we've split/duplicated elements with both playback and capture on the recovery
406           of elements, we now can assume that we handle only to deal with either playback or
407           capture. */
408        isPlayback = isPlaybackFunction(portMixer->types[portIndex]);
409        isMono = (isPlayback && snd_mixer_selem_is_playback_mono(elem)) ||
410            (!isPlayback && snd_mixer_selem_is_capture_mono(elem));
411        isStereo = (isPlayback &&
412                    snd_mixer_selem_has_playback_channel(elem, SND_MIXER_SCHN_FRONT_LEFT) &&
413                    snd_mixer_selem_has_playback_channel(elem, SND_MIXER_SCHN_FRONT_RIGHT)) ||
414            (!isPlayback &&
415             snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_FRONT_LEFT) &&
416             snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_FRONT_RIGHT));
417        // single volume control
418        if (isMono || isStereo) {
419            if (getControlSlot(portMixer, &portControl)) {
420                portControl->elem = elem;
421                portControl->portType = portMixer->types[portIndex];
422                portControl->controlType = CONTROL_TYPE_VOLUME;
423                if (isMono) {
424                    portControl->channel = CHANNELS_MONO;
425                } else {
426                    portControl->channel = CHANNELS_STEREO;
427                }
428                control = createVolumeControl(creator, portControl, elem, isPlayback);
429                if (control != NULL) {
430                    controls[numControls++] = control;
431                }
432            }
433        } else { // more than two channels, each channels has its own control.
434            for (channel = SND_MIXER_SCHN_FRONT_LEFT; channel <= SND_MIXER_SCHN_LAST; channel++) {
435                if ((isPlayback && snd_mixer_selem_has_playback_channel(elem, channel)) ||
436                    (!isPlayback && snd_mixer_selem_has_capture_channel(elem, channel))) {
437                    if (getControlSlot(portMixer, &portControl)) {
438                        portControl->elem = elem;
439                        portControl->portType = portMixer->types[portIndex];
440                        portControl->controlType = CONTROL_TYPE_VOLUME;
441                        portControl->channel = channel;
442                        control = createVolumeControl(creator, portControl, elem, isPlayback);
443                        // We wrap in a compound control to provide the channel name.
444                        if (control != NULL) {
445                            /* $$mp 2003-09-14: The following cast shouln't be necessary. Instead, the
446                               declaration of PORT_NewCompoundControlPtr in Ports.h should be changed
447                               to take a const char* parameter. */
448                            control = (creator->newCompoundControl)(creator, (char*) snd_mixer_selem_channel_name(channel), &control, 1);
449                        }
450                        if (control != NULL) {
451                            controls[numControls++] = control;
452                        }
453                    }
454                }
455            }
456        }
457        // BALANCE control
458        if (isStereo) {
459            if (getControlSlot(portMixer, &portControl)) {
460                portControl->elem = elem;
461                portControl->portType = portMixer->types[portIndex];
462                portControl->controlType = CONTROL_TYPE_BALANCE;
463                portControl->channel = CHANNELS_STEREO;
464                /* $$mp: The value for precision is chosen more or less arbitrarily. */
465                control = (creator->newFloatControl)(creator, portControl, CONTROL_TYPE_BALANCE, -1.0F, 1.0F, 0.01F, "");
466                if (control != NULL) {
467                    controls[numControls++] = control;
468                }
469            }
470        }
471    }
472    if (snd_mixer_selem_has_playback_switch(elem) || snd_mixer_selem_has_capture_switch(elem)) {
473        if (getControlSlot(portMixer, &portControl)) {
474            type = isPlayback ? CONTROL_TYPE_MUTE : CONTROL_TYPE_SELECT;
475            portControl->elem = elem;
476            portControl->portType = portMixer->types[portIndex];
477            portControl->controlType = type;
478            control = (creator->newBooleanControl)(creator, portControl, type);
479            if (control != NULL) {
480                controls[numControls++] = control;
481            }
482        }
483    }
484    /* $$mp 2003-09-14: The following cast shouln't be necessary. Instead, the
485       declaration of PORT_NewCompoundControlPtr in Ports.h should be changed
486       to take a const char* parameter. */
487    portName = (char*) snd_mixer_selem_get_name(elem);
488    control = (creator->newCompoundControl)(creator, portName, controls, numControls);
489    if (control != NULL) {
490        (creator->addControl)(creator, control);
491    }
492    TRACE0("< PORT_GetControls\n");
493}
494
495
496INT32 PORT_GetIntValue(void* controlIDV) {
497    PortControl* portControl = (PortControl*) controlIDV;
498    int value = 0;
499    snd_mixer_selem_channel_id_t channel;
500
501    if (portControl != NULL) {
502        switch (portControl->channel) {
503        case CHANNELS_MONO:
504            channel = SND_MIXER_SCHN_MONO;
505            break;
506
507        case CHANNELS_STEREO:
508            channel = SND_MIXER_SCHN_FRONT_LEFT;
509            break;
510
511        default:
512            channel = portControl->channel;
513        }
514        if (portControl->controlType == CONTROL_TYPE_MUTE ||
515            portControl->controlType == CONTROL_TYPE_SELECT) {
516            if (isPlaybackFunction(portControl->portType)) {
517                snd_mixer_selem_get_playback_switch(portControl->elem, channel, &value);
518            } else {
519                snd_mixer_selem_get_capture_switch(portControl->elem, channel, &value);
520            }
521            if (portControl->controlType == CONTROL_TYPE_MUTE) {
522                value = ! value;
523            }
524        } else {
525            ERROR1("PORT_GetIntValue(): inappropriate control type: %s\n",
526                   portControl->controlType);
527        }
528    }
529    return (INT32) value;
530}
531
532
533void PORT_SetIntValue(void* controlIDV, INT32 value) {
534    PortControl* portControl = (PortControl*) controlIDV;
535    snd_mixer_selem_channel_id_t channel;
536
537    if (portControl != NULL) {
538        if (portControl->controlType == CONTROL_TYPE_MUTE) {
539            value = ! value;
540        }
541        if (portControl->controlType == CONTROL_TYPE_MUTE ||
542            portControl->controlType == CONTROL_TYPE_SELECT) {
543            if (isPlaybackFunction(portControl->portType)) {
544                snd_mixer_selem_set_playback_switch_all(portControl->elem, value);
545            } else {
546                snd_mixer_selem_set_capture_switch_all(portControl->elem, value);
547            }
548        } else {
549            ERROR1("PORT_SetIntValue(): inappropriate control type: %s\n",
550                   portControl->controlType);
551        }
552    }
553}
554
555
556static float scaleVolumeValueToNormalized(long value, long min, long max) {
557    return (float) (value - min) / getRange(min, max);
558}
559
560
561static long scaleVolumeValueToHardware(float value, long min, long max) {
562    return (long)(value * getRange(min, max) + min);
563}
564
565
566float getRealVolume(PortControl* portControl,
567                    snd_mixer_selem_channel_id_t channel) {
568    float fValue;
569    long lValue = 0;
570    long min = 0;
571    long max = 0;
572
573    if (isPlaybackFunction(portControl->portType)) {
574        snd_mixer_selem_get_playback_volume_range(portControl->elem,
575                                                  &min, &max);
576        snd_mixer_selem_get_playback_volume(portControl->elem,
577                                            channel, &lValue);
578    } else {
579        snd_mixer_selem_get_capture_volume_range(portControl->elem,
580                                                 &min, &max);
581        snd_mixer_selem_get_capture_volume(portControl->elem,
582                                           channel, &lValue);
583    }
584    fValue = scaleVolumeValueToNormalized(lValue, min, max);
585    return fValue;
586}
587
588
589void setRealVolume(PortControl* portControl,
590                   snd_mixer_selem_channel_id_t channel, float value) {
591    long lValue = 0;
592    long min = 0;
593    long max = 0;
594
595    if (isPlaybackFunction(portControl->portType)) {
596        snd_mixer_selem_get_playback_volume_range(portControl->elem,
597                                                  &min, &max);
598        lValue = scaleVolumeValueToHardware(value, min, max);
599        snd_mixer_selem_set_playback_volume(portControl->elem,
600                                            channel, lValue);
601    } else {
602        snd_mixer_selem_get_capture_volume_range(portControl->elem,
603                                                 &min, &max);
604        lValue = scaleVolumeValueToHardware(value, min, max);
605        snd_mixer_selem_set_capture_volume(portControl->elem,
606                                           channel, lValue);
607    }
608}
609
610
611static float getFakeBalance(PortControl* portControl) {
612    float volL, volR;
613
614    // pan is the ratio of left and right
615    volL = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_LEFT);
616    volR = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_RIGHT);
617    if (volL > volR) {
618        return -1.0f + (volR / volL);
619    }
620    else if (volR > volL) {
621        return 1.0f - (volL / volR);
622    }
623    return 0.0f;
624}
625
626
627static float getFakeVolume(PortControl* portControl) {
628    float valueL;
629    float valueR;
630    float value;
631
632    valueL = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_LEFT);
633    valueR = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_RIGHT);
634    // volume is the greater value of both
635    value = valueL > valueR ? valueL : valueR ;
636    return value;
637}
638
639
640/*
641 * sets the unsigned values for left and right volume according to
642 * the given volume (0...1) and balance (-1..0..+1)
643 */
644static void setFakeVolume(PortControl* portControl, float vol, float bal) {
645    float volumeLeft;
646    float volumeRight;
647
648    if (bal < 0.0f) {
649        volumeLeft = vol;
650        volumeRight = vol * (bal + 1.0f);
651    } else {
652        volumeLeft = vol * (1.0f - bal);
653        volumeRight = vol;
654    }
655    setRealVolume(portControl, SND_MIXER_SCHN_FRONT_LEFT, volumeLeft);
656    setRealVolume(portControl, SND_MIXER_SCHN_FRONT_RIGHT, volumeRight);
657}
658
659
660float PORT_GetFloatValue(void* controlIDV) {
661    PortControl* portControl = (PortControl*) controlIDV;
662    float value = 0.0F;
663
664    if (portControl != NULL) {
665        if (portControl->controlType == CONTROL_TYPE_VOLUME) {
666            switch (portControl->channel) {
667            case CHANNELS_MONO:
668                value = getRealVolume(portControl, SND_MIXER_SCHN_MONO);
669                break;
670
671            case CHANNELS_STEREO:
672                value = getFakeVolume(portControl);
673                break;
674
675            default:
676                value = getRealVolume(portControl, portControl->channel);
677            }
678        } else if (portControl->controlType == CONTROL_TYPE_BALANCE) {
679            if (portControl->channel == CHANNELS_STEREO) {
680                value = getFakeBalance(portControl);
681            } else {
682                ERROR0("PORT_GetFloatValue(): Balance only allowed for stereo channels!\n");
683            }
684        } else {
685            ERROR1("PORT_GetFloatValue(): inappropriate control type: %s!\n",
686                   portControl->controlType);
687        }
688    }
689    return value;
690}
691
692
693void PORT_SetFloatValue(void* controlIDV, float value) {
694    PortControl* portControl = (PortControl*) controlIDV;
695
696    if (portControl != NULL) {
697        if (portControl->controlType == CONTROL_TYPE_VOLUME) {
698            switch (portControl->channel) {
699            case CHANNELS_MONO:
700                setRealVolume(portControl, SND_MIXER_SCHN_MONO, value);
701                break;
702
703            case CHANNELS_STEREO:
704                setFakeVolume(portControl, value, getFakeBalance(portControl));
705                break;
706
707            default:
708                setRealVolume(portControl, portControl->channel, value);
709            }
710        } else if (portControl->controlType == CONTROL_TYPE_BALANCE) {
711            if (portControl->channel == CHANNELS_STEREO) {
712                setFakeVolume(portControl, getFakeVolume(portControl), value);
713            } else {
714                ERROR0("PORT_SetFloatValue(): Balance only allowed for stereo channels!\n");
715            }
716        } else {
717            ERROR1("PORT_SetFloatValue(): inappropriate control type: %s!\n",
718                   portControl->controlType);
719        }
720    }
721}
722
723
724#endif // USE_PORTS
725