mirror of
https://github.com/LMMS/lmms.git
synced 2026-03-13 11:38:24 -04:00
git-svn-id: https://lmms.svn.sf.net/svnroot/lmms/trunk/lmms@134 0778d3d1-df1d-0410-868b-ea421aaaa00d
725 lines
15 KiB
C++
725 lines
15 KiB
C++
#ifndef SINGLE_SOURCE_COMPILE
|
|
|
|
/*
|
|
* mixer.cpp - audio-device-independent mixer for LMMS
|
|
*
|
|
* Copyright (c) 2004-2006 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
|
*
|
|
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
|
|
*
|
|
* 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.
|
|
*
|
|
* 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 (see COPYING); if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*
|
|
*/
|
|
|
|
|
|
#include <math.h>
|
|
|
|
#include "mixer.h"
|
|
#include "play_handle.h"
|
|
#include "song_editor.h"
|
|
#include "templates.h"
|
|
#include "envelope_and_lfo_widget.h"
|
|
#include "buffer_allocator.h"
|
|
#include "debug.h"
|
|
#include "config_mgr.h"
|
|
#include "audio_port.h"
|
|
#include "sample_play_handle.h"
|
|
#include "piano_roll.h"
|
|
#include "micro_timer.h"
|
|
|
|
#include "audio_device.h"
|
|
#include "midi_client.h"
|
|
|
|
// platform-specific audio-interface-classes
|
|
#include "audio_alsa.h"
|
|
#include "audio_jack.h"
|
|
#include "audio_oss.h"
|
|
#include "audio_sdl.h"
|
|
#include "audio_dummy.h"
|
|
|
|
// platform-specific midi-interface-classes
|
|
#include "midi_alsa_raw.h"
|
|
#include "midi_alsa_seq.h"
|
|
#include "midi_oss.h"
|
|
#include "midi_dummy.h"
|
|
|
|
|
|
|
|
sample_rate_t SAMPLE_RATES[QUALITY_LEVELS] = { 44100, 88200 } ;
|
|
|
|
|
|
mixer::mixer( engine * _engine ) :
|
|
QObject(),
|
|
engineObject( _engine ),
|
|
m_framesPerAudioBuffer( DEFAULT_BUFFER_SIZE ),
|
|
m_readBuf( NULL ),
|
|
m_writeBuf( NULL ),
|
|
m_cpuLoad( 0 ),
|
|
m_qualityLevel( DEFAULT_QUALITY_LEVEL ),
|
|
m_masterGain( 1.0f ),
|
|
m_audioDev( NULL ),
|
|
m_oldAudioDev( NULL ),
|
|
m_mixMutex(),
|
|
m_mixMutexLockLevel( 0 )
|
|
{
|
|
if( configManager::inst()->value( "mixer", "framesperaudiobuffer"
|
|
).toInt() >= 32 )
|
|
{
|
|
m_framesPerAudioBuffer = configManager::inst()->value( "mixer",
|
|
"framesperaudiobuffer" ).toInt();
|
|
}
|
|
else
|
|
{
|
|
configManager::inst()->setValue( "mixer",
|
|
"framesperaudiobuffer",
|
|
QString::number( m_framesPerAudioBuffer ) );
|
|
}
|
|
|
|
for( Uint8 i = 0; i < 3; i++ )
|
|
{
|
|
m_readBuf = bufferAllocator::alloc<surroundSampleFrame>(
|
|
m_framesPerAudioBuffer );
|
|
|
|
clearAudioBuffer( m_readBuf, m_framesPerAudioBuffer );
|
|
m_bufferPool.push_back( m_readBuf );
|
|
}
|
|
|
|
setClipScaling( FALSE );
|
|
}
|
|
|
|
|
|
|
|
|
|
mixer::~mixer()
|
|
{
|
|
delete m_audioDev;
|
|
delete m_midiClient;
|
|
|
|
for( Uint8 i = 0; i < 3; i++ )
|
|
{
|
|
bufferAllocator::free( m_bufferPool[i] );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void mixer::initDevices( void )
|
|
{
|
|
m_audioDev = tryAudioDevices();
|
|
m_midiClient = tryMIDIClients();
|
|
}
|
|
|
|
|
|
|
|
|
|
void mixer::startProcessing( void )
|
|
{
|
|
m_audioDev->startProcessing();
|
|
}
|
|
|
|
|
|
|
|
|
|
void mixer::stopProcessing( void )
|
|
{
|
|
m_audioDev->stopProcessing();
|
|
}
|
|
|
|
|
|
|
|
|
|
bool mixer::criticalXRuns( void ) const
|
|
{
|
|
return( ( m_cpuLoad >= 99 &&
|
|
eng()->getSongEditor()->realTimeTask() == TRUE ) );
|
|
}
|
|
|
|
|
|
|
|
|
|
void mixer::setClipScaling( bool _state )
|
|
{
|
|
m_mixMutex.lock();
|
|
|
|
m_scaleClip = _state;
|
|
|
|
if( _state )
|
|
{
|
|
m_poolDepth = 3;
|
|
m_readBuffer = 0;
|
|
m_analBuffer = m_readBuffer + 1;
|
|
m_writeBuffer = m_poolDepth - 1;
|
|
|
|
for( ch_cnt_t chnl=0; chnl < m_audioDev->channels(); ++chnl )
|
|
{
|
|
m_clipped[chnl] = FALSE;
|
|
m_halfStart[chnl] = m_framesPerAudioBuffer;
|
|
m_maxClip[chnl] = 1.0f;
|
|
m_previousSample[chnl] = 0.0;
|
|
m_newBuffer[chnl] = FALSE;
|
|
}
|
|
// FIXME: why assign buffer-ptr to m_readBuf just for calling
|
|
// another method?
|
|
// clearAudioBuffer(m_bufferPool[i],...) would do as well
|
|
for( Uint8 i = 0; i < 3; i++ )
|
|
{
|
|
m_readBuf = m_bufferPool[i];
|
|
clearAudioBuffer( m_readBuf, m_framesPerAudioBuffer );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_poolDepth = 2;
|
|
m_readBuffer = 0;
|
|
m_writeBuffer = 1;
|
|
m_analBuffer = 1;
|
|
}
|
|
|
|
m_mixMutex.unlock();
|
|
}
|
|
|
|
|
|
|
|
|
|
const surroundSampleFrame * mixer::renderNextBuffer( void )
|
|
{
|
|
microTimer timer;
|
|
|
|
static songEditor::playPos last_metro_pos = -1;
|
|
|
|
songEditor::playPos p = eng()->getSongEditor()->getPlayPos(
|
|
songEditor::PLAY_PATTERN );
|
|
if( eng()->getSongEditor()->playMode() == songEditor::PLAY_PATTERN &&
|
|
eng()->getPianoRoll()->isRecording() == TRUE &&
|
|
p != last_metro_pos && p.getTact64th() % 16 == 0 )
|
|
{
|
|
addPlayHandle( new samplePlayHandle( "misc/metronome01.ogg",
|
|
eng() ) );
|
|
last_metro_pos = p;
|
|
}
|
|
|
|
// now we have to make sure no other thread does anything bad
|
|
// while we're acting...
|
|
m_mixMutex.lock();
|
|
|
|
// remove all play-handles that have to be deleted and delete
|
|
// them if they still exist...
|
|
// maybe this algorithm could be optimized...
|
|
while( !m_playHandlesToRemove.empty() )
|
|
{
|
|
playHandleVector::iterator it = m_playHandles.begin();
|
|
|
|
while( it != m_playHandles.end() )
|
|
{
|
|
if( *it == m_playHandlesToRemove.front() )
|
|
{
|
|
m_playHandles.erase( it );
|
|
delete m_playHandlesToRemove.front();
|
|
break;
|
|
}
|
|
++it;
|
|
}
|
|
|
|
m_playHandlesToRemove.erase( m_playHandlesToRemove.begin() );
|
|
}
|
|
|
|
// now swap the buffers... current buffer becomes next (last)
|
|
// buffer and the next buffer becomes current (first) buffer
|
|
// qSwap( m_curBuf, m_nextBuf );
|
|
m_writeBuffer++;
|
|
m_writeBuffer %= m_poolDepth;
|
|
|
|
m_readBuffer++;
|
|
m_readBuffer %= m_poolDepth;
|
|
|
|
m_analBuffer++;
|
|
m_analBuffer %= m_poolDepth;
|
|
|
|
m_writeBuf = m_bufferPool[m_writeBuffer];
|
|
m_readBuf = m_bufferPool[m_readBuffer];
|
|
|
|
// clear last audio-buffer
|
|
clearAudioBuffer( m_writeBuf, m_framesPerAudioBuffer );
|
|
|
|
// if( criticalXRuns() == FALSE )
|
|
{
|
|
csize idx = 0;
|
|
while( idx < m_playHandles.size() )
|
|
{
|
|
register playHandle * n = m_playHandles[idx];
|
|
// delete play-handle if it played completely
|
|
if( n->done() )
|
|
{
|
|
delete n;
|
|
m_playHandles.erase( m_playHandles.begin() +
|
|
idx );
|
|
}
|
|
else
|
|
{
|
|
// play all uncompletely-played play-handles...
|
|
n->play();
|
|
++idx;
|
|
}
|
|
}
|
|
|
|
eng()->getSongEditor()->processNextBuffer();
|
|
|
|
for( vvector<audioPort *>::iterator it = m_audioPorts.begin();
|
|
it != m_audioPorts.end(); ++it )
|
|
{
|
|
if( ( *it )->m_bufferUsage != audioPort::NONE )
|
|
{
|
|
processBuffer( ( *it )->firstBuffer(),
|
|
( *it )->nextFxChannel() );
|
|
( *it )->nextPeriod();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
emit nextAudioBuffer( m_readBuf, m_framesPerAudioBuffer );
|
|
|
|
m_mixMutex.unlock();
|
|
|
|
// and trigger LFOs
|
|
envelopeAndLFOWidget::triggerLFO( eng() );
|
|
|
|
const float new_cpu_load = timer.elapsed() / 10000.0f * sampleRate() /
|
|
m_framesPerAudioBuffer;
|
|
m_cpuLoad = tLimit( (int) ( new_cpu_load * 0.1f + m_cpuLoad * 0.9f ), 0,
|
|
100 );
|
|
|
|
return( m_readBuf );
|
|
}
|
|
|
|
|
|
|
|
|
|
// removes all play-handles. this is neccessary, when the song is stopped ->
|
|
// all remaining notes etc. would be played until their end
|
|
void mixer::clear( bool _everything )
|
|
{
|
|
// TODO: m_midiClient->noteOffAll();
|
|
for( playHandleVector::iterator it = m_playHandles.begin();
|
|
it != m_playHandles.end(); ++it )
|
|
{
|
|
// we must not delete instrument-play-handles as they exist
|
|
// during the whole lifetime of an instrument - exception if
|
|
// parameter _everything is true (which is the case when
|
|
// clearing song for example)
|
|
if( _everything == TRUE ||
|
|
( *it )->type() != playHandle::INSTRUMENT_PLAY_HANDLE )
|
|
{
|
|
m_playHandlesToRemove.push_back( *it );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void FASTCALL mixer::clearAudioBuffer( sampleFrame * _ab,
|
|
const f_cnt_t _frames )
|
|
{
|
|
memset( _ab, 0, sizeof( *_ab ) * _frames );
|
|
}
|
|
|
|
|
|
|
|
#ifndef DISABLE_SURROUND
|
|
void FASTCALL mixer::clearAudioBuffer( surroundSampleFrame * _ab,
|
|
const f_cnt_t _frames )
|
|
{
|
|
memset( _ab, 0, sizeof( *_ab ) * _frames );
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
void FASTCALL mixer::bufferToPort( const sampleFrame * _buf,
|
|
const fpab_t _frames,
|
|
const fpab_t _frames_ahead,
|
|
const volumeVector & _volume_vector,
|
|
audioPort * _port )
|
|
{
|
|
const fpab_t start_frame = _frames_ahead % m_framesPerAudioBuffer;
|
|
fpab_t end_frame = start_frame + _frames;
|
|
const fpab_t loop1_frame = tMin( end_frame, m_framesPerAudioBuffer );
|
|
|
|
for( fpab_t frame = start_frame; frame < loop1_frame; ++frame )
|
|
{
|
|
for( ch_cnt_t chnl = 0; chnl < m_audioDev->channels(); ++chnl )
|
|
{
|
|
_port->firstBuffer()[frame][chnl] +=
|
|
_buf[frame - start_frame][chnl %
|
|
DEFAULT_CHANNELS] *
|
|
_volume_vector.vol[chnl];
|
|
}
|
|
}
|
|
|
|
if( end_frame > m_framesPerAudioBuffer )
|
|
{
|
|
fpab_t frames_done = m_framesPerAudioBuffer - start_frame;
|
|
end_frame = tMin( end_frame -= m_framesPerAudioBuffer,
|
|
m_framesPerAudioBuffer );
|
|
for( fpab_t frame = 0; frame < end_frame; ++frame )
|
|
{
|
|
for( ch_cnt_t chnl = 0; chnl < m_audioDev->channels();
|
|
++chnl )
|
|
{
|
|
_port->secondBuffer()[frame][chnl] +=
|
|
_buf[frames_done + frame][chnl %
|
|
DEFAULT_CHANNELS] *
|
|
_volume_vector.vol[chnl];
|
|
}
|
|
}
|
|
// we used both buffers so set flags
|
|
_port->m_bufferUsage = audioPort::BOTH;
|
|
}
|
|
else if( _port->m_bufferUsage == audioPort::NONE )
|
|
{
|
|
// only first buffer touched
|
|
_port->m_bufferUsage = audioPort::FIRST;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mixer::setHighQuality( bool _hq_on )
|
|
{
|
|
// delete (= close) our audio-device
|
|
delete m_audioDev;
|
|
|
|
// set new quality-level...
|
|
m_qualityLevel = ( _hq_on == TRUE ) ? HIGH_QUALITY_LEVEL :
|
|
DEFAULT_QUALITY_LEVEL;
|
|
|
|
// and re-open device
|
|
m_audioDev = tryAudioDevices();
|
|
m_audioDev->startProcessing();
|
|
|
|
emit( sampleRateChanged() );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FASTCALL mixer::setAudioDevice( audioDevice * _dev, bool _hq )
|
|
{
|
|
m_audioDev->stopProcessing();
|
|
|
|
m_oldAudioDev = m_audioDev;
|
|
|
|
if( _dev == NULL )
|
|
{
|
|
printf( "param _dev == NULL in mixer::setAudioDevice(...). "
|
|
"Trying any working audio-device\n" );
|
|
m_audioDev = tryAudioDevices();
|
|
}
|
|
else
|
|
{
|
|
m_audioDev = _dev;
|
|
}
|
|
|
|
m_qualityLevel = _hq ? HIGH_QUALITY_LEVEL : DEFAULT_QUALITY_LEVEL;
|
|
emit sampleRateChanged();
|
|
}
|
|
|
|
|
|
|
|
|
|
void mixer::restoreAudioDevice( void )
|
|
{
|
|
if( m_oldAudioDev != NULL )
|
|
{
|
|
delete m_audioDev; // dtor automatically calls
|
|
// stopProcessing()
|
|
m_audioDev = m_oldAudioDev;
|
|
for( Uint8 qli = DEFAULT_QUALITY_LEVEL;
|
|
qli < QUALITY_LEVELS; ++qli )
|
|
{
|
|
if( SAMPLE_RATES[qli] == m_audioDev->sampleRate() )
|
|
{
|
|
m_qualityLevel =
|
|
static_cast<qualityLevels>( qli );
|
|
emit sampleRateChanged();
|
|
break;
|
|
}
|
|
}
|
|
m_oldAudioDev = NULL;
|
|
m_audioDev->startProcessing();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void mixer::checkValidityOfPlayHandles( void )
|
|
{
|
|
playHandleVector::iterator it = m_playHandles.begin();
|
|
while( it != m_playHandles.end() )
|
|
{
|
|
( *it )->checkValidity();
|
|
++it;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
audioDevice * mixer::tryAudioDevices( void )
|
|
{
|
|
bool success_ful = FALSE;
|
|
audioDevice * dev = NULL;
|
|
QString dev_name = configManager::inst()->value( "mixer", "audiodev" );
|
|
|
|
#ifdef ALSA_SUPPORT
|
|
if( dev_name == audioALSA::name() || dev_name == "" )
|
|
{
|
|
dev = new audioALSA( SAMPLE_RATES[DEFAULT_QUALITY_LEVEL],
|
|
success_ful, this );
|
|
if( success_ful )
|
|
{
|
|
m_audioDevName = audioALSA::name();
|
|
return( dev );
|
|
}
|
|
delete dev;
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef OSS_SUPPORT
|
|
if( dev_name == audioOSS::name() || dev_name == "" )
|
|
{
|
|
dev = new audioOSS( SAMPLE_RATES[DEFAULT_QUALITY_LEVEL],
|
|
success_ful, this );
|
|
if( success_ful )
|
|
{
|
|
m_audioDevName = audioOSS::name();
|
|
return( dev );
|
|
}
|
|
delete dev;
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef JACK_SUPPORT
|
|
if( dev_name == audioJACK::name() || dev_name == "" )
|
|
{
|
|
dev = new audioJACK( SAMPLE_RATES[DEFAULT_QUALITY_LEVEL],
|
|
success_ful, this );
|
|
if( success_ful )
|
|
{
|
|
m_audioDevName = audioJACK::name();
|
|
return( dev );
|
|
}
|
|
delete dev;
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef SDL_AUDIO_SUPPORT
|
|
if( dev_name == audioSDL::name() || dev_name == "" )
|
|
{
|
|
dev = new audioSDL( SAMPLE_RATES[DEFAULT_QUALITY_LEVEL],
|
|
success_ful, this );
|
|
if( success_ful )
|
|
{
|
|
m_audioDevName = audioSDL::name();
|
|
return( dev );
|
|
}
|
|
delete dev;
|
|
}
|
|
#endif
|
|
|
|
// add more device-classes here...
|
|
//dev = new audioXXXX( SAMPLE_RATES[m_qualityLevel], success_ful, this );
|
|
//if( sucess_ful )
|
|
//{
|
|
// return( dev );
|
|
//}
|
|
//delete dev
|
|
|
|
printf( "No audio-driver working - falling back to dummy-audio-"
|
|
"driver\nYou can render your songs and listen to the output "
|
|
"files...\n" );
|
|
|
|
m_audioDevName = audioDummy::name();
|
|
|
|
return( new audioDummy( SAMPLE_RATES[m_qualityLevel], success_ful,
|
|
this ) );
|
|
}
|
|
|
|
|
|
|
|
|
|
midiClient * mixer::tryMIDIClients( void )
|
|
{
|
|
QString client_name = configManager::inst()->value( "mixer",
|
|
"mididev" );
|
|
|
|
#ifdef ALSA_SUPPORT
|
|
if( client_name == midiALSASeq::name() || client_name == "" )
|
|
{
|
|
midiALSASeq * malsas = new midiALSASeq( eng() );
|
|
if( malsas->isRunning() )
|
|
{
|
|
m_midiClientName = midiALSASeq::name();
|
|
return( malsas );
|
|
}
|
|
delete malsas;
|
|
}
|
|
|
|
if( client_name == midiALSARaw::name() || client_name == "" )
|
|
{
|
|
midiALSARaw * malsar = new midiALSARaw( eng() );
|
|
if( malsar->isRunning() )
|
|
{
|
|
m_midiClientName = midiALSARaw::name();
|
|
return( malsar );
|
|
}
|
|
delete malsar;
|
|
}
|
|
#endif
|
|
|
|
#ifdef OSS_SUPPORT
|
|
if( client_name == midiOSS::name() || client_name == "" )
|
|
{
|
|
midiOSS * moss = new midiOSS( eng() );
|
|
if( moss->isRunning() )
|
|
{
|
|
m_midiClientName = midiOSS::name();
|
|
return( moss );
|
|
}
|
|
delete moss;
|
|
}
|
|
#endif
|
|
|
|
printf( "Couldn't create MIDI-client, neither with ALSA nor with "
|
|
"OSS. Will use dummy-MIDI-client.\n" );
|
|
|
|
m_midiClientName = midiDummy::name();
|
|
|
|
return( new midiDummy( eng() ) );
|
|
}
|
|
|
|
|
|
|
|
|
|
void mixer::processBuffer( const surroundSampleFrame * _buf,
|
|
fx_ch_t/* _fx_chnl */ )
|
|
{
|
|
// TODO: effect-implementation
|
|
|
|
if( m_scaleClip )
|
|
{
|
|
for( ch_cnt_t chnl=0;
|
|
chnl < m_audioDev->channels();
|
|
++chnl )
|
|
{
|
|
m_newBuffer[chnl] = TRUE;
|
|
}
|
|
}
|
|
|
|
for( fpab_t frame = 0; frame < m_framesPerAudioBuffer; ++frame )
|
|
{
|
|
for( ch_cnt_t chnl = 0; chnl < m_audioDev->channels(); ++chnl )
|
|
{
|
|
m_writeBuf[frame][chnl] += _buf[frame][chnl];
|
|
|
|
if( m_scaleClip )
|
|
{
|
|
scaleClip( frame, chnl );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void FASTCALL mixer::scaleClip( fpab_t _frame, ch_cnt_t _chnl )
|
|
{
|
|
// Check for zero crossing
|
|
if( ( m_writeBuf[_frame][_chnl] >=0 &&
|
|
m_previousSample[_chnl] < 0 ) ||
|
|
( m_writeBuf[_frame][_chnl] <=0 &&
|
|
m_previousSample[_chnl] > 0 ) )
|
|
{
|
|
// if a clip occurred between the zero
|
|
// crossings, scale the half-wave
|
|
if( m_clipped[_chnl] )
|
|
{
|
|
if( m_newBuffer[_chnl] )
|
|
{
|
|
for( fpab_t i = m_halfStart[_chnl];
|
|
i < m_framesPerAudioBuffer;
|
|
i++ )
|
|
{
|
|
m_bufferPool[m_analBuffer][i][_chnl] /=
|
|
m_maxClip[_chnl];
|
|
}
|
|
|
|
for( fpab_t i = 0;
|
|
i < _frame;
|
|
i++ )
|
|
{
|
|
m_writeBuf[i][_chnl] /=
|
|
m_maxClip[_chnl];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( fpab_t i = m_halfStart[_chnl];
|
|
i < _frame;
|
|
i++ )
|
|
{
|
|
m_writeBuf[i][_chnl] /= m_maxClip[_chnl];
|
|
}
|
|
}
|
|
}
|
|
m_halfStart[_chnl] = _frame;
|
|
m_clipped[_chnl] = FALSE;
|
|
m_newBuffer[_chnl] = FALSE;
|
|
m_maxClip[_chnl] = 1.0;
|
|
}
|
|
|
|
// check for clip
|
|
if( fabsf( m_writeBuf[_frame][_chnl] ) > 1.0f )
|
|
{
|
|
m_clipped[_chnl] = TRUE;
|
|
if( fabs( m_writeBuf[_frame][_chnl] ) >
|
|
m_maxClip[_chnl] )
|
|
{
|
|
m_maxClip[_chnl] = fabs(
|
|
m_writeBuf[_frame][_chnl] );
|
|
}
|
|
}
|
|
|
|
m_previousSample[_chnl] = m_writeBuf[_frame][_chnl];
|
|
}
|
|
|
|
|
|
#include "mixer.moc"
|
|
|
|
|
|
#endif
|