/*
    This file is part of kenvy24 KDE multimedia utility
    Copyright (c) 2007  Valentin RUSU kenvy24@rusu.info

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License or (at
    your option) version 3 or, at the discretion of KDE e.V. (which shall
    act as a proxy as in section 14 of the GPLv3), any later version.

    This program 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 for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "envycard.h"

#include <assert.h>
//#include <gdk/gdk.h>
#include <qsocketnotifier.h>
#include <algorithm>
#include <math.h>
#include <kdebug.h>

#define ANALOG_PLAYBACK_ROUTE_NAME  "H/W Playback Route"
#define SPDIF_PLAYBACK_ROUTE_NAME   "IEC958 Playback Route"
#define MULTI_PLAYBACK_SWITCH       "Multi Playback Switch"
#define HW_MULTI_CAPTURE_SWITCH     "H/W Multi Capture Switch"
#define IEC958_MULTI_CAPTURE_SWITCH "IEC958 Multi Capture Switch"

#define HW_MULTI_CAPTURE_VOLUME     "H/W Multi Capture Volume"
#define IEC958_MULTI_CAPTURE_VOLUME "IEC958 Multi Capture Volume"
#define MULTI_PLAYBACK_VOLUME       "Multi Playback Volume"

#define DAC_VOLUME_NAME             "DAC Volume"
#define ADC_VOLUME_NAME             "ADC Volume"

EnvyCard::SndCtlElemValue::SndCtlElemValue( _snd_ctl_elem_iface iface, const char* name ) {
  snd_ctl_elem_value_malloc( &val);
  snd_ctl_elem_value_set_interface( val, iface);
  snd_ctl_elem_value_set_name( val, name );
}

EnvyCard::SndCtlElemValue::SndCtlElemValue( _snd_ctl_elem_iface iface, const char* name, int index ) {
    snd_ctl_elem_value_malloc( &val);
    snd_ctl_elem_value_set_interface( val, iface);
    snd_ctl_elem_value_set_name( val, name );
    snd_ctl_elem_value_set_index( val, index );
}

EnvyCard::SndCtlElemValue::~SndCtlElemValue() {
  snd_ctl_elem_value_free(val);
}


EnvyCard::EnvyCard() :
    cardIsDMX6FIRE(false)
{
    SndCtlElemValue val( SND_CTL_ELEM_IFACE_CARD, "ICE1712 EEPROM" );

    int err;
    if ((err = snd_ctl_elem_read(ctl, &val)) < 0) {
        fprintf(stderr, "Unable to read EEPROM contents: %s\n", snd_strerror(err));
        exit(EXIT_FAILURE);
    }
    memcpy(&card_eeprom, snd_ctl_elem_value_get_bytes(&val), 32);

    if(card_eeprom.subvendor == ICE1712_SUBDEVICE_DMX6FIRE)
        cardIsDMX6FIRE = true;

    snd_ctl_elem_value_malloc(&peaks);
    snd_ctl_elem_value_set_interface(peaks, SND_CTL_ELEM_IFACE_MIXER);
    snd_ctl_elem_value_set_name(peaks, "Multi Track Peak");

    initPatchbay();
    initEvents();
}

EnvyCard::EnvyCard( snd_ctl_t* sndCtl, const char* name, int number ) :
    ctl( sndCtl),
    cardNumber( number ),
    cardIsDMX6FIRE( false )
{
    assert( sndCtl != 0 );
    assert( name != 0 );

    cardName = name;

    snd_ctl_card_info_t     *hw_info;
    snd_ctl_card_info_alloca(&hw_info);
    assert( snd_ctl_card_info(sndCtl, hw_info) >=0 );
    cardLongName = snd_ctl_card_info_get_longname( hw_info );
    if (cardLongName.isEmpty())
        cardLongName = name;
    kDebug() << "binding to card " << cardLongName;

    // find out it it's a DMX 6FIRE
    SndCtlElemValue val( SND_CTL_ELEM_IFACE_CARD, "ICE1712 EEPROM" );

    int err;
    if ((err = snd_ctl_elem_read(ctl, &val)) < 0) {
        fprintf(stderr, "Unable to read EEPROM contents: %s\n", snd_strerror(err));
        exit(EXIT_FAILURE);
    }
    memcpy(&card_eeprom, snd_ctl_elem_value_get_bytes(&val), 32);

    if(card_eeprom.subvendor == ICE1712_SUBDEVICE_DMX6FIRE)
        cardIsDMX6FIRE = true;

    snd_ctl_elem_value_malloc(&peaks);
    snd_ctl_elem_value_set_interface(peaks, SND_CTL_ELEM_IFACE_MIXER);
    snd_ctl_elem_value_set_name(peaks, "Multi Track Peak");

    initPatchbay();
    initEvents();
}

EnvyCard::~EnvyCard() {
  while ( !eventNotifiers.empty() ) {
      delete eventNotifiers.back();
      eventNotifiers.pop_back();
  }
  snd_ctl_close( ctl );
/*
  if ( peaks != 0)
    snd_ctl_elem_value_free( peaks );
  if ( val != 0 )
    snd_ctl_elem_value_free( val );
  if ( hw_info != 0 )
    snd_ctl_card_info_free( hw_info );
*/
}

void EnvyCard::updatePeaks() {
    int err;
    // FIXME implement error handling
    err = snd_ctl_elem_read(ctl, peaks);

    int left, right, index;

    // PCM levels
    int base = 0;
    for ( index = base;
          index < MAX_PCM_OUTPUTS; index++ ) {
      left = snd_ctl_elem_value_get_integer( peaks, 2*index );
      right = snd_ctl_elem_value_get_integer( peaks, 2*index +1 );
      if ( pcmLevels[ index ].left != left || pcmLevels[ index ].right != right ) {
        pcmLevels[ index ].left = left;
        pcmLevels[ index ].right = right;
        emit pcmLevelUpdated( index, pcmLevels[ index ] );
      }
    }

    // spdif playback levels
    base = MAX_PCM_OUTPUTS;
    for ( index = base; index <= base + MAX_SPDIF_CHANNELS; index++ ) {
      left = snd_ctl_elem_value_get_integer( peaks, 2*index );
      right = snd_ctl_elem_value_get_integer( peaks, 2*index +1 );
      if ( spdifLevels[ index ].left != left || spdifLevels[ index ].right != right ) {
          spdifLevels[ index -base ].left = left;
          spdifLevels[ index -base ].right = right;
          emit spdifLevelUpdated( index - base, spdifLevels[ index -base ] );
      }
    }

    // input levels
    base = MAX_PCM_OUTPUTS + MAX_SPDIF_CHANNELS;
    for ( index = base;
          index <= (base + MAX_INPUT_CHANNELS); index++ ) {
      left = snd_ctl_elem_value_get_integer( peaks, 2*index );
      right = snd_ctl_elem_value_get_integer( peaks, 2*index +1 );
      if ( inputLevels[ index ].left != left || inputLevels[ index ].right != right ) {
          inputLevels[ index -base ].left = left;
          inputLevels[ index -base ].right = right;
          emit inputLevelUpdated( index - base, inputLevels[ index -base ] );
      }
    }

    // spdif input levels
    base = MAX_PCM_OUTPUTS + MAX_SPDIF_CHANNELS + MAX_INPUT_CHANNELS;
    for ( index = base;
          index <= (base + MAX_SPDIF_CHANNELS); index++ ) {
      left = snd_ctl_elem_value_get_integer( peaks, 2*index );
      right = snd_ctl_elem_value_get_integer( peaks, 2*index +1 );
      if ( spdifInLevels[ index ].left != left || spdifInLevels[ index ].right != right ) {
        spdifInLevels[ index -base ].left = left;
        spdifInLevels[ index -base ].right = right;
        emit spdifInLevelUpdated( index - base, spdifInLevels[ index -base  ] );
      }
    }


    // mixer levels
    left = snd_ctl_elem_value_get_integer( peaks, 20);
    right = snd_ctl_elem_value_get_integer( peaks, 21);
    if ( mixerLevels.left != left || mixerLevels.right != right ) {
      mixerLevels.left = left;
      mixerLevels.right = right;
      emit mixerLevelUpdated( mixerLevels );
    }
}

void EnvyCard::initPatchbay() {

    // detect active outputs
    outChannels = 0;
    for ( int out=0; out< MAX_OUTPUT_CHANNELS; out++) {
        SndCtlElemValue left( SND_CTL_ELEM_IFACE_MIXER, ANALOG_PLAYBACK_ROUTE_NAME );
        SndCtlElemValue right( SND_CTL_ELEM_IFACE_MIXER, ANALOG_PLAYBACK_ROUTE_NAME );

        snd_ctl_elem_value_set_numid( &left, 0 );
        snd_ctl_elem_value_set_numid( &right, 0 );

        snd_ctl_elem_value_set_index( &left, 2*out);
        snd_ctl_elem_value_set_index( &right, 2*out+1);

        if ((snd_ctl_elem_read(ctl, &left) < 0) ||
                (snd_ctl_elem_read(ctl, &right) < 0))   {
            outputActive[ out ] = false;
        }
        else {
            outputActive[ out ] = true;
            ++outChannels;
        }
    }

    spdifChannels = 0;
    for ( int spdif = 0; spdif < MAX_SPDIF_CHANNELS; spdif++ ) {
        SndCtlElemValue left( SND_CTL_ELEM_IFACE_MIXER, SPDIF_PLAYBACK_ROUTE_NAME );
        SndCtlElemValue right( SND_CTL_ELEM_IFACE_MIXER, SPDIF_PLAYBACK_ROUTE_NAME );

        snd_ctl_elem_value_set_numid( &left, 0 );
        snd_ctl_elem_value_set_numid( &right, 0 );

        snd_ctl_elem_value_set_index( &left, 2*spdif);
        snd_ctl_elem_value_set_index( &right, 2*spdif+1);

        if ((snd_ctl_elem_read(ctl, &left) < 0) ||
             (snd_ctl_elem_read(ctl, &right) < 0))   {
            outputSPDIFActive[ spdif ] = false;
        }
        else {
            outputSPDIFActive[ spdif ] = true;
            ++spdifChannels;
        }
    }
}

bool EnvyCard::isOutputActive( int index ) {
    assert( (index >= 0) || (index < MAX_OUTPUT_CHANNELS) );
    return outputActive[ index ];
}

bool EnvyCard::isOutputSPDIFActive( int index ) {
    assert( (index >= 0) || (index < MAX_SPDIF_CHANNELS) );
    return outputSPDIFActive[ index ];
}

EnvyCard::PatchBayRoutes  EnvyCard::getAnalogRouteState( int index, LeftRight channel ) {
    PatchBayRoutes result = ROUTE_NONE;
    int err;
    int channelIndex = 2*index + channel;
    if ( isOutputActive( index ) ) {
        SndCtlElemValue val( SND_CTL_ELEM_IFACE_MIXER, ANALOG_PLAYBACK_ROUTE_NAME );
        snd_ctl_elem_value_set_index( &val, channelIndex );
        if ((err = snd_ctl_elem_read(ctl, &val)) >= 0) {
            int out = snd_ctl_elem_value_get_enumerated( &val, 0);

            if (out >= MAX_INPUT_CHANNELS*2 + MAX_SPDIF_CHANNELS*2 + 1) {
                if (channelIndex >= MAX_PCM_OUTPUTS*2 || channelIndex < MAX_SPDIF_CHANNELS*2)
                    result = (PatchBayRoutes)1; /* digital mixer */
                else
                    assert(0); // TODO: unknown state - fix it
            } else if (out >= MAX_INPUT_CHANNELS*2 + 1)
                result = (PatchBayRoutes)(out - (MAX_INPUT_CHANNELS*2 + 1) + 2); /* spdif left (=2) / right (=3) */
                else if (out >= 1)
                    result =  (PatchBayRoutes)( out + spdifChannels + 1 ); /* analog (4-) */
                else
                    result = (PatchBayRoutes)0; /* pcm */
        }
    }
    return result;
}

EnvyCard::PatchBayRoutes  EnvyCard::getSPDIFRouteState( int index, LeftRight  ) {
    PatchBayRoutes result = ROUTE_NONE;
    if ( isOutputSPDIFActive( index ) ) {
    }
    return result;
}


void EnvyCard::initEvents() {
    int npfds = snd_ctl_poll_descriptors_count(ctl);
    if (npfds > 0) {
        struct pollfd* pfds = (pollfd*)alloca(sizeof(*pfds) * npfds);
        npfds = snd_ctl_poll_descriptors(ctl, pfds, npfds);
        for (int i = 0; i < npfds; i++) {
/*            gdk_input_add(pfds[i].fd,
                          GDK_INPUT_READ,
                          controlInputCallback,
                          ctl);*/
            QSocketNotifier* eventNotifier = new QSocketNotifier( pfds[i].fd, QSocketNotifier::Read );
            connect( eventNotifier, SIGNAL( activated( int ) ), SLOT( driverEvent( int ) ) );
            eventNotifiers.push_back( eventNotifier );
        }
        snd_ctl_subscribe_events(ctl, 1);
    }
}

void EnvyCard::pulseSignals()
{
    // pulse everything but vu levels
    // the levels will updated when the timer will tick

    // TODO iterate the correct number of channels, depending on the card type
    for ( int i=0; i < MAX_PCM_OUTPUTS*2; i++ )
    {
        mixerUpdatePlaybackVolume( i );
        mixerUpdatePlaybackSwitch( i );
    }

    // TODO iterate the currect number of input channels, depending on the card type
    for ( int i =0; i < MAX_INPUT_CHANNELS*2; i++ )
    {
        mixerUpdateInputVolume( i );
        mixerUpdateInputSwitch( i );
    }

    // TODO iterato the currect number of spdif channels
    for ( int i =0; i < MAX_SPDIF_CHANNELS*2; i++ )
    {
        mixerUpdateSPDIFVolume( i );
        mixerUpdateSPDIFSwitch( i );
    }

    dacVolumeUpdate(0);
    dacVolumeUpdate(1);

    adcVolumeUpdate(0);
    adcVolumeUpdate(1);

    patchbayUpdate();
}

void EnvyCard::driverEvent( int  ) {
    snd_ctl_event_t *ev;
    snd_ctl_event_alloca(&ev);
    if (snd_ctl_read(ctl, ev) < 0)
        return;

    const char* name = snd_ctl_event_elem_get_name(ev);
    int index = snd_ctl_event_elem_get_index(ev);
    unsigned int mask = snd_ctl_event_elem_get_mask(ev);
    if (! (mask & (SND_CTL_EVENT_MASK_VALUE | SND_CTL_EVENT_MASK_INFO)))
        return;

#ifdef TRACE_DRIVER_EVENTS
    qDebug( "driverEvent : %s (%d)", name, index );
#endif 

    switch (snd_ctl_event_elem_get_interface(ev)) {
        case SND_CTL_ELEM_IFACE_MIXER:
            // TODO: implement these cases
/*            if (!strcmp(name, "Word Clock Sync"))
            masterClockUpdate();*/
/*            else if (!strcmp(name, "Multi Track Volume Rate"))
            volumeChangeRateUpdate();
            else if (!strcmp(name, "IEC958 Input Optical"))
            spdifInputUpdate();
            else if (!strcmp(name, "Delta IEC958 Output Defaults"))
            spdifOutputUpdate();*/
/*            else if (!strcmp(name, "Multi Track Internal Clock"))
            masterClockUpdate();
            else if (!strcmp(name, "Multi Track Internal Clock Default"))
            masterClockUpdate();
            else if (!strcmp(name, "Multi Track Rate Locking"))
            rateLockingUpdate();
            else if (!strcmp(name, "Multi Track Rate Reset"))
            rateResetUpdate();*/

            if (!strcmp(name, MULTI_PLAYBACK_VOLUME))
                mixerUpdatePlaybackVolume(index);
            else if (!strcmp(name, HW_MULTI_CAPTURE_VOLUME))
                mixerUpdateInputVolume(index);
            else if (!strcmp(name, IEC958_MULTI_CAPTURE_VOLUME))
                mixerUpdateSPDIFVolume(index);

            else if (!strcmp(name, MULTI_PLAYBACK_SWITCH))
                mixerUpdatePlaybackSwitch(index);
            else if (!strcmp(name, HW_MULTI_CAPTURE_SWITCH))
                mixerUpdateInputSwitch(index);
            else if (!strcmp(name,IEC958_MULTI_CAPTURE_SWITCH))
                mixerUpdateSPDIFSwitch(index);

            else if (!strcmp(name, ANALOG_PLAYBACK_ROUTE_NAME))
                patchbayUpdate();
            else if (!strcmp(name, SPDIF_PLAYBACK_ROUTE_NAME))
                patchbayUpdate();

            else if (!strcmp(name, DAC_VOLUME_NAME))
                dacVolumeUpdate(index);
            else if (!strcmp(name,ADC_VOLUME_NAME))
                adcVolumeUpdate(index);
            // TODO; implement these cases
/*
            else if (!strcmp(name, "IPGA Analog Capture Volume"))
            ipgaVolumeUpdate(index);
            else if (!strcmp(name, "Output Sensitivity Switch"))
            dacSenseUpdate(index);
            else if (!strcmp(name, "Input Sensitivity Switch"))
            adcSenseUpdate(index);*/
            break;
        default:
            break;
    }
}

void EnvyCard::mixerUpdatePlaybackVolume(int index){
    SndCtlElemValue vols( SND_CTL_ELEM_IFACE_MIXER, getSectionName( SECTION_PLAYBACK) );
    snd_ctl_elem_value_set_index( &vols, index );
    readSndCtl( vols );
    int volLeft = snd_ctl_elem_value_get_integer( &vols, 0 );
    int volRight = snd_ctl_elem_value_get_integer( &vols, 1 );
    // TODO call log scale function
    emit mixerUpdatePlaybackVolume( index/2, (LeftRight)(index%2), volLeft, volRight );
}

void EnvyCard::mixerUpdateInputVolume(int index){
    SndCtlElemValue vols( SND_CTL_ELEM_IFACE_MIXER, getSectionName( SECTION_CAPTURE ) );
    snd_ctl_elem_value_set_index( &vols, index );
    readSndCtl( vols );
    int volLeft = snd_ctl_elem_value_get_integer( &vols, 0 );
    int volRight = snd_ctl_elem_value_get_integer( &vols, 1 );
    // TODO call log scale function
    emit mixerUpdateAnalogInVolume( index/2, (LeftRight)(index%2), volLeft, volRight );
}

void EnvyCard::mixerUpdateSPDIFVolume(int index){
    SndCtlElemValue vols( SND_CTL_ELEM_IFACE_MIXER, getSectionName( SECTION_IEC598 ) );
    snd_ctl_elem_value_set_index( &vols, index );
    readSndCtl( vols );
    int volLeft = snd_ctl_elem_value_get_integer( &vols, 0 );
    int volRight = snd_ctl_elem_value_get_integer( &vols, 1 );
    // TODO call log scale function
    emit mixerUpdateSPDIFVolume( index/2, (LeftRight)(index%2), volLeft, volRight );
}

void EnvyCard::dacVolumeUpdate( int index )
{
#ifdef TRACE_DRIVER_EVENTS
    kDebug() << index;
#endif 
    SndCtlElemValue vols( SND_CTL_ELEM_IFACE_MIXER,  DAC_VOLUME_NAME );
    snd_ctl_elem_value_set_index( &vols, index );
    readSndCtl( vols );
    int vol = snd_ctl_elem_value_get_integer( &vols, 0 );
    // TODO call log scale function
    emit analogUpdateDACVolume( index/2, (LeftRight)(index%2), vol );
}

void EnvyCard::adcVolumeUpdate(int)
{
}

void EnvyCard::mixerUpdatePlaybackSwitch(int index){
    SndCtlElemValue sw( SND_CTL_ELEM_IFACE_MIXER, MULTI_PLAYBACK_SWITCH );
    snd_ctl_elem_value_set_index( &sw, index );
    if ( snd_ctl_elem_read( ctl, &sw ) >= 0 ) {
        bool left = snd_ctl_elem_value_get_boolean( &sw, 0 );
        bool right = snd_ctl_elem_value_get_boolean( &sw, 1 );
        if ( left != right )
            qWarning( "Mute switches for channel %d do not match. Displaying state of left channel only", index );
        emit mixerUpdatePCMMuteSwitch( index /2, (LeftRight)(index %2), !left );
    }
}

/**
 *
 */
void EnvyCard::mixerUpdateInputSwitch(int index) {
    SndCtlElemValue sw( SND_CTL_ELEM_IFACE_MIXER, HW_MULTI_CAPTURE_SWITCH );
    snd_ctl_elem_value_set_index( &sw, index );
    if ( snd_ctl_elem_read( ctl, &sw ) >= 0 ) {
        bool leftOn = snd_ctl_elem_value_get_boolean( &sw, 0 );
        bool rightOn = snd_ctl_elem_value_get_boolean( &sw, 1 );
        if ( leftOn != rightOn )
            qWarning( "Mute switches for channel %d do not match. Displaying state of left channel only", index );
        emit mixerUpdateAnalogInMuteSwitch( index /2, (LeftRight)(index %2), !leftOn );
    }
}

void EnvyCard::mixerUpdateSPDIFSwitch(int index){
    SndCtlElemValue sw( SND_CTL_ELEM_IFACE_MIXER, IEC958_MULTI_CAPTURE_SWITCH );
    snd_ctl_elem_value_set_index( &sw, index );
    if ( snd_ctl_elem_read( ctl, &sw ) >= 0 ) {
        bool left = snd_ctl_elem_value_get_boolean( &sw, 0 );
        bool right = snd_ctl_elem_value_get_boolean( &sw, 1 );
        if ( left != right )
            qWarning( "Mute switches for channel %d do not match. Displaying state of left channel only", index );
        emit mixerUpdateSPDIFInMuteSwitch( index /2, (LeftRight)(index %2), !left );
    }
}

void EnvyCard::patchbayUpdate() {
    emit patchBayUpdated();
}

void EnvyCard::mixerMuteCaptureChannel( int index, LeftRight channel, bool muteOn ) {
    SndCtlElemValue sw( SND_CTL_ELEM_IFACE_MIXER, HW_MULTI_CAPTURE_SWITCH );
    snd_ctl_elem_value_set_index( &sw, index *2 + (int)channel );
    snd_ctl_elem_value_set_boolean( &sw, 0, !muteOn );
    snd_ctl_elem_value_set_boolean( &sw, 1, !muteOn );
    writeSndCtl( sw );
}

void EnvyCard::mixerMutePlaybackChannel( int index, LeftRight channel, bool muteOn ) {
    SndCtlElemValue sw( SND_CTL_ELEM_IFACE_MIXER, MULTI_PLAYBACK_SWITCH );
    snd_ctl_elem_value_set_index( &sw, index *2 + (int)channel );
    snd_ctl_elem_value_set_boolean( &sw, 0, !muteOn );
    snd_ctl_elem_value_set_boolean( &sw, 1, !muteOn );
    writeSndCtl( sw );
}

void EnvyCard::mixerMuteSPDIFChannel( int index, LeftRight channel, bool muteOn) {
    SndCtlElemValue sw( SND_CTL_ELEM_IFACE_MIXER, IEC958_MULTI_CAPTURE_SWITCH );
    snd_ctl_elem_value_set_index( &sw, index *2 + (int)channel );
    snd_ctl_elem_value_set_boolean( &sw, 0, !muteOn );
    snd_ctl_elem_value_set_boolean( &sw, 1, !muteOn );
    writeSndCtl( sw );
}

void EnvyCard::writeSndCtl( const SndCtlElemValue& val ) {
    int err = snd_ctl_elem_write( ctl, &val );
    if ( err <0 )
        qWarning( "Unable to write value: %s", snd_strerror( err ) );
}

void EnvyCard::readSndCtl( const SndCtlElemValue& val ) {
    int err = snd_ctl_elem_read( ctl, &val );
    if ( err <0 )
        qWarning( "Unable to read value: %s", snd_strerror( err ) );
}

bool EnvyCard::getMixerPCMMuteState( int index, LeftRight channel ) {
    SndCtlElemValue sw( SND_CTL_ELEM_IFACE_MIXER, MULTI_PLAYBACK_SWITCH );
    snd_ctl_elem_value_set_index( &sw, index *2 + (int)channel );
    readSndCtl( sw );
    return !snd_ctl_elem_value_get_boolean(&sw, 0);
}

bool EnvyCard::getMixerAnalogInMuteState( int index, LeftRight channel ) {
    SndCtlElemValue sw( SND_CTL_ELEM_IFACE_MIXER, HW_MULTI_CAPTURE_SWITCH );
    snd_ctl_elem_value_set_index( &sw, index *2 + (int)channel );
    readSndCtl( sw );
    return !snd_ctl_elem_value_get_boolean(&sw, 0);
}

bool EnvyCard::getMixerSPDIFMuteState( int index, LeftRight channel ) {
    SndCtlElemValue sw( SND_CTL_ELEM_IFACE_MIXER, IEC958_MULTI_CAPTURE_SWITCH );
    snd_ctl_elem_value_set_index( &sw, index *2 + (int)channel );
    readSndCtl( sw );
    return !snd_ctl_elem_value_get_boolean(&sw, 0);
}

void EnvyCard::setMixerCaptureVolume( int index, LeftRight channel, int volLeft, int volRight )
{
    setVolume( SECTION_CAPTURE, index, channel, volLeft, volRight );
}

void EnvyCard::setMixerPlaybackVolume( int index, LeftRight channel, int volLeft, int volRight )
{
    setVolume( SECTION_PLAYBACK, index, channel, volLeft, volRight );
}

void EnvyCard::setMixerSPDIFVolume( int index, LeftRight channel, int volLeft, int volRight )
{
    setVolume( SECTION_IEC598, index, channel, volLeft, volRight );
}

// void EnvyCard::setVolume( Section section, int index, LeftRight channel, int vol, int pan ) {
//     assert( vol <= 96 );
//
//     int leftVol = lrint( vol * std::min(1.0, (double)(PAN_MAX -pan) / PAN_MAX) );
//     int rightVol = lrint( vol * std::min(1.0, (double)(pan + PAN_MAX) / PAN_MAX) );
//
//     SndCtlElemValue volume( SND_CTL_ELEM_IFACE_MIXER, getSectionName( section ) );
//     snd_ctl_elem_value_set_index( &volume, index * 2 + (int)channel );
//     readSndCtl( volume );
// /*
//     int curLeftVol = snd_ctl_elem_value_get_integer( &volume, 0 );
//     int curRightVol = snd_ctl_elem_value_get_integer( &volume, 1 );
//
//     if ( curLeftVol != leftVol || curRightVol != rightVol ) { */
//         leftVol = std::max( leftVol, 1 );
//         rightVol = std::max( rightVol, 1 );
//
//         leftVol = (int)round( (96.0 / log(96.0)) * log( leftVol ) );
//         rightVol = (int)round( (96.0 / log(96.0)) * log( rightVol ) );
//
//         snd_ctl_elem_value_set_integer( &volume, 0, leftVol );
//         snd_ctl_elem_value_set_integer( &volume, 1, rightVol );
//
//         writeSndCtl( volume );
// //    }
// }
void EnvyCard::setVolume( Section section, int index, LeftRight channel, int volLeft, int volRight )
{
    SndCtlElemValue volume( SND_CTL_ELEM_IFACE_MIXER, getSectionName( section ) );
    snd_ctl_elem_value_set_index( &volume, index * 2 + (int)channel );
    readSndCtl( volume );
    int curVolLeft = snd_ctl_elem_value_get_integer( &volume, 0 );
    int curVolRight = snd_ctl_elem_value_get_integer( &volume, 1 );
    if ( (curVolLeft != volLeft) || (curVolRight != volRight) )
    {
        snd_ctl_elem_value_set_integer( &volume, 0, volLeft );
        snd_ctl_elem_value_set_integer( &volume, 1, volRight );
        writeSndCtl( volume );
    }
}

const char* EnvyCard::getSectionName( Section section ) {
    const char* elemName = 0;
    switch ( section ) {
        case SECTION_CAPTURE:
            elemName = HW_MULTI_CAPTURE_VOLUME;
            break;
        case SECTION_PLAYBACK:
            elemName = MULTI_PLAYBACK_VOLUME;
            break;
        case SECTION_IEC598:
            elemName = IEC958_MULTI_CAPTURE_VOLUME;
            break;
        default:
            assert(0);
    }
    return elemName;
}

// MixerAdjustement EnvyCard::getMixerCaptureAdjustement( int index, LeftRight channel ) {
//     return getMixerAdjustement( SECTION_CAPTURE, index, channel );
// }
//
// MixerAdjustement EnvyCard::getMixerPlaybackAdjustement( int index, LeftRight channel) {
//     return getMixerAdjustement( SECTION_PLAYBACK, index, channel );
// }
//
// MixerAdjustement EnvyCard::getMixerSPDIFAdjustement( int index, LeftRight channel) {
//     return getMixerAdjustement( SECTION_IEC598, index, channel );
// }

// MixerAdjustement EnvyCard::getMixerAdjustement( Section sect, int index, LeftRight channel ) {
//     SndCtlElemValue volume( SND_CTL_ELEM_IFACE_MIXER, getSectionName( sect ) );
//     snd_ctl_elem_value_set_index( &volume, index * 2 + (int)channel );
//     readSndCtl( volume );
//
//     int curLeftVol = snd_ctl_elem_value_get_integer( &volume, 0 );
//     int curRightVol = snd_ctl_elem_value_get_integer( &volume, 1 );
//     return deduceMixerAdjustement( curLeftVol, curRightVol );
// }

/**
 * pan = PAN_MAX*(1-L/R)/(1+L/R)
 * volume is logarithmic. e.g. slider volume is converted to log. so, when deducing
 * slider value, an exponentiation must be done.
 */
// MixerAdjustement EnvyCard::deduceMixerAdjustement( int aleft, int aright, int maxVol /* = 96 */ ) {
//     MixerAdjustement result;
//
//     double L = aleft == 0 ? 0.0 : (int)round( pow( (double)maxVol, (double)aleft / (double)maxVol ) );
//     double R = aright == 0 ? 0.0 : (int)round( pow( (double)maxVol, (double)aright / (double)maxVol ) );
//
//     if ( R < L ) {
//         std::swap( L, R );
//     }
//
//     result.volume = lrint( std::max( L, R ) );
//
//     // This will also prevent division by zero
//     if ( aleft == aright)
//         result.pan = 0;
//     else
//         result.pan = lrint( (double)PAN_MAX * (1.0 - L/R)/(1.0 + L/R) );
//
//     if ( aleft > aright )
//         result.pan = -result.pan;
//     return result;
// }

void EnvyCard::patchBaySetRoute( RouteKind kind, int index, LeftRight channel, PatchBayRoutes route ) {
    int out = 0;
    switch ( route ) {
        case ROUTE_PCM_OR_SPDIF:
            out = 0;
            break;
        case ROUTE_MIXER_OUT:
            out = MAX_INPUT_CHANNELS*2 + MAX_SPDIF_CHANNELS*2 + 1;
            break;
        case ROUTE_INPUT_SPDIF_LEFT:
        case ROUTE_INPUT_SPDIF_RIGHT:
            out = route - ROUTE_INPUT_SPDIF_LEFT + 9; // 9..10
            break;
        case ROUTE_INPUT_ANALOG_LEFT:
        case ROUTE_INPUT_ANALOG_RIGHT:
            out = route - ROUTE_INPUT_ANALOG_LEFT + 1;
            break;
        default:
            assert(0);
    }

    SndCtlElemValue sndRoute( SND_CTL_ELEM_IFACE_MIXER, kind == ANALOG ? ANALOG_PLAYBACK_ROUTE_NAME : SPDIF_PLAYBACK_ROUTE_NAME );
    snd_ctl_elem_value_set_index( &sndRoute, 2*index + channel );
    snd_ctl_elem_value_set_enumerated( &sndRoute, 0, out );
    writeSndCtl( sndRoute );
}

void EnvyCard::routeThroughMixer()
{
    for ( int i=0; i< MAX_PCM_OUTPUTS; i++)
    {
        patchBaySetRoute( ANALOG, i, LEFT, ROUTE_MIXER_OUT );
        patchBaySetRoute( ANALOG, i, RIGHT, ROUTE_MIXER_OUT );
    }
    for ( int i =0; i< MAX_SPDIF_CHANNELS; i++)
    {
        patchBaySetRoute( SPDIF, i, LEFT, ROUTE_MIXER_OUT );
        patchBaySetRoute( SPDIF, i, RIGHT, ROUTE_MIXER_OUT );
    }
}

StereoLevels EnvyCard::getDACVolume() {
    StereoLevels levels;
    SndCtlElemValue leftVal( SND_CTL_ELEM_IFACE_MIXER, DAC_VOLUME_NAME, 0 );
    SndCtlElemValue rightVal( SND_CTL_ELEM_IFACE_MIXER, DAC_VOLUME_NAME, 1 );
    readSndCtl( leftVal );
    readSndCtl( rightVal );
    levels.left = leftVal.getInteger();
    levels.right = rightVal.getInteger();
    return levels;
}

StereoLevels EnvyCard::getADCVolume() {
    StereoLevels levels;
    SndCtlElemValue leftVal( SND_CTL_ELEM_IFACE_MIXER, ADC_VOLUME_NAME, 0 );
    SndCtlElemValue rightVal( SND_CTL_ELEM_IFACE_MIXER, ADC_VOLUME_NAME, 1 );
    readSndCtl( leftVal );
    readSndCtl( rightVal );
    levels.left = leftVal.getInteger();
    levels.right = rightVal.getInteger();
    return levels;
}

void EnvyCard::setDACVolume( LeftRight leftRight, int level ) {
    SndCtlElemValue val( SND_CTL_ELEM_IFACE_MIXER, DAC_VOLUME_NAME, (int)leftRight );
    val.setInteger( level );
    writeSndCtl( val );
}

void EnvyCard::setADCVolume( LeftRight leftRight, int level ) {
    SndCtlElemValue val( SND_CTL_ELEM_IFACE_MIXER, ADC_VOLUME_NAME, (int)leftRight );
    val.setInteger( level );
    writeSndCtl( val );
}

void EnvyCard::setLineInSource( int source )
{
    SndCtlElemValue analogInputSelect( SND_CTL_ELEM_IFACE_MIXER, "Analog Input Select" );
    snd_ctl_elem_value_set_enumerated( &analogInputSelect, 0, source);
    writeSndCtl( analogInputSelect );
}

void EnvyCard::setMicInSource( int source )
{
    SndCtlElemValue analogInputSelect( SND_CTL_ELEM_IFACE_MIXER, "Phono Analog Input Switch" );
    snd_ctl_elem_value_set_boolean( &analogInputSelect, 0, source);
    writeSndCtl( analogInputSelect );
}

#include "envycard.moc"
// kate: space-indent on; indent-width 4; replace-tabs on;
