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 
  41 typedef 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 
  56 typedef 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 
  71 INT32 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 
 108 INT32 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 
 145 void* 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 
 212 void 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 
 235 INT32 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 
 279 INT32 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 
 298 INT32 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 
 320 static 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 */
 328 static 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  */
 341 inline 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 */
 353 static 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 
 375 void 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     int i;
 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     for (i = numControls; i < 10; i++) { controls[i] = NULL; }
 485     /* $$mp 2003-09-14: The following cast shouln't be necessary. Instead, the
 486        declaration of PORT_NewCompoundControlPtr in Ports.h should be changed
 487        to take a const char* parameter. */
 488     portName = (char*) snd_mixer_selem_get_name(elem);
 489     control = (creator->newCompoundControl)(creator, portName, controls, numControls);
 490     if (control != NULL) {
 491         (creator->addControl)(creator, control);
 492     }
 493     TRACE0("< PORT_GetControls\n");
 494 }
 495 
 496 
 497 INT32 PORT_GetIntValue(void* controlIDV) {
 498     PortControl* portControl = (PortControl*) controlIDV;
 499     int value = 0;
 500     snd_mixer_selem_channel_id_t channel;
 501 
 502     if (portControl != NULL) {
 503         switch (portControl->channel) {
 504         case CHANNELS_MONO:
 505             channel = SND_MIXER_SCHN_MONO;
 506             break;
 507 
 508         case CHANNELS_STEREO:
 509             channel = SND_MIXER_SCHN_FRONT_LEFT;
 510             break;
 511 
 512         default:
 513             channel = portControl->channel;
 514         }
 515         if (portControl->controlType == CONTROL_TYPE_MUTE ||
 516             portControl->controlType == CONTROL_TYPE_SELECT) {
 517             if (isPlaybackFunction(portControl->portType)) {
 518                 snd_mixer_selem_get_playback_switch(portControl->elem, channel, &value);
 519             } else {
 520                 snd_mixer_selem_get_capture_switch(portControl->elem, channel, &value);
 521             }
 522             if (portControl->controlType == CONTROL_TYPE_MUTE) {
 523                 value = ! value;
 524             }
 525         } else {
 526             ERROR1("PORT_GetIntValue(): inappropriate control type: %s\n",
 527                    portControl->controlType);
 528         }
 529     }
 530     return (INT32) value;
 531 }
 532 
 533 
 534 void PORT_SetIntValue(void* controlIDV, INT32 value) {
 535     PortControl* portControl = (PortControl*) controlIDV;
 536     snd_mixer_selem_channel_id_t channel;
 537 
 538     if (portControl != NULL) {
 539         if (portControl->controlType == CONTROL_TYPE_MUTE) {
 540             value = ! value;
 541         }
 542         if (portControl->controlType == CONTROL_TYPE_MUTE ||
 543             portControl->controlType == CONTROL_TYPE_SELECT) {
 544             if (isPlaybackFunction(portControl->portType)) {
 545                 snd_mixer_selem_set_playback_switch_all(portControl->elem, value);
 546             } else {
 547                 snd_mixer_selem_set_capture_switch_all(portControl->elem, value);
 548             }
 549         } else {
 550             ERROR1("PORT_SetIntValue(): inappropriate control type: %s\n",
 551                    portControl->controlType);
 552         }
 553     }
 554 }
 555 
 556 
 557 static float scaleVolumeValueToNormalized(long value, long min, long max) {
 558     return (float) (value - min) / getRange(min, max);
 559 }
 560 
 561 
 562 static long scaleVolumeValueToHardware(float value, long min, long max) {
 563     return (long)(value * getRange(min, max) + min);
 564 }
 565 
 566 
 567 float getRealVolume(PortControl* portControl,
 568                     snd_mixer_selem_channel_id_t channel) {
 569     float fValue;
 570     long lValue = 0;
 571     long min = 0;
 572     long max = 0;
 573 
 574     if (isPlaybackFunction(portControl->portType)) {
 575         snd_mixer_selem_get_playback_volume_range(portControl->elem,
 576                                                   &min, &max);
 577         snd_mixer_selem_get_playback_volume(portControl->elem,
 578                                             channel, &lValue);
 579     } else {
 580         snd_mixer_selem_get_capture_volume_range(portControl->elem,
 581                                                  &min, &max);
 582         snd_mixer_selem_get_capture_volume(portControl->elem,
 583                                            channel, &lValue);
 584     }
 585     fValue = scaleVolumeValueToNormalized(lValue, min, max);
 586     return fValue;
 587 }
 588 
 589 
 590 void setRealVolume(PortControl* portControl,
 591                    snd_mixer_selem_channel_id_t channel, float value) {
 592     long lValue = 0;
 593     long min = 0;
 594     long max = 0;
 595 
 596     if (isPlaybackFunction(portControl->portType)) {
 597         snd_mixer_selem_get_playback_volume_range(portControl->elem,
 598                                                   &min, &max);
 599         lValue = scaleVolumeValueToHardware(value, min, max);
 600         snd_mixer_selem_set_playback_volume(portControl->elem,
 601                                             channel, lValue);
 602     } else {
 603         snd_mixer_selem_get_capture_volume_range(portControl->elem,
 604                                                  &min, &max);
 605         lValue = scaleVolumeValueToHardware(value, min, max);
 606         snd_mixer_selem_set_capture_volume(portControl->elem,
 607                                            channel, lValue);
 608     }
 609 }
 610 
 611 
 612 static float getFakeBalance(PortControl* portControl) {
 613     float volL, volR;
 614 
 615     // pan is the ratio of left and right
 616     volL = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_LEFT);
 617     volR = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_RIGHT);
 618     if (volL > volR) {
 619         return -1.0f + (volR / volL);
 620     }
 621     else if (volR > volL) {
 622         return 1.0f - (volL / volR);
 623     }
 624     return 0.0f;
 625 }
 626 
 627 
 628 static float getFakeVolume(PortControl* portControl) {
 629     float valueL;
 630     float valueR;
 631     float value;
 632 
 633     valueL = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_LEFT);
 634     valueR = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_RIGHT);
 635     // volume is the greater value of both
 636     value = valueL > valueR ? valueL : valueR ;
 637     return value;
 638 }
 639 
 640 
 641 /*
 642  * sets the unsigned values for left and right volume according to
 643  * the given volume (0...1) and balance (-1..0..+1)
 644  */
 645 static void setFakeVolume(PortControl* portControl, float vol, float bal) {
 646     float volumeLeft;
 647     float volumeRight;
 648 
 649     if (bal < 0.0f) {
 650         volumeLeft = vol;
 651         volumeRight = vol * (bal + 1.0f);
 652     } else {
 653         volumeLeft = vol * (1.0f - bal);
 654         volumeRight = vol;
 655     }
 656     setRealVolume(portControl, SND_MIXER_SCHN_FRONT_LEFT, volumeLeft);
 657     setRealVolume(portControl, SND_MIXER_SCHN_FRONT_RIGHT, volumeRight);
 658 }
 659 
 660 
 661 float PORT_GetFloatValue(void* controlIDV) {
 662     PortControl* portControl = (PortControl*) controlIDV;
 663     float value = 0.0F;
 664 
 665     if (portControl != NULL) {
 666         if (portControl->controlType == CONTROL_TYPE_VOLUME) {
 667             switch (portControl->channel) {
 668             case CHANNELS_MONO:
 669                 value = getRealVolume(portControl, SND_MIXER_SCHN_MONO);
 670                 break;
 671 
 672             case CHANNELS_STEREO:
 673                 value = getFakeVolume(portControl);
 674                 break;
 675 
 676             default:
 677                 value = getRealVolume(portControl, portControl->channel);
 678             }
 679         } else if (portControl->controlType == CONTROL_TYPE_BALANCE) {
 680             if (portControl->channel == CHANNELS_STEREO) {
 681                 value = getFakeBalance(portControl);
 682             } else {
 683                 ERROR0("PORT_GetFloatValue(): Balance only allowed for stereo channels!\n");
 684             }
 685         } else {
 686             ERROR1("PORT_GetFloatValue(): inappropriate control type: %s!\n",
 687                    portControl->controlType);
 688         }
 689     }
 690     return value;
 691 }
 692 
 693 
 694 void PORT_SetFloatValue(void* controlIDV, float value) {
 695     PortControl* portControl = (PortControl*) controlIDV;
 696 
 697     if (portControl != NULL) {
 698         if (portControl->controlType == CONTROL_TYPE_VOLUME) {
 699             switch (portControl->channel) {
 700             case CHANNELS_MONO:
 701                 setRealVolume(portControl, SND_MIXER_SCHN_MONO, value);
 702                 break;
 703 
 704             case CHANNELS_STEREO:
 705                 setFakeVolume(portControl, value, getFakeBalance(portControl));
 706                 break;
 707 
 708             default:
 709                 setRealVolume(portControl, portControl->channel, value);
 710             }
 711         } else if (portControl->controlType == CONTROL_TYPE_BALANCE) {
 712             if (portControl->channel == CHANNELS_STEREO) {
 713                 setFakeVolume(portControl, getFakeVolume(portControl), value);
 714             } else {
 715                 ERROR0("PORT_SetFloatValue(): Balance only allowed for stereo channels!\n");
 716             }
 717         } else {
 718             ERROR1("PORT_SetFloatValue(): inappropriate control type: %s!\n",
 719                    portControl->controlType);
 720         }
 721     }
 722 }
 723 
 724 
 725 #endif // USE_PORTS