Beispiel #1
0
bool Font::GenerateEmptyGlyph()
{
    unsigned int key = 0;
    GlyphBitmap glyphBitmap;
    glyphBitmap.bitmap = new Bitmap();
    glyphBitmap.bitmap->Generate(Bitmap::FORMAT_RGBA, 6, 8, 0x00000000);
    unsigned int channelCount = GetChannelCount(Bitmap::FORMAT_RGBA);

    byte *glyphImageData = glyphBitmap.bitmap->GetData();

    unsigned int w = glyphBitmap.bitmap->GetWidth();
    unsigned int h = glyphBitmap.bitmap->GetHeight();

    blankX = w;
    blankY = h;

    for(unsigned int i = 0; i < w; i++)
    {
        glyphImageData[i * channelCount + channelCount - 1] = 255;
        glyphImageData[(h - 1) * w * channelCount + i * channelCount + channelCount - 1] = 255;
    }
    for(unsigned int j = 0; j < h; j++)
    {
        glyphImageData[j * w * channelCount + channelCount - 1] = 255;
        glyphImageData[j * w * channelCount + (w - 1) * channelCount + channelCount - 1] = 255;
    }
    glyphBitmap.offsetDown = 0;

    glyphBitmap.key = key;
    glyphsBitmapList.push_back(glyphBitmap);

    return true;
}
Beispiel #2
0
void CSoundMgr::CleanUp (void)

//	CleanUp
//
//	Clean up sound manager

	{
	int i;

	if (m_pDS)
		{
		//	Stop and delete all buffers

		for (i = 0; i < GetChannelCount(); i++)
			CleanUpChannel(m_Channels[i]);

		m_Channels.DeleteAll();

		//	Kill the DirectSound object

		m_pDS->Release();
		m_pDS = NULL;
		}

	if (m_hMusic)
		{
		::DestroyWindow(m_hMusic);
		m_hMusic = NULL;
		}
	}
void AVFAudioEqualizer::ResetBandParameters() {
    // Update channel counts, recalculate params if necessary
    // bands are automatically sorted by the map from low to high
    for (AVFEQBandIterator iter = mEQBands.begin(); iter != mEQBands.end();) {
        if (!iter->second) {
            // NULL pointer protection, just remove the offending band
            mEQBands.erase(iter++);
            if (!mEQBands.empty() && (iter == mEQBands.end())) {
                // re-process the last valid band, otherwise it won't be set to
                // HighShelf filter type
                --iter;
            } else {
                continue;
            }
        }
        AVFEqualizerBand *band = iter->second;
        // middle bands are peak/notch filters
        AVFEqualizerBand::AVFEqualizerFilterType type = AVFEqualizerBand::Peak;

        if (iter == mEQBands.begin()) {
            type = AVFEqualizerBand::LowShelf;
        } else if (iter == --(mEQBands.end())) {
            type = AVFEqualizerBand::HighShelf;
        }

        band->SetFilterType(type);
        band->SetChannelCount(GetChannelCount());
        band->RecalculateParams();
        iter++; // here due to NULL ptr protection, otherwise we double increment
    }
}
Beispiel #4
0
void MMDeviceAudioSource::ReleaseBuffer()
{
    UINT sampleSizeFloats = sampleWindowSize*GetChannelCount();
    if (inputBufferSize > sampleSizeFloats)
        mcpy(inputBuffer.Array(), inputBuffer.Array()+sampleSizeFloats, (inputBufferSize-sampleSizeFloats)*sizeof(float));

    inputBufferSize -= sampleSizeFloats;
}
Beispiel #5
0
/*----------------------------------------------------------------------
|   AP4_AudioSampleEntry::ToSampleDescription
+---------------------------------------------------------------------*/
AP4_SampleDescription*
AP4_AudioSampleEntry::ToSampleDescription()
{
    // create a sample description
    return new AP4_GenericAudioSampleDescription(
        m_Type,
        GetSampleRate(),
        GetSampleSize(),
        GetChannelCount(),
        this);
}
Beispiel #6
0
/*----------------------------------------------------------------------
|   AP4_AudioSampleEntry::InspectFields
+---------------------------------------------------------------------*/
AP4_Result
AP4_AudioSampleEntry::InspectFields(AP4_AtomInspector& inspector)
{
    // dump the fields from the base class
    AP4_SampleEntry::InspectFields(inspector);

    // fields
    inspector.AddField("channel_count", GetChannelCount());
    inspector.AddField("sample_size", GetSampleSize());
    inspector.AddField("sample_rate", GetSampleRate());
    if (m_QtVersion) {
        inspector.AddField("qt_version", m_QtVersion);
    }
    
    return AP4_SUCCESS;
}
Beispiel #7
0
/*----------------------------------------------------------------------
|   AP4_MpegAudioSampleEntry::ToSampleDescription
+---------------------------------------------------------------------*/
AP4_SampleDescription*
AP4_MpegAudioSampleEntry::ToSampleDescription()
{
    // find the esds atom
    AP4_EsdsAtom* esds = AP4_DYNAMIC_CAST(AP4_EsdsAtom, GetChild(AP4_ATOM_TYPE_ESDS));
    if (esds == NULL) {
        // check if this is a quicktime style sample description
        if (m_QtVersion > 0) {
            esds = AP4_DYNAMIC_CAST(AP4_EsdsAtom, FindChild("wave/esds"));
        }
    }
    
    // create a sample description
    return new AP4_MpegAudioSampleDescription(GetSampleRate(),
                                              GetSampleSize(),
                                              GetChannelCount(),
                                              esds);
}
/*++

Routine Name:

    CColorChannelData::GetChannelCountNoAlpha

Routine Description:

    Retrieves the number of channels less the alpha channel if present

Arguments:

    pcChannels - Pointer to a variable that recieves the count of non-alpha channels

Return Value:

    HRESULT
    S_OK - On success
    E_*  - On error

--*/
HRESULT
CColorChannelData::GetChannelCountNoAlpha(
    _Out_ DWORD* pcChannels
    )
{
    HRESULT hr = S_OK;

    if (SUCCEEDED(hr = GetChannelCount(pcChannels)))
    {
        if (HasAlpha())
        {
            (*pcChannels)--;
        }
    }

    ERR_ON_HR(hr);
    return hr;
}
Beispiel #9
0
bool Font::GenerateGlyph( unsigned int glyphNumber, GlyphBitmap &glyphBitmap)
{

    if(FT_Load_Glyph( face, FT_Get_Char_Index( face, glyphNumber ), FT_LOAD_DEFAULT ))
    {

        return false;
    }

    FT_Glyph glyph;
    if(FT_Get_Glyph( face->glyph, &glyph ))
    {

        return false;
    }

    FT_Glyph_To_Bitmap( &glyph, ft_render_mode_normal, 0, 1 );
    FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;

    FT_Bitmap &bitmap=bitmap_glyph->bitmap;


    glyphBitmap.bitmap->Generate(Bitmap::FORMAT_RGBA, bitmap.width, bitmap.rows, 0x00000000);
    unsigned int channelCount = GetChannelCount(Bitmap::FORMAT_RGBA);

    byte *glyphImageData = glyphBitmap.bitmap->GetData();

    for(int j = 0; j < bitmap.rows; j++)
    {
        for(int i=0; i < bitmap.width; i++)
        {
            glyphImageData[( bitmap.width * j + i ) * channelCount + channelCount - 1]
            = bitmap.buffer[bitmap.width * ( bitmap.rows - j - 1) + i];
        }
    }

    glyphBitmap.offsetDown = bitmap_glyph->top - bitmap.rows;
    glyphBitmap.bitmap->BlackToWhite();

    return true;
}
Beispiel #10
0
void CSoundMgr::CleanUp (void)

//	CleanUp
//
//	Clean up sound manager

	{
	if (m_pDS)
		{
		//	Stop and delete all buffers

		for (int i = 0; i < GetChannelCount(); i++)
			{
			SChannel *pChannel = GetChannel(i);
			while (pChannel)
				{
				pChannel->pBuffer->Stop();
				pChannel->pBuffer->Release();

				SChannel *pDelete = pChannel;
				pChannel = pChannel->pNext;
				delete pDelete;
				}

			}

		m_Channels.RemoveAll();

		//	Kill the DirectSound object

		m_pDS->Release();
		m_pDS = NULL;
		}

	if (m_hMusic)
		::DestroyWindow(m_hMusic);
	}
Beispiel #11
0
bool MMDeviceAudioSource::GetNextBuffer(void **buffer, UINT *numFrames, QWORD *timestamp)
{
    UINT captureSize = 0;
    bool bFirstRun = true;
    HRESULT hRes;
    UINT64 devPosition, qpcTimestamp;
    LPBYTE captureBuffer;
    UINT32 numFramesRead;
    DWORD dwFlags = 0;

    while (true) {
        if (inputBufferSize >= sampleWindowSize*GetChannelCount()) {
            if (bFirstRun) {
                lastQPCTimestamp += 10;
            } else if (bIsMic && !bUseQPC) {
                captureSize = 0;
                mmCapture->GetNextPacketSize(&captureSize);

                //throws away worthless mic data that's sampling faster than the desktop buffer.
                //disgusting fix for stupid worthless mic issues.
                if (captureSize > 0) {
                    ++numTimesInARowNewDataSeen;

                    if (numTimesInARowNewDataSeen > angerThreshold) {
                        if (SUCCEEDED(mmCapture->GetBuffer(&captureBuffer, &numFramesRead, &dwFlags, &devPosition, &qpcTimestamp))) {
                            mmCapture->ReleaseBuffer(numFramesRead);
                            numTimesInARowNewDataSeen = 0;
                        }
                    }
                } else {
                    numTimesInARowNewDataSeen = 0;
                }
            }
            firstTimestamp = GetTimestamp(lastQPCTimestamp);
            break;
        }

        //---------------------------------------------------------

        hRes = mmCapture->GetNextPacketSize(&captureSize);

        if (FAILED(hRes)) {
            RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetNextPacketSize failed, result = %08lX"), hRes);
            return false;
        }

        if (!captureSize)
            return false;

        //---------------------------------------------------------

        hRes = mmCapture->GetBuffer(&captureBuffer, &numFramesRead, &dwFlags, &devPosition, &qpcTimestamp);

        if (FAILED(hRes)) {
            RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetBuffer failed, result = %08lX"), hRes);
            return false;
        }

        UINT totalFloatsRead = numFramesRead*GetChannelCount();

        if (bConvert) {
            if (convertBuffer.Num() < totalFloatsRead)
                convertBuffer.SetSize(totalFloatsRead);

            short *shortBuffer = (short*)captureBuffer;
            for (UINT i = 0; i < totalFloatsRead; i++)
                convertBuffer[i] = float(shortBuffer[i])*(1.0f/32767.0f);

            captureBuffer = (LPBYTE)convertBuffer.Array();
        }

        if (inputBufferSize) {
            double timeAdjust = double(inputBufferSize/GetChannelCount());
            timeAdjust /= (double(GetSamplesPerSec())*0.0000001);

            qpcTimestamp -= UINT64(timeAdjust);
        }

        qpcTimestamp /= 10000;
        lastQPCTimestamp = qpcTimestamp;

        //---------------------------------------------------------

        UINT newInputBufferSize = inputBufferSize + totalFloatsRead;
        if (newInputBufferSize > inputBuffer.Num())
            inputBuffer.SetSize(newInputBufferSize);

        mcpy(inputBuffer.Array()+inputBufferSize, captureBuffer, totalFloatsRead*sizeof(float));
        inputBufferSize = newInputBufferSize;

        mmCapture->ReleaseBuffer(numFramesRead);

        bFirstRun = false;
    }

    *numFrames = sampleWindowSize;
    *buffer = (void*)inputBuffer.Array();
    *timestamp = firstTimestamp;

    /*if (bIsMic) {
        static QWORD lastTimestamp = 0;
        if (firstTimestamp != lastTimestamp+10)
            Log(TEXT("A: %llu, difference: %llu"), firstTimestamp, firstTimestamp-lastTimestamp);

        lastTimestamp = firstTimestamp;
    }*/

    return true;
}
Beispiel #12
0
bool MMDeviceAudioSource::GetNextBuffer(void **buffer, UINT *numFrames, QWORD *timestamp)
{
    UINT captureSize = 0;
    bool bFirstRun = true;
    HRESULT hRes;

    while (true) {
        if (inputBufferSize >= sampleWindowSize*GetChannelCount()) {
            if (bFirstRun)
                lastQPCTimestamp += 10;
            firstTimestamp = GetTimestamp(lastQPCTimestamp);
            break;
        }

        //---------------------------------------------------------

        hRes = mmCapture->GetNextPacketSize(&captureSize);

        if (FAILED(hRes)) {
            RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetNextPacketSize failed, result = %08lX"), hRes);
            return false;
        }

        if (!captureSize)
            return false;

        //---------------------------------------------------------

        LPBYTE captureBuffer;
        UINT32 numFramesRead;
        DWORD dwFlags = 0;

        UINT64 devPosition, qpcTimestamp;
        hRes = mmCapture->GetBuffer(&captureBuffer, &numFramesRead, &dwFlags, &devPosition, &qpcTimestamp);

        if (FAILED(hRes)) {
            RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetBuffer failed, result = %08lX"), hRes);
            return false;
        }

        if (inputBufferSize) {
            double timeAdjust = double(inputBufferSize/GetChannelCount());
            timeAdjust /= (double(GetSamplesPerSec())*0.0000001);

            qpcTimestamp -= UINT64(timeAdjust);
        }

        /*if (!bIsMic) {
            Log(TEXT("f: %u, i: %u, qpc: %llu"), numFramesRead, inputBufferSize != 0, qpcTimestamp);
        }*/

        qpcTimestamp /= 10000;
        lastQPCTimestamp = qpcTimestamp;

        //---------------------------------------------------------

        UINT totalFloatsRead = numFramesRead*GetChannelCount();
        UINT newInputBufferSize = inputBufferSize + totalFloatsRead;
        if (newInputBufferSize > inputBuffer.Num())
            inputBuffer.SetSize(newInputBufferSize);

        SSECopy(inputBuffer.Array()+inputBufferSize, captureBuffer, totalFloatsRead*sizeof(float));
        inputBufferSize = newInputBufferSize;

        mmCapture->ReleaseBuffer(numFramesRead);

        bFirstRun = false;
    }

    *numFrames = sampleWindowSize;
    *buffer = (void*)inputBuffer.Array();
    *timestamp = firstTimestamp;

    return true;
}
Beispiel #13
0
 ////////////////////////////////////////////////////////////
 /// /see SoundStream::OnSeek
 ///
 ////////////////////////////////////////////////////////////
 virtual void OnSeek(sf::Uint32 timeOffset)
 {
     myOffset = timeOffset * GetSampleRate() * GetChannelCount() / 1000;
 }
Beispiel #14
0
bool MMDeviceAudioSource::GetNextBuffer(void **buffer, UINT *numFrames, QWORD *timestamp)
{
    UINT captureSize = 0;
    bool bFirstRun = true;
    HRESULT hRes;
    UINT64 devPosition, qpcTimestamp;
    LPBYTE captureBuffer;
    UINT32 numFramesRead;
    DWORD dwFlags = 0;

    if (deviceLost) {
        QWORD timeVal = GetQPCTimeMS();
        QWORD timer = (timeVal - reinitTimer);
        if (timer > 1000) {
            if (Reinitialize()) {
                Log(L"Device '%s' reacquired.", strDeviceName.Array());
                StartCapture();
            }
            reinitTimer = timeVal;
        }

        return false;
    }

    while (true) {
        if (inputBufferSize >= sampleWindowSize*GetChannelCount()) {
            if (bFirstRun)
                lastQPCTimestamp += 10;
            firstTimestamp = GetTimestamp(lastQPCTimestamp);
            break;
        }

        //---------------------------------------------------------

        hRes = mmCapture->GetNextPacketSize(&captureSize);

        if (FAILED(hRes)) {
            if (hRes == AUDCLNT_E_DEVICE_INVALIDATED) {
                FreeData();
                deviceLost = true;
                Log(L"Audio device '%s' has been lost, attempting to reinitialize", strDeviceName.Array());
                reinitTimer = GetQPCTimeMS();
                return false;
            }

            RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetNextPacketSize failed, result = %08lX"), hRes);
            return false;
        }

        if (!captureSize)
            return false;

        //---------------------------------------------------------

        hRes = mmCapture->GetBuffer(&captureBuffer, &numFramesRead, &dwFlags, &devPosition, &qpcTimestamp);

        if (FAILED(hRes)) {
            RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetBuffer failed, result = %08lX"), hRes);
            return false;
        }

        UINT totalFloatsRead = numFramesRead*GetChannelCount();

        if (inputBufferSize) {
            double timeAdjust = double(inputBufferSize/GetChannelCount());
            timeAdjust /= (double(GetSamplesPerSec())*0.0000001);

            qpcTimestamp -= UINT64(timeAdjust);
        }

        qpcTimestamp /= 10000;
        lastQPCTimestamp = qpcTimestamp;

        //---------------------------------------------------------

        UINT newInputBufferSize = inputBufferSize + totalFloatsRead;
        if (newInputBufferSize > inputBuffer.Num())
            inputBuffer.SetSize(newInputBufferSize);

        mcpy(inputBuffer.Array()+inputBufferSize, captureBuffer, totalFloatsRead*sizeof(float));
        inputBufferSize = newInputBufferSize;

        mmCapture->ReleaseBuffer(numFramesRead);

        bFirstRun = false;
    }

    *numFrames = sampleWindowSize;
    *buffer = (void*)inputBuffer.Array();
    *timestamp = firstTimestamp;

    /*if (bIsMic) {
        static QWORD lastTimestamp = 0;
        if (firstTimestamp != lastTimestamp+10)
            Log(TEXT("A: %llu, difference: %llu"), firstTimestamp, firstTimestamp-lastTimestamp);

        lastTimestamp = firstTimestamp;
    }*/

    return true;
}
std::string AUDIOSOURCE::GetChannelString()
{
	std::ostringstream sstrResult;
	sstrResult << GetChannelCount();
	return sstrResult.str();
}
Beispiel #16
0
 ////////////////////////////////////////////////////////////
 /// /see SoundStream::OnSeek
 ///
 ////////////////////////////////////////////////////////////
 virtual void OnSeek(sf::Time timeOffset)
 {
     myOffset = timeOffset.AsMilliseconds() * GetSampleRate() * GetChannelCount() / 1000;
 }
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("<<PORT_GetControls: line (portIndex = %d) not found\n", portIndex);
        return;
    }

    PortLine *port = &(mixer->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("<<PORT_GetControls (portIndex = %d): no owned AudioControls\n", portIndex);
        return;
    }

    int totalChannels = GetChannelCount(mixer->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; i<mixer->deviceControlCount; 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, 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("<<PORT_GetControls (portIndex = %d)\n", portIndex);
}