/* * KMix -- KDE's full featured mini mixer * * * Copyright (C) 1996-2004 Christian Esken - esken@kde.org * 2002 Helio Chissini de Castro - helio@conectiva.com.br * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include "mixer.h" #include "mixer_backend.h" #include "kmix-platforms.cpp" #include "volume.h" //#define MIXER_MASTER_DEBUG #ifdef MIXER_MASTER_DEBUG #warning MIXER_MASTER_DEBUG is enabled. DO NOT SHIP KMIX LIKE THIS !!! #endif /** * Some general design hints. Hierachy is Mixer->MixDevice->Volume */ // !! Warning: Don't commit with "KMIX_DCOP_OBJID_TEST" #define'd (cesken) #undef KMIX_DCOP_OBJID_TEST int Mixer::_dcopID = 0; TQPtrList Mixer::s_mixers; TQString Mixer::_masterCard; TQString Mixer::_masterCardDevice; int Mixer::numDrivers() { MixerFactory *factory = g_mixerFactories; int num = 0; while( factory->getMixer!=0 ) { num++; factory++; } return num; } /* * Returns a reference of the current mixer list. */ TQPtrList& Mixer::mixers() { return s_mixers; } Mixer::Mixer( int driver, int device ) : DCOPObject( "Mixer" ) { _pollingTimer = 0; _mixerBackend = 0; getMixerFunc *f = g_mixerFactories[driver].getMixer; if( f!=0 ) { _mixerBackend = f( device ); } readSetFromHWforceUpdate(); // enforce an initial update on first readSetFromHW() m_balance = 0; m_profiles.setAutoDelete( true ); _pollingTimer = new TQTimer(); // will be started on open() and stopped on close() connect( _pollingTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(readSetFromHW())); TQCString objid; #ifndef KMIX_DCOP_OBJID_TEST objid.setNum(_mixerBackend->m_devnum); #else // should use a nice name like the Unique-Card-ID instead !! objid.setNum(Mixer::_dcopID); Mixer::_dcopID ++; #endif objid.prepend("Mixer"); DCOPObject::setObjId( objid ); } Mixer::~Mixer() { // Close the mixer. This might also free memory, depending on the called backend method close(); delete _pollingTimer; } void Mixer::volumeSave( TDEConfig *config ) { // kdDebug(67100) << "Mixer::volumeSave()" << endl; readSetFromHW(); TQString grp("Mixer"); grp.append(mixerName()); _mixerBackend->m_mixDevices.write( config, grp ); } void Mixer::volumeLoad( TDEConfig *config ) { TQString grp("Mixer"); grp.append(mixerName()); if ( ! config->hasGroup(grp) ) { // no such group. Volumes (of this mixer) were never saved beforehand. // Thus don't restore anything (also see Bug #69320 for understanding the real reason) return; // make sure to bail out immediately } // else restore the volumes _mixerBackend->m_mixDevices.read( config, grp ); // set new settings TQPtrListIterator it( _mixerBackend->m_mixDevices ); for(MixDevice *md=it.toFirst(); md!=0; md=++it ) { // kdDebug(67100) << "Mixer::volumeLoad() writeVolumeToHW(" << md->num() << ", "<< md->getVolume() << ")" << endl; // !! @todo Restore record source //setRecordSource( md->num(), md->isRecSource() ); _mixerBackend->setRecsrcHW( md->num(), md->isRecSource() ); _mixerBackend->writeVolumeToHW( md->num(), md->getVolume() ); if ( md->isEnum() ) _mixerBackend->setEnumIdHW( md->num(), md->enumId() ); } } /** * Opens the mixer. * Also, starts the polling timer, for polling the Volumes from the Mixer. * * @return 0, if OK. An Mixer::ERR_ error code otherwise */ int Mixer::open() { int err = _mixerBackend->open(); // A better ID is now calculated in mixertoolbox.cpp, and set via setID(), // but we want a somhow usable fallback just in case. _id = mixerName(); if( err == ERR_INCOMPATIBLESET ) // !!! When does this happen ?!? { // Clear the mixdevices list _mixerBackend->m_mixDevices.clear(); // try again with fresh set err = _mixerBackend->open(); } MixDevice* recommendedMaster = _mixerBackend->recommendedMaster(); if ( recommendedMaster != 0 ) { setMasterDevice(recommendedMaster->getPK() ); } else { kdError(67100) << "Mixer::open() no master detected." << endl; TQString noMaster = "---no-master-detected---"; setMasterDevice(noMaster); // no master } /* // --------- Copy the hardware values to the MixDevice ------------------- MixSet &mset = _mixerBackend->m_mixDevices; if( !mset.isEmpty() ) { // Copy the initial mix set // kdDebug(67100) << "Mixer::setupMixer() copy Set" << endl; MixDevice* md; for( md = mset.first(); md != 0; md = mset.next() ) { MixDevice* mdCopy = _mixerBackend->m_mixDevices.first(); while( mdCopy!=0 && mdCopy->num() != md->num() ) { mdCopy = _mixerBackend->m_mixDevices.next(); } if ( mdCopy != 0 ) { // The "mdCopy != 0" was not checked before, but its safer to do so setRecordSource( md->num(), md->isRecSource() ); Volume &vol = mdCopy->getVolume(); vol.setVolume( md->getVolume() ); mdCopy->setMuted( md->isMuted() ); // !! might need writeVolumeToHW( mdCopy->num(), mdCopy->getVolume() ); } } } */ if ( _mixerBackend->needsPolling() ) { _pollingTimer->start(50); } else { _mixerBackend->prepareSignalling(this); // poll once to give the GUI a chance to rebuild it's info TQTimer::singleShot( 50, this, TQT_SLOT( readSetFromHW() ) ); } return err; } /** * Closes the mixer. * Also, stops the polling timer. * * @return 0 (always) */ int Mixer::close() { _pollingTimer->stop(); return _mixerBackend->close(); } /* ------- WRAPPER METHODS. START ------------------------------ */ unsigned int Mixer::size() const { return _mixerBackend->m_mixDevices.count(); } MixDevice* Mixer::operator[](int num) { MixDevice* md = _mixerBackend->m_mixDevices.at( num ); Q_ASSERT( md ); return md; } MixSet Mixer::getMixSet() { return _mixerBackend->m_mixDevices; } bool Mixer::isValid() { return _mixerBackend->isValid(); } bool Mixer::isOpen() const { if ( _mixerBackend == 0 ) return false; else return _mixerBackend->isOpen(); } /* ------- WRAPPER METHODS. END -------------------------------- */ /** * After calling this, readSetFromHW() will do a complete update. This will * trigger emitting the appropriate signals like newVolumeLevels(). * * This method is useful, if you need to get a "refresh signal" - used at: * 1) Start of KMix - so that we can be sure an initial signal is emitted * 2) When reconstructing any MixerWidget (e.g. DockIcon after applying preferences) */ void Mixer::readSetFromHWforceUpdate() const { _readSetFromHWforceUpdate = true; } /** You can call this to retrieve the freshest information from the mixer HW. This method is also called regulary by the mixer timer. */ void Mixer::readSetFromHW() { if ( ! _mixerBackend->isOpen() ) { // bail out immediately, if the mixer is not open. // This can happen currently only, if the user executes the DCOP close() call. return; } bool updated = _mixerBackend->prepareUpdateFromHW(); if ( (! updated) && (! _readSetFromHWforceUpdate) ) { // Some drivers (ALSA) are smart. We don't need to run the following // time-consuming update loop if there was no change return; } _readSetFromHWforceUpdate = false; MixDevice* md; for( md = _mixerBackend->m_mixDevices.first(); md != 0; md = _mixerBackend->m_mixDevices.next() ) { Volume& vol = md->getVolume(); _mixerBackend->readVolumeFromHW( md->num(), vol ); md->setRecSource( _mixerBackend->isRecsrcHW( md->num() ) ); if (md->isEnum() ) { md->setEnumId( _mixerBackend->enumIdHW(md->num()) ); } } // Trivial implementation. Without looking at the devices // kdDebug(67100) << "Mixer::readSetFromHW(): emit newVolumeLevels()" << endl; emit newVolumeLevels(); emit newRecsrc(); // cheap, but works } void Mixer::setBalance(int balance) { // !! BAD, because balance only works on the master device. If you have not Master, the slider is a NOP if( balance == m_balance ) { // balance unchanged => return return; } m_balance = balance; MixDevice* master = masterDevice(); if ( master == 0 ) { // no master device available => return return; } Volume& vol = master->getVolume(); _mixerBackend->readVolumeFromHW( master->num(), vol ); int left = vol[ Volume::LEFT ]; int right = vol[ Volume::RIGHT ]; int refvol = left > right ? left : right; if( balance < 0 ) // balance left { vol.setVolume( Volume::LEFT, refvol); vol.setVolume( Volume::RIGHT, (balance * refvol) / 100 + refvol ); } else { vol.setVolume( Volume::LEFT, -(balance * refvol) / 100 + refvol ); vol.setVolume( Volume::RIGHT, refvol); } _mixerBackend->writeVolumeToHW( master->num(), vol ); emit newBalance( vol ); } TQString Mixer::mixerName() { return _mixerBackend->m_mixerName; } int Mixer::devnum() { return _mixerBackend->m_devnum; } TQString Mixer::driverName( int driver ) { getDriverNameFunc *f = g_mixerFactories[driver].getDriverName; if( f!=0 ) return f(); else return "unknown"; } void Mixer::setID(TQString& ref_id) { _id = ref_id; } TQString& Mixer::id() { return _id; } void Mixer::setMasterCard(const TQString& ref_id) { // The value is taken over without checking on existance. This allows the User to define // a MasterCard that is not always available (e.g. it is an USB hotplugging device). // Also you can set the master at any time you like, e.g. after reading the KMix configuration file // and before actually constructing the Mixer instances (hint: this mehtod is static!). _masterCard = ref_id; } Mixer* Mixer::masterCard() { kdDebug(67100) << "Mixer::masterCard() searching for id=" << _masterCard << "\n"; for (Mixer *mixer = Mixer::mixers().first(); mixer; mixer = Mixer::mixers().next()) { if ( mixer->id() == _masterCard ) { #ifdef MIXER_MASTER_DEBUG kdDebug(67100) << "Mixer::masterCard() found id=" << mixer->id() << "\n"; #endif return mixer; } } #ifdef MIXER_MASTER_DEBUG kdDebug(67100) << "Mixer::masterCard() found no Mixer* mixer \n"; #endif return NULL; } void Mixer::setMasterCardDevice(const TQString &ref_id) { // The value is taken over without checking on existance. This allows the User to define // a MasterCard that is not always available (e.g. it is an USB hotplugging device). // Also you can set the master at any time you like, e.g. after reading the KMix configuration file // and before actually constructing the Mixer instances (hint: this mehtod is static!). _masterCardDevice = ref_id; #ifdef MIXER_MASTER_DEBUG kdDebug(67100) << "Mixer::setMasterCardDevice(\"" << ref_id << "\")\n"; #endif } MixDevice* Mixer::masterCardDevice() { MixDevice* md = 0; Mixer *mixer = masterCard(); if ( mixer != 0 ) { for( md = mixer->_mixerBackend->m_mixDevices.first(); md != 0; md = mixer->_mixerBackend->m_mixDevices.next() ) { if ( md->getPK() == _masterCardDevice ) { #ifdef MIXER_MASTER_DEBUG kdDebug(67100) << "Mixer::masterCardDevice() getPK()=" << md->getPK() << " , _masterCardDevice=" << _masterCardDevice << "\n"; #endif break; } } } #ifdef MIXER_MASTER_DEBUG if ( md == 0) kdDebug(67100) << "Mixer::masterCardDevice() found no MixDevice* md" "\n"; #endif return md; } /** Used internally by the Mixer class and as DCOP method */ void Mixer::setRecordSource( int devnum, bool on ) { if( !_mixerBackend->setRecsrcHW( devnum, on ) ) // others have to be updated { for( MixDevice* md = _mixerBackend->m_mixDevices.first(); md != 0; md = _mixerBackend->m_mixDevices.next() ) { bool isRecsrc = _mixerBackend->isRecsrcHW( md->num() ); // kdDebug(67100) << "Mixer::setRecordSource(): isRecsrcHW(" << md->num() << ") =" << isRecsrc << endl; md->setRecSource( isRecsrc ); } // emitting is done after read //emit newRecsrc(); // like "emit newVolumeLevels()", but for record source } else { // just the actual mixdevice for( MixDevice* md = _mixerBackend->m_mixDevices.first(); md != 0; md = _mixerBackend->m_mixDevices.next() ) { if( md->num() == devnum ) { bool isRecsrc = _mixerBackend->isRecsrcHW( md->num() ); md->setRecSource( isRecsrc ); } } // emitting is done after read //emit newRecsrc(); // like "emit newVolumeLevels()", but for record source } } MixDevice* Mixer::masterDevice() { return find( _masterDevicePK ); } void Mixer::setMasterDevice(TQString &devPK) { _masterDevicePK = devPK; } MixDevice* Mixer::find(TQString& devPK) { MixDevice* md = 0; for( md = _mixerBackend->m_mixDevices.first(); md != 0; md = _mixerBackend->m_mixDevices.next() ) { if( devPK == md->getPK() ) { break; } } return md; } MixDevice *Mixer::mixDeviceByType( int deviceidx ) { unsigned int i=0; while (inum()!=deviceidx) i++; if (i==size()) return 0; return (*this)[i]; } // @dcop // Used also by the setMasterVolume() method. void Mixer::setVolume( int deviceidx, int percentage ) { MixDevice *mixdev= mixDeviceByType( deviceidx ); if (!mixdev) return; Volume vol=mixdev->getVolume(); // @todo The next call doesn't handle negative volumes correctly. vol.setAllVolumes( (percentage*vol.maxVolume())/100 ); _mixerBackend->writeVolumeToHW(deviceidx, vol); } /** Call this if you have a *reference* to a Volume object and have modified that locally. Pass the MixDevice associated to that Volume to this method for writing back the changed value to the mixer. Hint: Why do we do it this way? - It is fast (no copying of Volume objects required) - It is easy to understand ( read - modify - commit ) */ void Mixer::commitVolumeChange( MixDevice* md ) { _mixerBackend->writeVolumeToHW(md->num(), md->getVolume() ); _mixerBackend->setEnumIdHW(md->num(), md->enumId() ); } // @dcop only void Mixer::setMasterVolume( int percentage ) { MixDevice *master = masterDevice(); if (master != 0 ) { setVolume( master->num(), percentage ); } } // @dcop int Mixer::volume( int deviceidx ) { MixDevice *mixdev= mixDeviceByType( deviceidx ); if (!mixdev) return 0; Volume vol=mixdev->getVolume(); // @todo This will not work, if minVolume != 0 !!! // e.g.: minVolume=5 or minVolume=-10 // The solution is to check two cases: // volume < 0 => use minVolume for volumeRange // volume > 0 => use maxVolume for volumeRange // If chosen volumeRange==0 => return 0 // As this is potentially used often (Sliders, ...), it // should beimplemented in the Volume class. // For now we go with "maxVolume()", like in the rest of KMix. long volumeRange = vol.maxVolume(); // -vol.minVolume() ; if ( volumeRange == 0 ) { return 0; } else { return ( vol.getVolume( Volume::LEFT )*100) / volumeRange ; } } // @dcop , especially for use in KMilo void Mixer::setAbsoluteVolume( int deviceidx, long absoluteVolume ) { MixDevice *mixdev= mixDeviceByType( deviceidx ); if (!mixdev) return; Volume vol=mixdev->getVolume(); vol.setAllVolumes( absoluteVolume ); _mixerBackend->writeVolumeToHW(deviceidx, vol); } // @dcop , especially for use in KMilo long Mixer::absoluteVolume( int deviceidx ) { MixDevice *mixdev= mixDeviceByType( deviceidx ); if (!mixdev) return 0; Volume vol=mixdev->getVolume(); long avgVolume=vol.getAvgVolume((Volume::ChannelMask)(Volume::MLEFT | Volume::MRIGHT)); return avgVolume; } // @dcop , especially for use in KMilo long Mixer::absoluteVolumeMax( int deviceidx ) { MixDevice *mixdev= mixDeviceByType( deviceidx ); if (!mixdev) return 0; Volume vol=mixdev->getVolume(); long maxVolume=vol.maxVolume(); return maxVolume; } // @dcop , especially for use in KMilo long Mixer::absoluteVolumeMin( int deviceidx ) { MixDevice *mixdev= mixDeviceByType( deviceidx ); if (!mixdev) return 0; Volume vol=mixdev->getVolume(); long minVolume=vol.minVolume(); return minVolume; } // @dcop int Mixer::masterVolume() { int vol = 0; MixDevice *master = masterDevice(); if (master != 0 ) { vol = volume( master->num() ); } return vol; } // @dcop void Mixer::increaseVolume( int deviceidx ) { MixDevice *mixdev= mixDeviceByType( deviceidx ); if (mixdev != 0) { Volume vol=mixdev->getVolume(); double fivePercent = (vol.maxVolume()-vol.minVolume()+1) / 20; for (unsigned int i=Volume::CHIDMIN; i <= Volume::CHIDMAX; i++) { int volToChange = vol.getVolume((Volume::ChannelID)i); if ( fivePercent < 1 ) fivePercent = 1; volToChange += (int)fivePercent; vol.setVolume((Volume::ChannelID)i, volToChange); } _mixerBackend->writeVolumeToHW(deviceidx, vol); } /* see comment at the end of decreaseVolume() int vol=volume(deviceidx); setVolume(deviceidx, vol+5); */ } // @dcop void Mixer::decreaseVolume( int deviceidx ) { MixDevice *mixdev= mixDeviceByType( deviceidx ); if (mixdev != 0) { Volume vol=mixdev->getVolume(); double fivePercent = (vol.maxVolume()-vol.minVolume()+1) / 20; for (unsigned int i=Volume::CHIDMIN; i <= Volume::CHIDMAX; i++) { int volToChange = vol.getVolume((Volume::ChannelID)i); //std::cout << "Mixer::decreaseVolume(): before: volToChange " <setMuted( on ); _mixerBackend->writeVolumeToHW(deviceidx, mixdev->getVolume() ); } // @dcop only void Mixer::setMasterMute( bool on ) { MixDevice *master = masterDevice(); if (master != 0 ) { setMute( master->num(), on ); } } // @dcop void Mixer::toggleMute( int deviceidx ) { MixDevice *mixdev= mixDeviceByType( deviceidx ); if (!mixdev) return; bool previousState= mixdev->isMuted(); mixdev->setMuted( !previousState ); _mixerBackend->writeVolumeToHW(deviceidx, mixdev->getVolume()); // Muting/unmuting PulseAudio directly does not send back any notification to the mixer // so we make sure we always update the tray icon after each operation. readSetFromHWforceUpdate(); TQTimer::singleShot(50, this, TQT_SLOT(readSetFromHW())); } // @dcop only void Mixer::toggleMasterMute() { MixDevice *master = masterDevice(); if (master != 0 ) { toggleMute( master->num() ); } } // @dcop bool Mixer::mute( int deviceidx ) { MixDevice *mixdev= mixDeviceByType( deviceidx ); if (!mixdev) return true; return mixdev->isMuted(); } // @dcop only bool Mixer::masterMute() { MixDevice *master = masterDevice(); if (master != 0 ) { return mute( master->num() ); } return true; } // @dcop only int Mixer::masterDeviceIndex() { return masterDevice()->num(); } bool Mixer::isRecordSource( int deviceidx ) { MixDevice *mixdev= mixDeviceByType( deviceidx ); if (!mixdev) return false; return mixdev->isRecSource(); } /// @DCOP WHAT DOES THIS METHOD?!?!? bool Mixer::isAvailableDevice( int deviceidx ) { return mixDeviceByType( deviceidx ); } #include "mixer.moc"