/***************************************************************************
* Copyright ( C ) 2005 Paul Cifarelli *
* *
* 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 ) any later version . *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include <tqthread.h>
# include <sys/param.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <unistd.h>
# include <climits>
# include <cmath>
# include <stdarg.h>
# include <cstring>
# include <config.h>
# include <iostream>
# include "debug.h"
# include <tdelocale.h>
# include <tdemessagebox.h>
# include <tqapplication.h>
# include <tqdir.h>
# include <tqstringlist.h>
# include "helix-engine.h"
# include "helix-configdialog.h"
# include "config/helixconfig.h"
# include "helix-errors.h"
# include "helix-sp.h"
# include "hxplayercontrol.h"
# include "amarokconfig.h"
AMAROK_EXPORT_PLUGIN ( HelixEngine )
# define DEBUG_PREFIX "helix-engine"
using namespace std ;
extern " C "
{
# include <unistd.h>
}
# define HELIX_ENGINE_TIMER 10 // 10 ms timer
# define SCOPE_MAX_BEHIND 200 // 200 postmix buffers
# ifndef LLONG_MAX
# define LLONG_MAX 9223372036854775807LL
# endif
///returns the configuration we will use
static inline TQCString configPath ( ) { return TQFile : : encodeName ( TQDir : : homeDirPath ( ) + " /.helix/config " ) ; }
HelixEngine : : HelixEngine ( )
: EngineBase ( ) , PlayerControl ( ) ,
m_state ( Engine : : Empty ) ,
m_coredir ( HELIX_LIBS " /common " ) ,
m_pluginsdir ( HELIX_LIBS " /plugins " ) ,
m_codecsdir ( HELIX_LIBS " /codecs " ) ,
m_inited ( false ) ,
m_scopeplayerlast ( 0 ) ,
m_sfps ( 0.0 ) ,
m_scopedelta ( 0 ) ,
m_sframes ( 0 ) ,
m_lframes ( 0 )
{
addPluginProperty ( " HasConfigure " , " true " ) ;
addPluginProperty ( " HasEqualizer " , " true " ) ;
addPluginProperty ( " HasCrossfade " , " true " ) ;
addPluginProperty ( " HasCDDA " , " false " ) ;
memset ( & m_md , 0 , sizeof ( m_md ) ) ;
memset ( hscope , 0 , 2 * sizeof ( HelixScope ) ) ;
memset ( & m_scopetm , 0 , sizeof ( struct timeval ) ) ;
memset ( m_pfade , 0 , 2 * sizeof ( FadeTrack ) ) ;
}
HelixEngine : : ~ HelixEngine ( )
{
m_mimes . clear ( ) ;
}
int HelixEngine : : print2stdout ( const char * fmt , . . . )
{
va_list args ;
char buf [ 1024 ] ;
va_start ( args , fmt ) ;
int ret = vsprintf ( buf , fmt , args ) ;
debug ( ) < < buf ;
va_end ( args ) ;
return ret ;
}
int HelixEngine : : print2stderr ( const char * fmt , . . . )
{
va_list args ;
char buf [ 1024 ] ;
va_start ( args , fmt ) ;
int ret = vsprintf ( buf , fmt , args ) ;
debug ( ) < < buf ;
va_end ( args ) ;
return ret ;
}
void HelixEngine : : notifyUser ( unsigned long code , const char * moreinfo , const char * moreinfourl )
{
TQString * err = HelixErrors : : errorText ( code ) ;
if ( err )
emit statusText ( i18n ( " Helix Core returned error: %1 %2 %3 " ) . arg ( TQString ( * err ) ) . arg ( TQString ( moreinfo ) ) . arg ( TQString ( moreinfourl ) ) ) ;
else
emit statusText ( i18n ( " Helix Core returned error: <unknown> " ) ) ;
}
void HelixEngine : : interruptUser ( unsigned long code , const char * moreinfo , const char * moreinfourl )
{
TQString * err = HelixErrors : : errorText ( code ) ;
if ( err )
emit infoMessage ( i18n ( " Helix Core returned error: %1 %1 %1 " ) . arg ( TQString ( * err ) ) . arg ( TQString ( moreinfo ) ) . arg ( TQString ( moreinfourl ) ) ) ;
else
emit infoMessage ( i18n ( " Helix Core returned error: <unknown> " ) ) ;
// since this is a serious error, emit trackEnded so amarok knows to move on
play_finished ( m_current ) ;
}
void HelixEngine : : onContacting ( const char * host )
{
emit statusText ( i18n ( " Contacting: %1 " ) . arg ( TQString ( host ) ) ) ;
}
void HelixEngine : : onBuffering ( int pcnt )
{
if ( pcnt ! = 100 ) // let's not report that...
emit statusText ( i18n ( " Buffering %1% " ) . arg ( pcnt ) ) ;
}
Amarok : : PluginConfig *
HelixEngine : : configure ( ) const
{
debug ( ) < < " Starting HelixConfigDialog \n " ;
return new HelixConfigDialog ( ( HelixEngine * ) this ) ;
}
int HelixEngine : : fallbackToOSS ( )
{
KMessageBox : : information ( 0 , i18n ( " The helix library you have configured does not support ALSA, the helix-engine has fallen back to OSS " ) ) ;
debug ( ) < < " Falling back to OSS \n " ;
return ( HelixConfigDialog : : setSoundSystem ( ( int ) HelixSimplePlayer : : OSS ) ) ;
}
bool
HelixEngine : : init ( )
{
debug ( ) < < " Initializing HelixEngine \n " ;
struct stat s ;
bool exists = false ;
stop ( ) ;
m_state = Engine : : Empty ;
m_numPlayers = 2 ;
m_current = 1 ;
m_coredir = HelixConfig : : coreDirectory ( ) ;
if ( m_coredir . isEmpty ( ) )
m_coredir = HELIX_LIBS " /common " ;
m_pluginsdir = HelixConfig : : pluginDirectory ( ) ;
if ( m_pluginsdir . isEmpty ( ) )
m_pluginsdir = HELIX_LIBS " /plugins " ;
m_codecsdir = HelixConfig : : codecsDirectory ( ) ;
if ( m_codecsdir . isEmpty ( ) )
m_codecsdir = HELIX_LIBS " /codecs " ;
if ( HelixConfig : : outputplugin ( ) = = " oss " )
setOutputSink ( HelixSimplePlayer : : OSS ) ;
else
{
setOutputSink ( HelixSimplePlayer : : ALSA ) ;
if ( HelixConfig : : deviceenabled ( ) )
setDevice ( HelixConfig : : device ( ) . utf8 ( ) ) ;
else
setDevice ( " default " ) ;
}
if ( ! stat ( m_coredir . utf8 ( ) , & s ) & & ! stat ( m_pluginsdir . utf8 ( ) , & s ) & & ! stat ( m_codecsdir . utf8 ( ) , & s ) )
{
long vol = 0 ;
bool eqenabled = false ;
int savedpreamp = 0 ;
TQValueList < int > savedequalizerGains ;
if ( m_inited )
{
vol = PlayerControl : : getVolume ( ) ;
eqenabled = PlayerControl : : isEQenabled ( ) ;
for ( unsigned int i = 0 ; i < m_equalizerGains . size ( ) ; i + + )
savedequalizerGains . append ( m_equalizerGains [ i ] ) ;
savedpreamp = m_preamp ;
PlayerControl : : tearDown ( ) ;
}
PlayerControl : : init ( m_coredir . utf8 ( ) , m_pluginsdir . utf8 ( ) , m_codecsdir . utf8 ( ) , 2 ) ;
if ( PlayerControl : : initDirectSS ( ) )
{
fallbackToOSS ( ) ;
PlayerControl : : initDirectSS ( ) ;
}
if ( m_inited )
{
PlayerControl : : setVolume ( vol ) ;
setEqualizerEnabled ( eqenabled ) ;
setEqualizerParameters ( savedpreamp , savedequalizerGains ) ;
}
m_inited = exists = true ;
}
if ( ! exists | | PlayerControl : : getError ( ) )
{
KMessageBox : : error ( 0 , i18n ( " The Helix Engine requires the RealPlayer(tm) or HelixPlayer libraries to be installed. Please make sure one is installed, and adjust the paths in \" Amarok Settings \" -> \" Engine \" " ) ) ;
// we need to return true here so that the user has an oppportunity to change the directory
//return false;
return true ;
}
// create a list of mime types and ext for use in canDecode()
m_mimes . resize ( getMimeListLen ( ) ) ;
int i = 0 ;
const MimeList * ml = getMimeList ( ) ;
MimeEntry * entry ;
while ( ml )
{
TQString mt = ml - > mimetypes ;
TQString me = ml - > mimeexts ;
entry = new MimeEntry ;
entry - > type = TQStringList : : split ( ' | ' , mt ) ;
entry - > ext = TQStringList : : split ( ' | ' , me ) ;
m_mimes [ i ] = * entry ;
debug ( ) < < ml - > mimetypes < < endl ;
i + + ;
ml = ml - > fwd ;
}
debug ( ) < < " Succussful init \n " ;
return true ;
}
bool
HelixEngine : : load ( const KURL & url , bool isStream )
{
debug ( ) < < " In load " < < url . url ( ) < < endl ;
if ( ! m_inited )
return false ;
if ( ! canDecode ( url ) )
{
const TQString path = url . path ( ) ;
const TQString ext = path . mid ( path . findRev ( ' . ' ) + 1 ) . lower ( ) ;
emit statusText ( i18n ( " No plugin found for the %1 format " ) . arg ( ext ) ) ;
return false ;
}
debug ( ) < < " xfadeLength is " < < m_xfadeLength < < endl ;
if ( m_xfadeLength > 0 & & m_state = = Engine : : Playing & & ! isStream & &
( m_xfadeNextTrack | | //set by engine controller when switching tracks automatically
( uint ) AmarokConfig : : crossfadeType ( ) = = 0 | | //crossfade always
( uint ) AmarokConfig : : crossfadeType ( ) = = 2 ) ) //crossfade when switching tracks manually)
{
//set m_xfadeNextTrack true here regardless to play() will work correctly; disable in there
m_xfadeNextTrack = true ;
int nextPlayer = m_current ? 0 : 1 ;
// prepare the next player
PlayerControl : : stop ( nextPlayer ) ;
resetScope ( nextPlayer ) ;
memset ( & hscope [ nextPlayer ] , 0 , sizeof ( HelixScope ) ) ;
memset ( & m_pfade [ nextPlayer ] , 0 , sizeof ( FadeTrack ) ) ;
if ( isPlaying ( m_current ) )
{
m_pfade [ m_current ] . m_fadeactive = true ;
m_pfade [ m_current ] . m_startfadetime = PlayerControl : : where ( m_current ) ;
setFadeout ( true , m_xfadeLength , m_current ) ;
}
Engine : : Base : : load ( url , false ) ; // we don't crossfade streams ?? do we load the base here ??
PlayerControl : : setURL ( TQFile : : encodeName ( url . url ( ) ) , nextPlayer , ! isStream ) ;
m_isStream = false ;
}
else
cleanup ( ) ;
m_isStream = isStream ;
int nextPlayer ;
nextPlayer = m_current ? 0 : 1 ;
Engine : : Base : : load ( url , isStream | | url . protocol ( ) = = " http " ) ;
m_state = Engine : : Idle ;
emit stateChanged ( Engine : : Idle ) ;
m_url = url ;
if ( url . isLocalFile ( ) )
PlayerControl : : setURL ( TQFile : : encodeName ( url . url ( ) ) , nextPlayer , ! m_isStream ) ;
else
{
m_isStream = true ;
PlayerControl : : setURL ( TQFile : : encodeName ( url . url ( ) ) , nextPlayer , ! m_isStream ) ;
}
return true ;
}
bool
HelixEngine : : play ( uint offset )
{
debug ( ) < < " In play " < < endl ;
int nextPlayer ;
if ( ! m_inited )
return false ;
if ( m_state ! = Engine : : Playing )
{
struct timezone tz ;
memset ( & tz , 0 , sizeof ( struct timezone ) ) ;
gettimeofday ( & m_scopetm , & tz ) ;
startTimer ( HELIX_ENGINE_TIMER ) ;
}
nextPlayer = m_current ? 0 : 1 ;
if ( m_xfadeLength & & m_xfadeNextTrack & & ! offset & & isPlaying ( m_current ) )
{
m_xfadeNextTrack = false ;
PlayerControl : : start ( nextPlayer , true , m_xfadeLength ) ;
}
else
PlayerControl : : start ( nextPlayer ) ;
if ( offset )
PlayerControl : : seek ( offset , nextPlayer ) ;
if ( ! PlayerControl : : getError ( ) )
{
if ( m_state ! = Engine : : Playing )
{
m_state = Engine : : Playing ;
emit stateChanged ( Engine : : Playing ) ;
}
m_current = nextPlayer ;
return true ;
}
cleanup ( ) ;
m_state = Engine : : Empty ;
emit stateChanged ( Engine : : Empty ) ;
return false ;
}
void
HelixEngine : : cleanup ( )
{
if ( ! m_inited )
return ;
m_url = KURL ( ) ;
PlayerControl : : stop ( ) ; // stop all players
resetScope ( 0 ) ;
resetScope ( 1 ) ;
killTimers ( ) ;
m_isStream = false ;
memset ( & m_md , 0 , sizeof ( m_md ) ) ;
memset ( hscope , 0 , 2 * sizeof ( HelixScope ) ) ;
memset ( m_pfade , 0 , 2 * sizeof ( FadeTrack ) ) ;
}
void
HelixEngine : : stop ( )
{
if ( ! m_inited )
return ;
debug ( ) < < " In stop where= " < < where ( m_current ) < < " duration= " < < duration ( m_current ) < < endl ;
if ( AmarokConfig : : fadeout ( ) & & ! m_pfade [ m_current ] . m_fadeactive & & state ( ) = = Engine : : Playing )
{
debug ( ) < < " fading out... \n " ;
m_state = Engine : : Empty ;
emit stateChanged ( Engine : : Empty ) ; // tell the controller not to bother you anymore
m_pfade [ m_current ] . m_fadeactive = true ;
m_pfade [ m_current ] . m_stopfade = true ;
m_pfade [ m_current ] . m_startfadetime = PlayerControl : : where ( m_current ) ;
setFadeout ( true , AmarokConfig : : fadeoutLength ( ) , m_current ) ;
}
else
{
debug ( ) < < " Stopping immediately \n " ;
cleanup ( ) ;
cleanUpStream ( m_current ) ;
m_state = Engine : : Empty ;
emit stateChanged ( m_state ) ;
}
}
void HelixEngine : : play_finished ( int playerIndex )
{
debug ( ) < < " Ok, finished playing the track \n " ;
cleanUpStream ( playerIndex ) ;
resetScope ( playerIndex ) ;
memset ( & hscope [ playerIndex ] , 0 , sizeof ( HelixScope ) ) ;
memset ( & m_pfade [ playerIndex ] , 0 , sizeof ( FadeTrack ) ) ;
if ( playerIndex = = m_current & & ! m_pfade [ playerIndex ] . m_stopfade & & ! m_pfade [ playerIndex ] . m_fadeactive )
{
m_state = Engine : : Idle ;
emit stateChanged ( m_state ) ;
emit trackEnded ( ) ;
}
}
void
HelixEngine : : pause ( )
{
if ( ! m_inited )
return ;
// TODO: PAUSE in XFADE
debug ( ) < < " In pause \n " ;
if ( m_state = = Engine : : Playing )
{
PlayerControl : : pause ( m_current ) ;
m_state = Engine : : Paused ;
emit stateChanged ( Engine : : Paused ) ;
}
}
void
HelixEngine : : unpause ( )
{
if ( ! m_inited )
return ;
// TODO: PAUSE in XFADE
debug ( ) < < " In unpause \n " ;
if ( m_state = = Engine : : Paused )
{
PlayerControl : : resume ( m_current ) ;
m_state = Engine : : Playing ;
emit stateChanged ( Engine : : Playing ) ;
}
}
Engine : : State
HelixEngine : : state ( ) const
{
//debug() << "In state, state is " << m_state << endl;
if ( ! m_inited | | m_url . isEmpty ( ) )
return ( Engine : : Empty ) ;
return m_state ;
}
uint
HelixEngine : : position ( ) const
{
if ( ! m_inited )
return 0 ;
return PlayerControl : : where ( m_current ) ;
}
uint
HelixEngine : : length ( ) const
{
if ( ! m_inited )
return 0 ;
return PlayerControl : : duration ( m_current ) ;
}
void
HelixEngine : : seek ( uint ms )
{
if ( ! m_inited )
return ;
debug ( ) < < " In seek \n " ;
resetScope ( 0 ) ;
resetScope ( 1 ) ;
PlayerControl : : seek ( ms , m_current ) ;
}
void
HelixEngine : : setVolumeSW ( uint vol )
{
if ( ! m_inited )
return ;
debug ( ) < < " In setVolumeSW \n " ;
PlayerControl : : setVolume ( vol ) ; // set the volume in all players!
}
bool
HelixEngine : : canDecode ( const KURL & url ) const
{
if ( ! m_inited )
return false ;
debug ( ) < < " In canDecode " < < url . prettyURL ( ) < < endl ;
if ( url . protocol ( ) = = " http " | | url . protocol ( ) = = " rtsp " )
return true ;
const TQString path = url . path ( ) ;
const TQString ext = path . mid ( path . findRev ( ' . ' ) + 1 ) . lower ( ) ;
if ( ext ! = " txt " )
for ( int i = 0 ; i < ( int ) m_mimes . size ( ) ; i + + )
{
if ( m_mimes [ i ] . type . grep ( " audio " ) . count ( ) | |
m_mimes [ i ] . type . grep ( " video " ) . count ( ) | |
m_mimes [ i ] . type . grep ( " application " ) . count ( ) )
if ( m_mimes [ i ] . ext . grep ( ext ) . count ( ) )
{
return true ;
}
}
return false ;
}
void
HelixEngine : : timerEvent ( TQTimerEvent * )
{
PlayerControl : : dispatch ( ) ; // dispatch the players
if ( m_xfadeLength < = 0 & & m_state = = Engine : : Playing & & PlayerControl : : done ( m_current ) )
play_finished ( m_current ) ;
else if ( m_xfadeLength > 0 | | AmarokConfig : : fadeout ( ) )
{
if ( m_state = = Engine : : Playing & & isPlaying ( m_current ? 0 : 1 ) & & PlayerControl : : done ( m_current ? 0 : 1 ) )
hscope [ m_current ? 0 : 1 ] . m_lasttime = 0 ;
// fade on stop finished
if ( m_pfade [ m_current ] . m_stopfade & & m_pfade [ m_current ] . m_fadeactive & &
( PlayerControl : : where ( m_current ) > m_pfade [ m_current ] . m_startfadetime + ( unsigned ) AmarokConfig : : fadeoutLength ( ) | |
PlayerControl : : done ( m_current ) ) )
{
debug ( ) < < " Stop fade end \n " ;
stop ( ) ;
}
// crossfade finished
if ( m_pfade [ m_current ? 0 : 1 ] . m_fadeactive & &
PlayerControl : : where ( m_current ? 0 : 1 ) > m_pfade [ m_current ? 0 : 1 ] . m_startfadetime + ( unsigned ) m_xfadeLength )
play_finished ( m_current ? 0 : 1 ) ;
}
// prune the scope(s)
prune ( ) ;
struct timeval tm ;
struct timezone tz ;
memset ( & tz , 0 , sizeof ( struct timezone ) ) ;
gettimeofday ( & tm , & tz ) ;
m_scopedelta = ( tm . tv_sec - m_scopetm . tv_sec ) * 1000 + ( tm . tv_usec - m_scopetm . tv_usec ) / 1000 ; // ms
m_scopetm . tv_sec = tm . tv_sec ;
m_scopetm . tv_usec = tm . tv_usec ;
hscope [ m_current ] . m_lasttime + = m_scopedelta ;
HelixSimplePlayer : : metaData * md = getMetaData ( m_current ) ;
if ( m_isStream & &
( strcmp ( m_md . title , md - > title ) | | strcmp ( m_md . artist , md - > artist ) ) )
{
memcpy ( & m_md , md , sizeof ( m_md ) ) ;
debug ( ) < < " {Title}: " < < md - > title < < " {Artist}: " < < md - > artist < < " {Bitrate}: " < < md - > bitrate < < endl ;
/* Real Radio One (and Rhapsody?) streams have their own format, where title is:
* clipinfo : title = < title > | artist name = < artist > | Album name = < album > | Artist : Next artist = < next artist > | \
* ordinal = < some number > | duration = < in secs > | Track : Rhapsody Track Id = < some number >
*
* for all other streams helix sends the title of the song in the artist string .
* this prevents context lookup , so we split it here ( the artist and title are separated by a ' - '
* we ' ll put the ' title ' in album instead . . .
*/
Engine : : SimpleMetaBundle bndl ;
bndl . album = TQString : : fromUtf8 ( m_md . title ) ;
if ( bndl . album . startsWith ( TQString ( " clipinfo: " ) ) )
{
bndl . album = bndl . album . remove ( 0 , 9 ) ;
TQStringList sl = TQStringList : : split ( ' | ' , bndl . album ) ;
for ( TQStringList : : Iterator it = sl . begin ( ) ; it ! = sl . end ( ) ; + + it )
{
if ( ( * it ) . startsWith ( " title= " ) )
bndl . title = ( * it ) . section ( ' = ' , 1 , 1 ) ;
if ( ( * it ) . startsWith ( " artist name= " ) )
bndl . artist = ( * it ) . section ( ' = ' , 1 , 1 ) ;
if ( ( * it ) . startsWith ( " Album name= " ) )
bndl . album = ( * it ) . section ( ' = ' , 1 , 1 ) ;
if ( ( * it ) . startsWith ( " duration= " ) )
bndl . length = ( * it ) . section ( ' = ' , 1 , 1 ) ;
}
//debug() << "Title: " << bndl.title << endl;
//debug() << "Artist: " << bndl.artist << endl;
//debug() << "Album: " << bndl.album << endl;
//debug() << "length: " << bndl.length << endl;
}
else
{
char c , * tmp = strchr ( m_md . artist , ' - ' ) ;
if ( tmp )
{
tmp - - ;
c = * tmp ;
* tmp = ' \0 ' ;
bndl . artist = TQString : : fromUtf8 ( m_md . artist ) ;
* tmp = c ;
tmp + = 3 ;
bndl . title = TQString : : fromUtf8 ( tmp ) ;
bndl . album = TQString : : fromUtf8 ( m_md . title ) ;
}
else // just copy them as is...
{
bndl . title = TQString : : fromUtf8 ( m_md . title ) ;
bndl . artist = TQString : : fromUtf8 ( m_md . artist ) ;
}
}
bndl . bitrate = TQString : : number ( m_md . bitrate / 1000 ) ;
emit EngineBase : : metaData ( bndl ) ;
}
}
int HelixEngine : : prune ( )
{
int err = 0 ;
err | = prune ( 0 ) ;
err | = prune ( 1 ) ;
return err ;
}
int HelixEngine : : prune ( int playerIndex )
{
//
// this bit is to help us keep more accurate time than helix provides
/////////////////////////////////////////////////////////////////////
unsigned long hpos = PlayerControl : : where ( playerIndex ) ;
if ( hpos ! = hscope [ playerIndex ] . m_lastpos
& & hpos - hscope [ playerIndex ] . m_lastpos < hscope [ playerIndex ] . m_lasttime - hscope [ playerIndex ] . m_lastpos )
hscope [ playerIndex ] . m_lasttime = hpos ;
if ( hpos > hscope [ playerIndex ] . m_lasttime )
{
hscope [ playerIndex ] . m_w = hpos ;
hscope [ playerIndex ] . m_lasttime = hpos ;
}
else
hscope [ playerIndex ] . m_w = hscope [ playerIndex ] . m_lasttime ;
hscope [ playerIndex ] . m_lastpos = hpos ;
if ( getScopeCount ( playerIndex ) > SCOPE_MAX_BEHIND ) // protect against naughty streams
{
resetScope ( playerIndex ) ;
return 0 ;
}
if ( ! hscope [ playerIndex ] . m_w | | ! hscope [ playerIndex ] . m_item )
return 0 ;
// prune, unless the player is still starting
while ( hpos & & hscope [ playerIndex ] . m_item & & hscope [ playerIndex ] . m_w > hscope [ playerIndex ] . m_item - > etime )
{
//debug() << "pruning " << hpos << "," << hscope[playerIndex].m_w << "," << hscope[playerIndex].m_lasttime
// << "," << hscope[playerIndex].m_item->time << ":" << hscope[playerIndex].m_item->etime << endl;
if ( hscope [ playerIndex ] . m_item & & hscope [ playerIndex ] . m_item - > allocd )
delete hscope [ playerIndex ] . m_item ;
hscope [ playerIndex ] . m_item = getScopeBuf ( playerIndex ) ;
}
if ( ! hscope [ playerIndex ] . m_item )
return 0 ;
if ( hscope [ playerIndex ] . m_w < hscope [ playerIndex ] . m_item - > time ) // wait for the player to catchup
{
//debug() << "waiting for player to catchup " << hpos << "," << hscope[playerIndex].m_w << "," << hscope[playerIndex].m_lasttime
// << "," << hscope[playerIndex].m_item->time << ":" << hscope[playerIndex].m_item->etime << endl;
return 0 ;
}
return 1 ;
}
const Engine : : Scope & HelixEngine : : scope ( )
{
if ( isPlaying ( 0 ) & & isPlaying ( 1 ) ) // crossfading
{
if ( m_scopeplayerlast )
scope ( m_current ) ;
else
scope ( m_current ? 0 : 1 ) ;
m_scopeplayerlast = ! m_scopeplayerlast ;
}
else
scope ( m_current ) ;
return m_scope ;
}
int HelixEngine : : scope ( int playerIndex )
{
int i ;
unsigned long t ;
if ( ! m_inited )
return 0 ;
if ( ! hscope [ playerIndex ] . m_item & & ! peekScopeTime ( t , playerIndex ) )
hscope [ playerIndex ] . m_item = getScopeBuf ( playerIndex ) ;
if ( ! prune ( playerIndex ) )
return 0 ;
if ( hscope [ playerIndex ] . m_item - > nchan > 2 )
return 0 ;
int j , k = 0 ;
short int * pint ;
unsigned char b [ 4 ] ;
// calculate the starting offset into the buffer
int off = ( hscope [ playerIndex ] . m_item - > spb * ( hscope [ playerIndex ] . m_w - hscope [ playerIndex ] . m_item - > time ) /
( hscope [ playerIndex ] . m_item - > etime - hscope [ playerIndex ] . m_item - > time ) ) *
hscope [ playerIndex ] . m_item - > nchan * hscope [ playerIndex ] . m_item - > bps ;
k = off ;
while ( hscope [ playerIndex ] . m_item & & hscope [ playerIndex ] . m_scopeindex < SCOPESIZE )
{
while ( k < ( int ) hscope [ playerIndex ] . m_item - > len )
{
for ( j = 0 ; j < hscope [ playerIndex ] . m_item - > nchan ; j + + )
{
switch ( hscope [ playerIndex ] . m_item - > bps )
{
case 1 :
b [ 1 ] = 0 ;
b [ 0 ] = hscope [ playerIndex ] . m_item - > buf [ k ] ;
break ;
case 2 :
b [ 1 ] = hscope [ playerIndex ] . m_item - > buf [ k + 1 ] ;
b [ 0 ] = hscope [ playerIndex ] . m_item - > buf [ k ] ;
break ;
}
pint = ( short * ) & b [ 0 ] ;
if ( hscope [ playerIndex ] . m_item - > nchan = = 1 ) // duplicate mono samples
{
hscope [ playerIndex ] . m_currentScope [ hscope [ playerIndex ] . m_scopeindex ] = * pint ;
hscope [ playerIndex ] . m_scopeindex + + ;
hscope [ playerIndex ] . m_currentScope [ hscope [ playerIndex ] . m_scopeindex ] = * pint ;
hscope [ playerIndex ] . m_scopeindex + + ;
}
else
{
hscope [ playerIndex ] . m_currentScope [ hscope [ playerIndex ] . m_scopeindex ] = * pint ;
hscope [ playerIndex ] . m_scopeindex + + ;
}
k + = hscope [ playerIndex ] . m_item - > bps ;
}
if ( hscope [ playerIndex ] . m_scopeindex > = SCOPESIZE )
{
hscope [ playerIndex ] . m_scopeindex = SCOPESIZE ;
break ;
}
}
// as long as we know there's another buffer...otherwise we need to wait for another
if ( hscope [ playerIndex ] . m_scopeindex < SCOPESIZE )
{
if ( hscope [ playerIndex ] . m_item & & hscope [ playerIndex ] . m_item - > allocd )
delete hscope [ playerIndex ] . m_item ;
hscope [ playerIndex ] . m_item = getScopeBuf ( playerIndex ) ;
k = 0 ;
if ( ! hscope [ playerIndex ] . m_item )
return 0 ; // wait until there are some more buffers available
}
else
{
if ( k > = ( int ) hscope [ playerIndex ] . m_item - > len )
{
if ( hscope [ playerIndex ] . m_item & & hscope [ playerIndex ] . m_item - > allocd )
delete hscope [ playerIndex ] . m_item ;
hscope [ playerIndex ] . m_item = getScopeBuf ( playerIndex ) ;
}
break ; // done with the scope buffer, so hand it off
}
}
// ok, we must have a full buffer here, give it to the scope
for ( i = 0 ; i < SCOPESIZE ; i + + )
m_scope [ i ] = hscope [ playerIndex ] . m_currentScope [ i ] ;
hscope [ playerIndex ] . m_scopeindex = 0 ;
return 1 ;
}
void
HelixEngine : : resetScope ( int playerIndex )
{
if ( playerIndex > = 0 & & playerIndex < numPlayers ( ) )
{
// make sure the scope is clear of old buffers
clearScopeQ ( playerIndex ) ;
hscope [ playerIndex ] . m_scopeindex = 0 ;
if ( hscope [ playerIndex ] . m_item & & hscope [ playerIndex ] . m_item - > allocd )
delete hscope [ playerIndex ] . m_item ;
hscope [ playerIndex ] . m_w = 0 ;
hscope [ playerIndex ] . m_item = 0 ;
}
}
void
HelixEngine : : setEqualizerEnabled ( bool enabled ) //SLOT
{
enableEQ ( enabled ) ;
}
// ok, this is lifted from gst... but why mess with what works?
void
HelixEngine : : setEqualizerParameters ( int preamp , const TQValueList < int > & bandGains ) //SLOT
{
m_preamp = ( preamp + 100 ) / 2 ;
m_equalizerGains . resize ( bandGains . count ( ) ) ;
for ( uint i = 0 ; i < bandGains . count ( ) ; i + + )
m_equalizerGains [ i ] = ( * bandGains . at ( i ) + 100 ) / 2 ;
updateEQgains ( ) ;
}
namespace Debug
{
# undef helix_indent
TQCString helix_indent ;
}
# include "helix-engine.moc"