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