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_BsdOS_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     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 
 496 INT32 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 
 533 void 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 
 556 static float scaleVolumeValueToNormalized(long value, long min, long max) {
 557     return (float) (value - min) / getRange(min, max);
 558 }
 559 
 560 
 561 static long scaleVolumeValueToHardware(float value, long min, long max) {
 562     return (long)(value * getRange(min, max) + min);
 563 }
 564 
 565 
 566 float 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 
 589 void 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 
 611 static 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 
 627 static 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  */
 644 static 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 
 660 float 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 
 693 void 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