1 /*
   2  * Copyright (c) 2003, 2013, 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 
 389     TRACE0("> PORT_GetControls\n");
 390     if (id == NULL) {
 391         ERROR0("Invalid handle!");
 392         // $$mp: an error code should be returned.
 393         return;
 394     }
 395     portMixer = (PortMixer*) id;
 396     if (portIndex < 0 || portIndex >= portMixer->numElems) {
 397         ERROR0("Port index out of range!");
 398         // $$mp: an error code should be returned.
 399         return;
 400     }
 401     numControls = 0;
 402     elem = portMixer->elems[portIndex];
 403     if (snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem)) {
 404         /* Since we've split/duplicated elements with both playback and capture on the recovery
 405            of elements, we now can assume that we handle only to deal with either playback or
 406            capture. */
 407         isPlayback = isPlaybackFunction(portMixer->types[portIndex]);
 408         isMono = (isPlayback && snd_mixer_selem_is_playback_mono(elem)) ||
 409             (!isPlayback && snd_mixer_selem_is_capture_mono(elem));
 410         isStereo = (isPlayback &&
 411                     snd_mixer_selem_has_playback_channel(elem, SND_MIXER_SCHN_FRONT_LEFT) &&
 412                     snd_mixer_selem_has_playback_channel(elem, SND_MIXER_SCHN_FRONT_RIGHT)) ||
 413             (!isPlayback &&
 414              snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_FRONT_LEFT) &&
 415              snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_FRONT_RIGHT));
 416         // single volume control
 417         if (isMono || isStereo) {
 418             if (getControlSlot(portMixer, &portControl)) {
 419                 portControl->elem = elem;
 420                 portControl->portType = portMixer->types[portIndex];
 421                 portControl->controlType = CONTROL_TYPE_VOLUME;
 422                 if (isMono) {
 423                     portControl->channel = CHANNELS_MONO;
 424                 } else {
 425                     portControl->channel = CHANNELS_STEREO;
 426                 }
 427                 control = createVolumeControl(creator, portControl, elem, isPlayback);
 428                 if (control != NULL) {
 429                     controls[numControls++] = control;
 430                 }
 431             }
 432         } else { // more than two channels, each channels has its own control.
 433             for (channel = SND_MIXER_SCHN_FRONT_LEFT; channel <= SND_MIXER_SCHN_LAST; channel++) {
 434                 if (isPlayback && snd_mixer_selem_has_playback_channel(elem, channel) ||
 435                     !isPlayback && snd_mixer_selem_has_capture_channel(elem, channel)) {
 436                     if (getControlSlot(portMixer, &portControl)) {
 437                         portControl->elem = elem;
 438                         portControl->portType = portMixer->types[portIndex];
 439                         portControl->controlType = CONTROL_TYPE_VOLUME;
 440                         portControl->channel = channel;
 441                         control = createVolumeControl(creator, portControl, elem, isPlayback);
 442                         // We wrap in a compound control to provide the channel name.
 443                         if (control != NULL) {
 444                             /* $$mp 2003-09-14: The following cast shouln't be necessary. Instead, the
 445                                declaration of PORT_NewCompoundControlPtr in Ports.h should be changed
 446                                to take a const char* parameter. */
 447                             control = (creator->newCompoundControl)(creator, (char*) snd_mixer_selem_channel_name(channel), &control, 1);
 448                         }
 449                         if (control != NULL) {
 450                             controls[numControls++] = control;
 451                         }
 452                     }
 453                 }
 454             }
 455         }
 456         // BALANCE control
 457         if (isStereo) {
 458             if (getControlSlot(portMixer, &portControl)) {
 459                 portControl->elem = elem;
 460                 portControl->portType = portMixer->types[portIndex];
 461                 portControl->controlType = CONTROL_TYPE_BALANCE;
 462                 portControl->channel = CHANNELS_STEREO;
 463                 /* $$mp: The value for precision is chosen more or less arbitrarily. */
 464                 control = (creator->newFloatControl)(creator, portControl, CONTROL_TYPE_BALANCE, -1.0F, 1.0F, 0.01F, "");
 465                 if (control != NULL) {
 466                     controls[numControls++] = control;
 467                 }
 468             }
 469         }
 470     }
 471     if (snd_mixer_selem_has_playback_switch(elem) || snd_mixer_selem_has_capture_switch(elem)) {
 472         if (getControlSlot(portMixer, &portControl)) {
 473             type = isPlayback ? CONTROL_TYPE_MUTE : CONTROL_TYPE_SELECT;
 474             portControl->elem = elem;
 475             portControl->portType = portMixer->types[portIndex];
 476             portControl->controlType = type;
 477             control = (creator->newBooleanControl)(creator, portControl, type);
 478             if (control != NULL) {
 479                 controls[numControls++] = control;
 480             }
 481         }
 482     }
 483     /* $$mp 2003-09-14: The following cast shouln't be necessary. Instead, the
 484        declaration of PORT_NewCompoundControlPtr in Ports.h should be changed
 485        to take a const char* parameter. */
 486     portName = (char*) snd_mixer_selem_get_name(elem);
 487     control = (creator->newCompoundControl)(creator, portName, controls, numControls);
 488     if (control != NULL) {
 489         (creator->addControl)(creator, control);
 490     }
 491     TRACE0("< PORT_GetControls\n");
 492 }
 493 
 494 
 495 INT32 PORT_GetIntValue(void* controlIDV) {
 496     PortControl* portControl = (PortControl*) controlIDV;
 497     int value = 0;
 498     snd_mixer_selem_channel_id_t channel;
 499 
 500     if (portControl != NULL) {
 501         switch (portControl->channel) {
 502         case CHANNELS_MONO:
 503             channel = SND_MIXER_SCHN_MONO;
 504             break;
 505 
 506         case CHANNELS_STEREO:
 507             channel = SND_MIXER_SCHN_FRONT_LEFT;
 508             break;
 509 
 510         default:
 511             channel = portControl->channel;
 512         }
 513         if (portControl->controlType == CONTROL_TYPE_MUTE ||
 514             portControl->controlType == CONTROL_TYPE_SELECT) {
 515             if (isPlaybackFunction(portControl->portType)) {
 516                 snd_mixer_selem_get_playback_switch(portControl->elem, channel, &value);
 517             } else {
 518                 snd_mixer_selem_get_capture_switch(portControl->elem, channel, &value);
 519             }
 520             if (portControl->controlType == CONTROL_TYPE_MUTE) {
 521                 value = ! value;
 522             }
 523         } else {
 524             ERROR1("PORT_GetIntValue(): inappropriate control type: %s\n",
 525                    portControl->controlType);
 526         }
 527     }
 528     return (INT32) value;
 529 }
 530 
 531 
 532 void PORT_SetIntValue(void* controlIDV, INT32 value) {
 533     PortControl* portControl = (PortControl*) controlIDV;
 534     snd_mixer_selem_channel_id_t channel;
 535 
 536     if (portControl != NULL) {
 537         if (portControl->controlType == CONTROL_TYPE_MUTE) {
 538             value = ! value;
 539         }
 540         if (portControl->controlType == CONTROL_TYPE_MUTE ||
 541             portControl->controlType == CONTROL_TYPE_SELECT) {
 542             if (isPlaybackFunction(portControl->portType)) {
 543                 snd_mixer_selem_set_playback_switch_all(portControl->elem, value);
 544             } else {
 545                 snd_mixer_selem_set_capture_switch_all(portControl->elem, value);
 546             }
 547         } else {
 548             ERROR1("PORT_SetIntValue(): inappropriate control type: %s\n",
 549                    portControl->controlType);
 550         }
 551     }
 552 }
 553 
 554 
 555 static float scaleVolumeValueToNormalized(long value, long min, long max) {
 556     return (float) (value - min) / getRange(min, max);
 557 }
 558 
 559 
 560 static long scaleVolumeValueToHardware(float value, long min, long max) {
 561     return (long)(value * getRange(min, max) + min);
 562 }
 563 
 564 
 565 float getRealVolume(PortControl* portControl,
 566                     snd_mixer_selem_channel_id_t channel) {
 567     float fValue;
 568     long lValue = 0;
 569     long min = 0;
 570     long max = 0;
 571 
 572     if (isPlaybackFunction(portControl->portType)) {
 573         snd_mixer_selem_get_playback_volume_range(portControl->elem,
 574                                                   &min, &max);
 575         snd_mixer_selem_get_playback_volume(portControl->elem,
 576                                             channel, &lValue);
 577     } else {
 578         snd_mixer_selem_get_capture_volume_range(portControl->elem,
 579                                                  &min, &max);
 580         snd_mixer_selem_get_capture_volume(portControl->elem,
 581                                            channel, &lValue);
 582     }
 583     fValue = scaleVolumeValueToNormalized(lValue, min, max);
 584     return fValue;
 585 }
 586 
 587 
 588 void setRealVolume(PortControl* portControl,
 589                    snd_mixer_selem_channel_id_t channel, float value) {
 590     long lValue = 0;
 591     long min = 0;
 592     long max = 0;
 593 
 594     if (isPlaybackFunction(portControl->portType)) {
 595         snd_mixer_selem_get_playback_volume_range(portControl->elem,
 596                                                   &min, &max);
 597         lValue = scaleVolumeValueToHardware(value, min, max);
 598         snd_mixer_selem_set_playback_volume(portControl->elem,
 599                                             channel, lValue);
 600     } else {
 601         snd_mixer_selem_get_capture_volume_range(portControl->elem,
 602                                                  &min, &max);
 603         lValue = scaleVolumeValueToHardware(value, min, max);
 604         snd_mixer_selem_set_capture_volume(portControl->elem,
 605                                            channel, lValue);
 606     }
 607 }
 608 
 609 
 610 static float getFakeBalance(PortControl* portControl) {
 611     float volL, volR;
 612 
 613     // pan is the ratio of left and right
 614     volL = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_LEFT);
 615     volR = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_RIGHT);
 616     if (volL > volR) {
 617         return -1.0f + (volR / volL);
 618     }
 619     else if (volR > volL) {
 620         return 1.0f - (volL / volR);
 621     }
 622     return 0.0f;
 623 }
 624 
 625 
 626 static float getFakeVolume(PortControl* portControl) {
 627     float valueL;
 628     float valueR;
 629     float value;
 630 
 631     valueL = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_LEFT);
 632     valueR = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_RIGHT);
 633     // volume is the greater value of both
 634     value = valueL > valueR ? valueL : valueR ;
 635     return value;
 636 }
 637 
 638 
 639 /*
 640  * sets the unsigned values for left and right volume according to
 641  * the given volume (0...1) and balance (-1..0..+1)
 642  */
 643 static void setFakeVolume(PortControl* portControl, float vol, float bal) {
 644     float volumeLeft;
 645     float volumeRight;
 646 
 647     if (bal < 0.0f) {
 648         volumeLeft = vol;
 649         volumeRight = vol * (bal + 1.0f);
 650     } else {
 651         volumeLeft = vol * (1.0f - bal);
 652         volumeRight = vol;
 653     }
 654     setRealVolume(portControl, SND_MIXER_SCHN_FRONT_LEFT, volumeLeft);
 655     setRealVolume(portControl, SND_MIXER_SCHN_FRONT_RIGHT, volumeRight);
 656 }
 657 
 658 
 659 float PORT_GetFloatValue(void* controlIDV) {
 660     PortControl* portControl = (PortControl*) controlIDV;
 661     float value = 0.0F;
 662 
 663     if (portControl != NULL) {
 664         if (portControl->controlType == CONTROL_TYPE_VOLUME) {
 665             switch (portControl->channel) {
 666             case CHANNELS_MONO:
 667                 value = getRealVolume(portControl, SND_MIXER_SCHN_MONO);
 668                 break;
 669 
 670             case CHANNELS_STEREO:
 671                 value = getFakeVolume(portControl);
 672                 break;
 673 
 674             default:
 675                 value = getRealVolume(portControl, portControl->channel);
 676             }
 677         } else if (portControl->controlType == CONTROL_TYPE_BALANCE) {
 678             if (portControl->channel == CHANNELS_STEREO) {
 679                 value = getFakeBalance(portControl);
 680             } else {
 681                 ERROR0("PORT_GetFloatValue(): Balance only allowed for stereo channels!\n");
 682             }
 683         } else {
 684             ERROR1("PORT_GetFloatValue(): inappropriate control type: %s!\n",
 685                    portControl->controlType);
 686         }
 687     }
 688     return value;
 689 }
 690 
 691 
 692 void PORT_SetFloatValue(void* controlIDV, float value) {
 693     PortControl* portControl = (PortControl*) controlIDV;
 694 
 695     if (portControl != NULL) {
 696         if (portControl->controlType == CONTROL_TYPE_VOLUME) {
 697             switch (portControl->channel) {
 698             case CHANNELS_MONO:
 699                 setRealVolume(portControl, SND_MIXER_SCHN_MONO, value);
 700                 break;
 701 
 702             case CHANNELS_STEREO:
 703                 setFakeVolume(portControl, value, getFakeBalance(portControl));
 704                 break;
 705 
 706             default:
 707                 setRealVolume(portControl, portControl->channel, value);
 708             }
 709         } else if (portControl->controlType == CONTROL_TYPE_BALANCE) {
 710             if (portControl->channel == CHANNELS_STEREO) {
 711                 setFakeVolume(portControl, getFakeVolume(portControl), value);
 712             } else {
 713                 ERROR0("PORT_SetFloatValue(): Balance only allowed for stereo channels!\n");
 714             }
 715         } else {
 716             ERROR1("PORT_SetFloatValue(): inappropriate control type: %s!\n",
 717                    portControl->controlType);
 718         }
 719     }
 720 }
 721 
 722 
 723 #endif // USE_PORTS