/* * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ //#define USE_ERROR //#define USE_TRACE #include #include #include "PLATFORM_API_MacOSX_Utils.h" extern "C" { #include "Ports.h" } #if USE_PORTS == TRUE /* If a device has the only AudioStream in the scope (input or output), * PortMixer provides a single Port, using the stream kAudioStreamPropertyTerminalType * property value to determine Port.Type (PORT_GetPortType function). * If the device has several (more than 1) AudioStreams, there are 2 ways to represent Ports: * 1. (HALLab-style) single Port which represents all device channels with * "master volume" and (if number of channel is 2) "master balance"; if AudioDevice * does not provide "master" controls, implement "virtual master" controls. * Port.Type is PORT_SRC_UNKNOWN or PORT_DST_UNKNOWN. * 2. provide a separate Port for every AudioStream (with appropriate Port.Type); * * AudioHardware.h claims that AudioStream objects share AudioControl objects with their owning AudioDevice. * In practice 10.7 OSX drivers (built-in devices, USB audio) implement AudioControl only for AudioDevice. * For now 1st way is implemented (2nd way can be better if AudioStreams provide AudioControls). */ static DeviceList deviceCache; #define FourCC2Str(n) ((char[5]){(char)(n >> 24), (char)(n >> 16), (char)(n >> 8), (char)(n), 0}) // CoreAudio's AudioControl struct AudioControl { AudioObjectID controlID; AudioClassID classID; // kAudioVolumeControlClassID etc. AudioObjectPropertyScope scope; // input, output AudioObjectPropertyElement channel; // master = 0, channels = 1 2 ... }; // Controls for Java // PortMixer do all memory management (alloc/free audioControls) struct PortControl { enum ControlType { Volume, // manages single or multiple volume AudioControl Mute, // manages single or multiple mute AudioControls Balance // "virtual" control, manages 2 volume AudioControls (only for stereo lines) }; ControlType type; int controlCount; AudioControl **audioControls; PortControl *next; // to organize PortControl list }; // represents line (port) for PortMixer // used for PORT_GetPortCount/PORT_GetPortType/PORT_GetPortName functions struct PortLine { AudioObjectPropertyScope scope; // if the device has several AudioStreams in the scope, streamID == 0 AudioStreamID streamID; }; struct PortMixer { AudioDeviceID deviceID; int portCount; PortLine ports[2]; // maximum 2 lines - 1 for input & 1 for output int deviceControlCount; // -1 means "not initialized" AudioControl *deviceControls; PortControl *portControls; // list of port controls bool listenersInstalled; }; void RemoveChangeListeners(PortMixer *mixer); // forward declaration OSStatus ChangeListenerProc(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData) { PortMixer *mixer = (PortMixer *)inClientData; OSStatus err = noErr; UInt32 size; bool invalid = false; for (UInt32 i = 0; i < inNumberAddresses; i++) { switch (inAddresses[i].mSelector) { case kAudioHardwarePropertyDevices: // check if the device has been removed err = GetAudioObjectPropertySize(kAudioObjectSystemObject, kAudioObjectPropertyScopeGlobal, kAudioHardwarePropertyDevices, &size); if (err == noErr) { int count = size/sizeof(AudioDeviceID); AudioDeviceID devices[count]; err = GetAudioObjectProperty(kAudioObjectSystemObject, kAudioObjectPropertyScopeGlobal, kAudioHardwarePropertyDevices, count*sizeof(AudioDeviceID), devices, 1); if (err == noErr) { bool found = false; for (int j = 0; j < count; j++) { if (devices[j] == mixer->deviceID) { found = true; break; } } if (!found) { invalid = true; } } } break; case kAudioObjectPropertyOwnedObjects: case kAudioDevicePropertyDeviceHasChanged: // ensure all _used_ AudioControl are valid err = GetAudioObjectPropertySize(mixer->deviceID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyOwnedObjects, &size); if (err == noErr) { int count = size / sizeof(AudioObjectID); AudioObjectID controlIDs[count]; err = GetAudioObjectProperty(mixer->deviceID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyOwnedObjects, count * sizeof(AudioObjectID), &controlIDs, 1); if (err == noErr) { for (PortControl *ctrl = mixer->portControls; ctrl != NULL; ctrl = ctrl->next) { for (int i = 0; i < ctrl->controlCount; i++) { bool found = false; for (int j = 0; j < count; j++) { if (ctrl->audioControls[i]->controlID == controlIDs[j]) { found = true; break; } } if (!found) { invalid = true; break; // goto next control } } } } } } } if (invalid) { TRACE1("PortMixer (deviceID=0x%x) becomes invalid", (int)mixer->deviceID); // invalidate all controls for (int i=0; ideviceControlCount; i++) { mixer->deviceControls[i].controlID = 0; } RemoveChangeListeners(mixer); } return noErr; } const AudioObjectPropertyAddress changeListenersAddresses[] = { {kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}, {kAudioObjectPropertyOwnedObjects, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}, {kAudioDevicePropertyDeviceHasChanged, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster} }; void AddChangeListeners(PortMixer *mixer) { if (!mixer->listenersInstalled) { for (size_t i=0; ideviceID, &changeListenersAddresses[i], ChangeListenerProc, mixer); } mixer->listenersInstalled = true; } } void RemoveChangeListeners(PortMixer *mixer) { if (mixer->listenersInstalled) { for (size_t i=0; ideviceID, &changeListenersAddresses[i], ChangeListenerProc, mixer); } mixer->listenersInstalled = false; } } //////////////////////////////////////////////////////////////////////////////// // functions from Port.h INT32 PORT_GetPortMixerCount() { deviceCache.Refresh(); int count = deviceCache.GetCount(); TRACE1("<name, mixerDescription->vendor, mixerDescription->description, mixerDescription->version); return result ? TRUE : FALSE; } void* PORT_Open(INT32 mixerIndex) { TRACE1("\n>>PORT_Open (mixerIndex=%d)\n", (int)mixerIndex); PortMixer *mixer = (PortMixer *)calloc(1, sizeof(PortMixer)); mixer->deviceID = deviceCache.GetDeviceID(mixerIndex); if (mixer->deviceID != 0) { mixer->deviceControlCount = -1; // not initialized // fill mixer->ports (and mixer->portCount) for (int i=0; i<2; i++) { OSStatus err; UInt32 size = 0; AudioObjectPropertyScope scope = (i == 0) ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; err = GetAudioObjectPropertySize(mixer->deviceID, scope, kAudioDevicePropertyStreams, &size); if (err || size == 0) { continue; } if (size / sizeof(AudioStreamID) == 1) { // the device has the only AudioStream AudioStreamID streamID; err = GetAudioObjectProperty(mixer->deviceID, scope, kAudioDevicePropertyStreams, sizeof(streamID), &streamID, 1); if (err) { continue; } mixer->ports[mixer->portCount].streamID = streamID; } else { // the device has several AudioStreams in the scope mixer->ports[mixer->portCount].streamID = 0; } mixer->ports[mixer->portCount].scope = scope; mixer->portCount++; } } TRACE2("<>PORT_Close %p\n", id); PortMixer *mixer = (PortMixer *)id; if (mixer) { RemoveChangeListeners(mixer); while (mixer->portControls != NULL) { PortControl *control2delete = mixer->portControls; mixer->portControls = control2delete->next; if (control2delete->audioControls != NULL) { free(control2delete->audioControls); } free(control2delete); } if (mixer->deviceControls) { free(mixer->deviceControls); } free(mixer); } TRACE1("<portCount; TRACE1("<= mixer->portCount) { ERROR1("PORT_GetPortType: line (portIndex = %d) not found\n", portIndex); return 0; } AudioObjectPropertyScope scope = mixer->ports[portIndex].scope; AudioStreamID streamID = mixer->ports[portIndex].streamID; if (streamID != 0) { UInt32 terminalType; OSStatus err = GetAudioObjectProperty(streamID, kAudioObjectPropertyScopeGlobal, kAudioStreamPropertyTerminalType, sizeof(terminalType), &terminalType, 1); if (err) { OS_ERROR1(err, "PORT_GetPortType(kAudioStreamPropertyTerminalType), portIndex=%d", portIndex); return 0; } // Note that kAudioStreamPropertyTerminalType actually returns values from // IOAudioTypes.h, not the defined kAudioStreamTerminalType*. TRACE4("PORT_GetPortType (portIndex=%d), scope=%s, termType=0x%04x (%s)\n", (int)portIndex, FourCC2Str(scope), (int)terminalType, FourCC2Str(terminalType)); switch (terminalType) { case INPUT_MICROPHONE: ret = PORT_SRC_MICROPHONE; break; case OUTPUT_SPEAKER: ret = PORT_DST_SPEAKER; break; case OUTPUT_HEADPHONES: ret = PORT_DST_HEADPHONE; break; case EXTERNAL_LINE_CONNECTOR: ret = scope == kAudioDevicePropertyScopeInput ? PORT_SRC_LINE_IN : PORT_DST_LINE_OUT; break; default: TRACE1(" unknown output terminal type %#x\n", terminalType); } } else { TRACE0(" PORT_GetPortType: multiple streams\n"); } if (ret == 0) { // if the type not detected, return "common type" ret = scope == kAudioDevicePropertyScopeInput ? PORT_SRC_UNKNOWN : PORT_DST_UNKNOWN; } TRACE2("<= mixer->portCount) { ERROR1("PORT_GetPortName: line (portIndex = %d) not found\n", portIndex); return FALSE; } AudioStreamID streamID = mixer->ports[portIndex].streamID; CFStringRef cfname = NULL; if (streamID != 0) { OSStatus err = GetAudioObjectProperty(streamID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyName, sizeof(cfname), &cfname, 1); if (err && err != kAudioHardwareUnknownPropertyError) { OS_ERROR1(err, "PORT_GetPortName(stream name), portIndex=%d", portIndex); return FALSE; } } if (!cfname) { // use the device's name if the stream has no name (usually the case) // or the device has several AudioStreams OSStatus err = GetAudioObjectProperty(mixer->deviceID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyName, sizeof(cfname), &cfname, 1); if (err) { OS_ERROR1(err, "PORT_GetPortName(device name), portIndex=%d", portIndex); return FALSE; } } if (cfname) { CFStringGetCString(cfname, name, len, kCFStringEncodingUTF8); CFRelease(cfname); } TRACE2("<type = type; control->controlCount = len; control->audioControls = (AudioControl **)malloc(len * sizeof(AudioControl *)); memcpy(control->audioControls, audioControls + offset, len * sizeof(AudioControl *)); switch (control->type) { case PortControl::Volume: jControl = creator->newFloatControl(creator, control, CONTROL_TYPE_VOLUME, 0, 1, precision, ""); break; case PortControl::Mute: jControl = creator->newBooleanControl(creator, control, CONTROL_TYPE_MUTE); break; case PortControl::Balance: jControl = creator->newFloatControl(creator, control, CONTROL_TYPE_BALANCE, -1, 1, precision, ""); break; }; if (jControl == NULL) { ERROR0("CreatePortControl: javaControl was not created\n"); free(control->audioControls); free(control); return NULL; } // add the control to mixer control list; control->next = mixer->portControls; mixer->portControls = control; return jControl; } void PORT_GetControls(void* id, INT32 portIndex, PortControlCreator* creator) { PortMixer *mixer = (PortMixer *)id; TRACE1(">>PORT_GetControls (portIndex = %d)\n", portIndex); if (portIndex < 0 || portIndex >= mixer->portCount) { ERROR1("<ports[portIndex]); if (mixer->deviceControlCount < 0) { // not initialized OSStatus err; UInt32 size; // deviceControlCount is overestimated // because we don't actually filter by if the owned objects are controls err = GetAudioObjectPropertySize(mixer->deviceID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyOwnedObjects, &size); if (err) { OS_ERROR1(err, "PORT_GetControls (portIndex = %d) get OwnedObject size", portIndex); } else { mixer->deviceControlCount = size / sizeof(AudioObjectID); TRACE1(" PORT_GetControls: detected %d owned objects\n", mixer->deviceControlCount); AudioObjectID controlIDs[mixer->deviceControlCount]; err = GetAudioObjectProperty(mixer->deviceID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyOwnedObjects, sizeof(controlIDs), controlIDs, 1); if (err) { OS_ERROR1(err, "PORT_GetControls (portIndex = %d) get OwnedObject values", portIndex); } else { mixer->deviceControls = (AudioControl *)calloc(mixer->deviceControlCount, sizeof(AudioControl)); for (int i = 0; i < mixer->deviceControlCount; i++) { AudioControl *control = &mixer->deviceControls[i]; control->controlID = controlIDs[i]; OSStatus err1 = GetAudioObjectProperty(control->controlID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyClass, sizeof(control->classID), &control->classID, 1); OSStatus err2 = GetAudioObjectProperty(control->controlID, kAudioObjectPropertyScopeGlobal, kAudioControlPropertyScope, sizeof(control->scope), &control->scope, 1); OSStatus err3 = GetAudioObjectProperty(control->controlID, kAudioObjectPropertyScopeGlobal, kAudioControlPropertyElement, sizeof(control->channel), &control->channel, 1); if (err1 || err2 || err3) { // not a control or other error control->classID = 0; continue; } TRACE4("- control 0x%x, class='%s', scope='%s', channel=%d\n", control->controlID, FourCC2Str(control->classID), FourCC2Str(control->scope), control->channel); } } } } if (mixer->deviceControlCount <= 0) { TRACE1("<deviceID, port->scope == kAudioDevicePropertyScopeOutput ? 1 : 0); // collect volume and mute controls AudioControl* volumeControls[totalChannels+1]; // 0 - for master channel memset(&volumeControls, 0, sizeof(AudioControl *) * (totalChannels+1)); AudioControl* muteControls[totalChannels+1]; // 0 - for master channel memset(&muteControls, 0, sizeof(AudioControl *) * (totalChannels+1)); for (int i=0; ideviceControlCount; i++) { AudioControl *control = &mixer->deviceControls[i]; if (control->classID == 0 || control->scope != port->scope || control->channel > (unsigned)totalChannels) { continue; } if (control->classID == kAudioVolumeControlClassID) { if (volumeControls[control->channel] == NULL) { volumeControls[control->channel] = control; } else { ERROR4("WARNING: duplicate VOLUME control 0x%x, class='%s', scope='%s', channel=%d\n", control->controlID, FourCC2Str(control->classID), FourCC2Str(control->scope), control->channel); } } else if (control->classID == kAudioMuteControlClassID) { if (muteControls[control->channel] == NULL) { muteControls[control->channel] = control; } else { ERROR4("WARNING: duplicate MUTE control 0x%x, class='%s', scope='%s', channel=%d\n", control->controlID, FourCC2Str(control->classID), FourCC2Str(control->scope), control->channel); } } else { #ifdef USE_ERROR if (control->classID != 0) { ERROR4("WARNING: unhandled control 0x%x, class='%s', scope='%s', channel=%d\n", control->controlID, FourCC2Str(control->classID), FourCC2Str(control->scope), control->channel); } #endif } } //////////////////////////////////////////////////////// // create java control hierarchy void *masterVolume = NULL, *masterMute = NULL, *masterBalance = NULL; // volumeControls[0] and muteControls[0] - master volume/mute // volumeControls[n] and muteControls[n] (n=1..totalChannels) - corresponding channel controls if (volumeControls[0] != NULL) { // "master volume" AudioControl masterVolume = CreatePortControl(mixer, creator, PortControl::Volume, volumeControls, 0, 1); } else { if (ValidControlCount(volumeControls, 1, totalChannels) == totalChannels) { // every channel has volume control => create virtual master volume masterVolume = CreatePortControl(mixer, creator, PortControl::Volume, volumeControls, 1, totalChannels); } else { TRACE2(" PORT_GetControls (master volume): totalChannels = %d, valid volume controls = %d\n", totalChannels, ValidControlCount(volumeControls, 1, totalChannels)); } } if (muteControls[0] != NULL) { // "master mute" masterMute = CreatePortControl(mixer, creator, PortControl::Mute, muteControls, 0, 1); } else { if (ValidControlCount(muteControls, 1, totalChannels) == totalChannels) { // every channel has mute control => create virtual master mute control masterMute = CreatePortControl(mixer, creator, PortControl::Mute, muteControls, 1, totalChannels); } else { TRACE2(" PORT_GetControls (master mute): totalChannels = %d, valid volume controls = %d\n", totalChannels, ValidControlCount(muteControls, 1, totalChannels)); } } // virtual balance if (totalChannels == 2) { if (ValidControlCount(volumeControls, 1, totalChannels) == totalChannels) { masterBalance = CreatePortControl(mixer, creator, PortControl::Balance, volumeControls, 1, totalChannels); } else { TRACE2(" PORT_GetControls (naster balance): totalChannels = %d, valid volume controls = %d\n", totalChannels, ValidControlCount(volumeControls, 1, totalChannels)); } } // add "master" controls if (masterVolume != NULL) { creator->addControl(creator, masterVolume); } if (masterBalance != NULL) { creator->addControl(creator, masterBalance); } if (masterMute != NULL) { creator->addControl(creator, masterMute); } // don't add per-channel controls for mono & stereo - they are handled by "master" controls // TODO: this should be reviewed to handle controls other than mute & volume if (totalChannels > 2) { // add separate compound control for each channel (containing volume and mute) // (ensure that we have controls) if (ValidControlCount(volumeControls, 1, totalChannels) > 0 || ValidControlCount(muteControls, 1, totalChannels) > 0) { for (int ch=1; ch<=totalChannels; ch++) { // get the channel name char *channelName; CFStringRef cfname = NULL; const AudioObjectPropertyAddress address = {kAudioObjectPropertyElementName, port->scope, (unsigned)ch}; UInt32 size = sizeof(cfname); OSStatus err = AudioObjectGetPropertyData(mixer->deviceID, &address, 0, NULL, &size, &cfname); if (err == noErr) { CFIndex length = CFStringGetLength(cfname) + 1; channelName = (char *)malloc(length); CFStringGetCString(cfname, channelName, length, kCFStringEncodingUTF8); CFRelease(cfname); } else { channelName = (char *)malloc(16); sprintf(channelName, "Ch %d", ch); } void* jControls[2]; int controlCount = 0; if (volumeControls[ch] != NULL) { jControls[controlCount++] = CreatePortControl(mixer, creator, PortControl::Volume, volumeControls, ch, 1); } if (muteControls[ch] != NULL) { jControls[controlCount++] = CreatePortControl(mixer, creator, PortControl::Mute, muteControls, ch, 1); } // TODO: add any extra controls for "other" controls for the channel void *compoundControl = creator->newCompoundControl(creator, channelName, jControls, controlCount); creator->addControl(creator, compoundControl); free(channelName); } } } AddChangeListeners(mixer); TRACE1("<controlCount; i++) { if (control->audioControls[i]->controlID == 0) return false; } return true; } #define DEFAULT_MUTE_VALUE 0 INT32 PORT_GetIntValue(void* controlIDV) { PortControl *control = (PortControl *)controlIDV; INT32 result = 0; switch (control->type) { case PortControl::Mute: if (!TestPortControlValidity(control)) { return DEFAULT_MUTE_VALUE; } result = 1; // default is "muted", if some channel in unmuted, then "virtual mute" is also unmuted for (int i=0; icontrolCount; i++) { UInt32 value; OSStatus err = GetAudioObjectProperty(control->audioControls[i]->controlID, kAudioObjectPropertyScopeGlobal, kAudioBooleanControlPropertyValue, sizeof(value), &value, 1); if (err) { OS_ERROR3(err, "PORT_GetIntValue, control %d of %d (coltrolID = 0x%x)", i, control->controlCount, control->audioControls[i]->controlID); return DEFAULT_MUTE_VALUE; } if (value == 0) { result = 0; } } break; default: ERROR1("PORT_GetIntValue requested for non-Int control (control-type == %d)\n", control->type); return 0; } //TRACE1("< PORT_SetIntValue = %d\n", value); PortControl *control = (PortControl *)controlIDV; if (!TestPortControlValidity(control)) { return; } switch (control->type) { case PortControl::Mute: for (int i=0; icontrolCount; i++) { OSStatus err = SetAudioObjectProperty(control->audioControls[i]->controlID, kAudioObjectPropertyScopeGlobal, kAudioBooleanControlPropertyValue, sizeof(value), &value); if (err) { OS_ERROR3(err, "PORT_SetIntValue, control %d of %d (coltrolID = 0x%x)", i, control->controlCount, control->audioControls[i]->controlID); // don't return - try to set the rest of AudioControls } } break; default: ERROR1("PORT_SetIntValue requested for non-Int control (control-type == %d)\n", control->type); return; } } // gets volume value for all AudioControls of the PortControl static bool GetPortControlVolumes(PortControl *control, Float32 *volumes, Float32 *maxVolume) { *maxVolume = 0.0f; for (int i=0; icontrolCount; i++) { OSStatus err = GetAudioObjectProperty(control->audioControls[i]->controlID, kAudioObjectPropertyScopeGlobal, kAudioLevelControlPropertyScalarValue, sizeof(volumes[i]), &volumes[i], 1); if (err) { OS_ERROR3(err, "GetPortControlVolumes, control %d of %d (controlID = 0x%x)", i, control->controlCount, control->audioControls[i]->controlID); return false; } if (volumes[i] > *maxVolume) { *maxVolume = volumes[i]; } } return true; } // sets volume value for all AudioControls of the PortControl static void SetPortControlVolumes(PortControl *control, Float32 *volumes) { for (int i=0; icontrolCount; i++) { OSStatus err = SetAudioObjectProperty(control->audioControls[i]->controlID, kAudioObjectPropertyScopeGlobal, kAudioLevelControlPropertyScalarValue, sizeof(volumes[i]), &volumes[i]); if (err) { OS_ERROR3(err, "SetPortControlVolumes , control %d of %d (coltrolID = 0x%x)", i, control->controlCount, control->audioControls[i]->controlID); // don't return - try to set the rest of AudioControls } } } #define DEFAULT_VOLUME_VALUE 1.0f #define DEFAULT_BALANCE_VALUE 0.0f float PORT_GetFloatValue(void* controlIDV) { PortControl *control = (PortControl *)controlIDV; Float32 result = 0; Float32 subVolumes[control->controlCount]; Float32 maxVolume; switch (control->type) { case PortControl::Volume: if (!TestPortControlValidity(control)) { return DEFAULT_VOLUME_VALUE; } if (!GetPortControlVolumes(control, subVolumes, &maxVolume)) { return DEFAULT_VOLUME_VALUE; } result = maxVolume; break; case PortControl::Balance: if (!TestPortControlValidity(control)) { return DEFAULT_BALANCE_VALUE; } // balance control always has 2 volume controls if (!GetPortControlVolumes(control, subVolumes, &maxVolume)) { return DEFAULT_VOLUME_VALUE; } // calculate balance value if (subVolumes[0] > subVolumes[1]) { result = -1.0f + (subVolumes[1] / subVolumes[0]); } else if (subVolumes[1] > subVolumes[0]) { result = 1.0f - (subVolumes[0] / subVolumes[1]); } else { result = 0.0f; } break; default: ERROR1("GetFloatValue requested for non-Float control (control-type == %d)\n", control->type); return 0; } TRACE1("< PORT_SetFloatValue = %f\n", value); PortControl *control = (PortControl *)controlIDV; if (!TestPortControlValidity(control)) { return; } Float32 subVolumes[control->controlCount]; Float32 maxVolume; switch (control->type) { case PortControl::Volume: if (!GetPortControlVolumes(control, subVolumes, &maxVolume)) { return; } // update the values if (maxVolume > 0.001) { float multiplicator = value/maxVolume; for (int i=0; icontrolCount; i++) subVolumes[i] *= multiplicator; } else { // volume for all channels == 0, so set all channels to "value" for (int i=0; icontrolCount; i++) subVolumes[i] = value; } // set new values SetPortControlVolumes(control, subVolumes); break; case PortControl::Balance: // balance control always has 2 volume controls if (!GetPortControlVolumes(control, subVolumes, &maxVolume)) { return; } // calculate new values if (value < 0.0f) { subVolumes[0] = maxVolume; subVolumes[1] = maxVolume * (value + 1.0f); } else { // this case also handles value == 0 subVolumes[0] = maxVolume * (1.0f - value); subVolumes[1] = maxVolume; } // set new values SetPortControlVolumes(control, subVolumes); break; default: ERROR1("PORT_SetFloatValue requested for non-Float control (control-type == %d)\n", control->type); return; } } #endif // USE_PORTS