![]() |
Mixxx
|
00001 /*************************************************************************** 00002 enginebuffer.cpp - description 00003 ------------------- 00004 begin : Wed Feb 20 2002 00005 copyright : (C) 2002 by Tue and Ken Haste Andersen 00006 email : 00007 ***************************************************************************/ 00008 00009 /*************************************************************************** 00010 * * 00011 * This program is free software; you can redistribute it and/or modify * 00012 * it under the terms of the GNU General Public License as published by * 00013 * the Free Software Foundation; either version 2 of the License, or * 00014 * (at your option) any later version. * 00015 * * 00016 ***************************************************************************/ 00017 00018 #include <QEvent> 00019 #include <QtDebug> 00020 00021 #include "engine/enginebuffer.h" 00022 #include "cachingreader.h" 00023 #include "sampleutil.h" 00024 00025 #include "controlpushbutton.h" 00026 #include "controlobjectthreadmain.h" 00027 #include "configobject.h" 00028 #include "controlpotmeter.h" 00029 #include "engine/enginebufferscalest.h" 00030 #include "engine/enginebufferscalelinear.h" 00031 #include "engine/enginebufferscaledummy.h" 00032 #include "mathstuff.h" 00033 #include "engine/engineworkerscheduler.h" 00034 #include "engine/readaheadmanager.h" 00035 #include "engine/enginecontrol.h" 00036 #include "engine/loopingcontrol.h" 00037 #include "engine/ratecontrol.h" 00038 #include "engine/bpmcontrol.h" 00039 #include "engine/quantizecontrol.h" 00040 00041 #ifdef __VINYLCONTROL__ 00042 #include "engine/vinylcontrolcontrol.h" 00043 #endif 00044 00045 #include "trackinfoobject.h" 00046 00047 #ifdef _MSC_VER 00048 #include <float.h> // for _isnan() on VC++ 00049 #define isnan(x) _isnan(x) // VC++ uses _isnan() instead of isnan() 00050 #else 00051 #include <math.h> // for isnan() everywhere else 00052 #endif 00053 00054 const double kMaxPlayposRange = 1.14; 00055 const double kMinPlayposRange = -0.14; 00056 00057 EngineBuffer::EngineBuffer(const char * _group, ConfigObject<ConfigValue> * _config) : 00058 m_engineLock(QMutex::Recursive), 00059 group(_group), 00060 m_pConfig(_config), 00061 m_pLoopingControl(NULL), 00062 m_pRateControl(NULL), 00063 m_pBpmControl(NULL), 00064 m_pReadAheadManager(NULL), 00065 m_pOtherEngineBuffer(NULL), 00066 m_pReader(NULL), 00067 filepos_play(0.), 00068 rate_old(0.), 00069 file_length_old(-1), 00070 file_srate_old(0), 00071 m_iSamplesCalculated(0), 00072 m_iUiSlowTick(0), 00073 m_pTrackEnd(NULL), 00074 m_pRepeat(NULL), 00075 startButton(NULL), 00076 endButton(NULL), 00077 m_pScale(NULL), 00078 m_pScaleLinear(NULL), 00079 m_pScaleST(NULL), 00080 m_bScalerChanged(false), 00081 m_bLastBufferPaused(true), 00082 m_fRampValue(0.0), 00083 m_iRampState(ENGINE_RAMP_NONE), 00084 m_pDitherBuffer(new CSAMPLE[MAX_BUFFER_LEN]), 00085 m_iDitherBufferReadIndex(0) { 00086 00087 // Generate dither values 00088 for (int i = 0; i < MAX_BUFFER_LEN; ++i) { 00089 m_pDitherBuffer[i] = static_cast<float>(rand() % 32768) / 32768.0 - 0.5; 00090 } 00091 00092 m_fLastSampleValue[0] = 0; 00093 m_fLastSampleValue[1] = 0; 00094 00095 m_pReader = new CachingReader(_group, _config); 00096 connect(m_pReader, SIGNAL(trackLoaded(TrackPointer, int, int)), 00097 this, SLOT(slotTrackLoaded(TrackPointer, int, int)), 00098 Qt::DirectConnection); 00099 connect(m_pReader, SIGNAL(trackLoadFailed(TrackPointer, QString)), 00100 this, SLOT(slotTrackLoadFailed(TrackPointer, QString)), 00101 Qt::DirectConnection); 00102 00103 // Play button 00104 playButton = new ControlPushButton(ConfigKey(group, "play")); 00105 playButton->setToggleButton(true); 00106 connect(playButton, SIGNAL(valueChanged(double)), 00107 this, SLOT(slotControlPlay(double)), 00108 Qt::DirectConnection); 00109 playButtonCOT = new ControlObjectThreadMain(playButton); 00110 00111 //Play from Start Button (for sampler) 00112 playStartButton = new ControlPushButton(ConfigKey(group, "start_play")); 00113 connect(playStartButton, SIGNAL(valueChanged(double)), 00114 this, SLOT(slotControlPlayFromStart(double)), 00115 Qt::DirectConnection); 00116 playStartButton->set(0); 00117 playStartButtonCOT = new ControlObjectThreadMain(playStartButton); 00118 00119 // Jump to start and stop button 00120 stopStartButton = new ControlPushButton(ConfigKey(group, "start_stop")); 00121 connect(stopStartButton, SIGNAL(valueChanged(double)), 00122 this, SLOT(slotControlJumpToStartAndStop(double)), 00123 Qt::DirectConnection); 00124 stopStartButton->set(0); 00125 stopStartButtonCOT = new ControlObjectThreadMain(stopStartButton); 00126 00127 //Stop playback (for sampler) 00128 stopButton = new ControlPushButton(ConfigKey(group, "stop")); 00129 connect(stopButton, SIGNAL(valueChanged(double)), 00130 this, SLOT(slotControlStop(double)), 00131 Qt::DirectConnection); 00132 stopButton->set(0); 00133 stopButtonCOT = new ControlObjectThreadMain(stopButton); 00134 00135 // Start button 00136 startButton = new ControlPushButton(ConfigKey(group, "start")); 00137 connect(startButton, SIGNAL(valueChanged(double)), 00138 this, SLOT(slotControlStart(double)), 00139 Qt::DirectConnection); 00140 00141 // End button 00142 endButton = new ControlPushButton(ConfigKey(group, "end")); 00143 connect(endButton, SIGNAL(valueChanged(double)), 00144 this, SLOT(slotControlEnd(double)), 00145 Qt::DirectConnection); 00146 00147 // Actual rate (used in visuals, not for control) 00148 rateEngine = new ControlObject(ConfigKey(group, "rateEngine")); 00149 00150 // BPM to display in the UI (updated more slowly than the actual bpm) 00151 visualBpm = new ControlObject(ConfigKey(group, "visual_bpm")); 00152 00153 // Slider to show and change song position 00154 //these bizarre choices map conveniently to the 0-127 range of midi 00155 playposSlider = new ControlPotmeter( 00156 ConfigKey(group, "playposition"), kMinPlayposRange, kMaxPlayposRange); 00157 connect(playposSlider, SIGNAL(valueChanged(double)), 00158 this, SLOT(slotControlSeek(double)), 00159 Qt::DirectConnection); 00160 00161 // Control used to communicate ratio playpos to GUI thread 00162 visualPlaypos = new ControlPotmeter( 00163 ConfigKey(group, "visual_playposition"), kMinPlayposRange, kMaxPlayposRange); 00164 00165 // m_pTrackEnd is used to signal when at end of file during 00166 // playback. TODO(XXX) This should not even be a control object because it 00167 // is an internal flag used only by the EngineBuffer. 00168 m_pTrackEnd = new ControlObject(ConfigKey(group, "TrackEnd")); 00169 //A COTM for use in slots that are called by the GUI thread. 00170 m_pTrackEndCOT = new ControlObjectThreadMain(m_pTrackEnd); 00171 00172 m_pRepeat = new ControlPushButton(ConfigKey(group, "repeat")); 00173 m_pRepeat->setToggleButton(true); 00174 00175 // Sample rate 00176 m_pSampleRate = ControlObject::getControl(ConfigKey("[Master]","samplerate")); 00177 00178 m_pTrackSamples = new ControlObject(ConfigKey(group, "track_samples")); 00179 m_pTrackSampleRate = new ControlObject(ConfigKey(group, "track_samplerate")); 00180 00181 // Quantization Controller for enabling and disabling the 00182 // quantization (alignment) of loop in/out positions and (hot)cues with 00183 // beats. 00184 addControl(new QuantizeControl(_group, _config)); 00185 00186 // Create the Loop Controller 00187 m_pLoopingControl = new LoopingControl(_group, _config); 00188 addControl(m_pLoopingControl); 00189 00190 #ifdef __VINYLCONTROL__ 00191 // If VinylControl is enabled, add a VinylControlControl. This must be done 00192 // before RateControl is created. 00193 addControl(new VinylControlControl(group, _config)); 00194 #endif 00195 00196 // Create the Rate Controller 00197 m_pRateControl = new RateControl(_group, _config); 00198 addControl(m_pRateControl); 00199 00200 fwdButton = ControlObject::getControl(ConfigKey(_group, "fwd")); 00201 backButton = ControlObject::getControl(ConfigKey(_group, "back")); 00202 00203 // Create the BPM Controller 00204 m_pBpmControl = new BpmControl(_group, _config); 00205 addControl(m_pBpmControl); 00206 00207 m_pReadAheadManager = new ReadAheadManager(m_pReader); 00208 m_pReadAheadManager->addEngineControl(m_pLoopingControl); 00209 m_pReadAheadManager->addEngineControl(m_pRateControl); 00210 00211 // Construct scaling objects 00212 m_pScaleLinear = new EngineBufferScaleLinear(m_pReadAheadManager); 00213 00214 m_pScaleST = new EngineBufferScaleST(m_pReadAheadManager); 00215 //m_pScaleST = (EngineBufferScaleST*)new EngineBufferScaleDummy(m_pReadAheadManager); 00216 setPitchIndpTimeStretch(false); // default to VE, let the user specify PITS in their mix 00217 00218 setNewPlaypos(0.); 00219 00220 m_pKeylock = new ControlPushButton(ConfigKey(group, "keylock")); 00221 m_pKeylock->setToggleButton(true); 00222 m_pKeylock->set(false); 00223 00224 m_pEject = new ControlPushButton(ConfigKey(group, "eject")); 00225 connect(m_pEject, SIGNAL(valueChanged(double)), 00226 this, SLOT(slotEjectTrack(double)), 00227 Qt::DirectConnection); 00228 00229 //m_iRampIter = 0; 00230 00231 /*df.setFileName("mixxx-debug.csv"); 00232 df.open(QIODevice::WriteOnly | QIODevice::Text); 00233 writer.setDevice(&df);*/ 00234 } 00235 00236 EngineBuffer::~EngineBuffer() 00237 { 00238 //close the writer 00239 /*df.close();*/ 00240 delete m_pReadAheadManager; 00241 delete m_pReader; 00242 00243 delete playButtonCOT; 00244 delete playButton; 00245 delete playStartButtonCOT; 00246 delete playStartButton; 00247 delete startButton; 00248 delete endButton; 00249 delete stopStartButtonCOT; 00250 delete stopButtonCOT; 00251 delete stopButton; 00252 delete rateEngine; 00253 delete playposSlider; 00254 delete visualPlaypos; 00255 00256 delete m_pTrackEndCOT; 00257 delete m_pTrackEnd; 00258 00259 delete m_pRepeat; 00260 00261 delete m_pTrackSamples; 00262 delete m_pTrackSampleRate; 00263 00264 delete m_pScaleLinear; 00265 delete m_pScaleST; 00266 00267 delete m_pKeylock; 00268 delete m_pEject; 00269 00270 delete [] m_pDitherBuffer; 00271 00272 while (m_engineControls.size() > 0) { 00273 EngineControl* pControl = m_engineControls.takeLast(); 00274 delete pControl; 00275 } 00276 } 00277 00278 void EngineBuffer::setPitchIndpTimeStretch(bool b) 00279 { 00280 // MUST ACQUIRE THE PAUSE MUTEX BEFORE CALLING THIS METHOD 00281 00282 // Change sound scale mode 00283 00284 //SoundTouch's linear interpolation code doesn't sound very good. 00285 //Our own EngineBufferScaleLinear sounds slightly better, but it's 00286 //not working perfectly. Eventually we should have our own working 00287 //better, so scratching sounds good. 00288 00289 //Update Dec 30/2007 00290 //If we delete the m_pScale object and recreate it, it eventually 00291 //causes some weird bad pointer somewhere, which will either cause 00292 //the waveform the roll in a weird way or fire an ASSERT from 00293 //visualchannel.cpp or something. Need to valgrind this or something. 00294 00295 if (b == true) { 00296 m_pScale = m_pScaleST; 00297 ((EngineBufferScaleST *)m_pScaleST)->setPitchIndpTimeStretch(b); 00298 } else { 00299 m_pScale = m_pScaleLinear; 00300 } 00301 m_bScalerChanged = true; 00302 } 00303 00304 double EngineBuffer::getBpm() 00305 { 00306 return m_pBpmControl->getBpm(); 00307 } 00308 00309 void EngineBuffer::setOtherEngineBuffer(EngineBuffer * pOtherEngineBuffer) 00310 { 00311 if (!m_pOtherEngineBuffer) { 00312 m_pOtherEngineBuffer = pOtherEngineBuffer; 00313 m_pBpmControl->setOtherEngineBuffer(pOtherEngineBuffer); 00314 } else 00315 qCritical("EngineBuffer: Other engine buffer already set!"); 00316 } 00317 00318 void EngineBuffer::setNewPlaypos(double newpos) 00319 { 00320 //qDebug() << "engine new pos " << newpos; 00321 00322 filepos_play = newpos; 00323 00324 // Ensures that the playpos slider gets updated in next process call 00325 m_iSamplesCalculated = 1000000; 00326 00327 // The right place to do this? 00328 if (m_pScale) 00329 m_pScale->clear(); 00330 m_pReadAheadManager->notifySeek(filepos_play); 00331 00332 // Must hold the engineLock while using m_engineControls 00333 m_engineLock.lock(); 00334 for (QList<EngineControl*>::iterator it = m_engineControls.begin(); 00335 it != m_engineControls.end(); it++) { 00336 EngineControl *pControl = *it; 00337 pControl->notifySeek(filepos_play); 00338 } 00339 m_engineLock.unlock(); 00340 } 00341 00342 const char * EngineBuffer::getGroup() 00343 { 00344 return group; 00345 } 00346 00347 double EngineBuffer::getRate() 00348 { 00349 return m_pRateControl->getRawRate(); 00350 } 00351 00352 // WARNING: Always called from the EngineWorker thread pool 00353 void EngineBuffer::slotTrackLoaded(TrackPointer pTrack, 00354 int iTrackSampleRate, 00355 int iTrackNumSamples) { 00356 pause.lock(); 00357 m_pCurrentTrack = pTrack; 00358 file_srate_old = iTrackSampleRate; 00359 file_length_old = iTrackNumSamples; 00360 m_pTrackSamples->set(iTrackNumSamples); 00361 m_pTrackSampleRate->set(iTrackSampleRate); 00362 slotControlSeek(0.); 00363 00364 // Let the engine know that a track is loaded now. 00365 m_pTrackEndCOT->slotSet(0.0f); //XXX: Not sure if to use the COT or CO here 00366 00367 pause.unlock(); 00368 00369 emit(trackLoaded(pTrack)); 00370 } 00371 00372 // WARNING: Always called from the EngineWorker thread pool 00373 void EngineBuffer::slotTrackLoadFailed(TrackPointer pTrack, 00374 QString reason) { 00375 ejectTrack(); 00376 emit(trackLoadFailed(pTrack, reason)); 00377 } 00378 00379 TrackPointer EngineBuffer::getLoadedTrack() const { 00380 return m_pCurrentTrack; 00381 } 00382 00383 void EngineBuffer::ejectTrack() { 00384 // Don't allow ejections while playing a track. We don't need to lock to 00385 // call ControlObject::get() so this is fine. 00386 if (playButton->get() > 0) 00387 return; 00388 00389 pause.lock(); 00390 TrackPointer pTrack = m_pCurrentTrack; 00391 m_pCurrentTrack.clear(); 00392 file_srate_old = 0; 00393 file_length_old = 0; 00394 playButton->set(0.0); 00395 slotControlSeek(0.); 00396 m_pTrackSamples->set(0); 00397 m_pTrackSampleRate->set(0); 00398 pause.unlock(); 00399 00400 emit(trackUnloaded(pTrack)); 00401 } 00402 00403 // WARNING: This method runs in both the GUI thread and the Engine Thread 00404 void EngineBuffer::slotControlSeek(double change) 00405 { 00406 if(isnan(change) || change > kMaxPlayposRange || change < kMinPlayposRange) { 00407 // This seek is ridiculous. 00408 return; 00409 } 00410 00411 // Find new playpos, restrict to valid ranges. 00412 double new_playpos = round(change*file_length_old); 00413 00414 // TODO(XXX) currently not limiting seeks file_length_old instead of 00415 // kMaxPlayposRange. 00416 if (new_playpos > file_length_old) 00417 new_playpos = file_length_old; 00418 00419 // Ensure that the file position is even (remember, stereo channel files...) 00420 if (!even((int)new_playpos)) 00421 new_playpos--; 00422 00423 setNewPlaypos(new_playpos); 00424 } 00425 00426 // WARNING: This method runs in both the GUI thread and the Engine Thread 00427 void EngineBuffer::slotControlSeekAbs(double abs) 00428 { 00429 slotControlSeek(abs/file_length_old); 00430 } 00431 00432 void EngineBuffer::slotControlPlay(double v) 00433 { 00434 // If no track is currently loaded, turn play off. 00435 if (v > 0.0 && !m_pCurrentTrack) { 00436 playButton->set(0.0f); 00437 } 00438 } 00439 00440 void EngineBuffer::slotControlStart(double v) 00441 { 00442 if (v > 0.0) { 00443 slotControlSeek(0.); 00444 } 00445 } 00446 00447 void EngineBuffer::slotControlEnd(double v) 00448 { 00449 if (v > 0.0) { 00450 slotControlSeek(1.); 00451 } 00452 } 00453 00454 void EngineBuffer::slotControlPlayFromStart(double v) 00455 { 00456 if (v > 0.0) { 00457 slotControlSeek(0.); 00458 playButton->set(1); 00459 } 00460 } 00461 00462 void EngineBuffer::slotControlJumpToStartAndStop(double v) 00463 { 00464 if (v > 0.0) { 00465 slotControlSeek(0.); 00466 playButton->set(0); 00467 } 00468 } 00469 00470 void EngineBuffer::slotControlStop(double v) 00471 { 00472 if (v > 0.0) { 00473 playButton->set(0); 00474 } 00475 } 00476 00477 void EngineBuffer::process(const CSAMPLE *, const CSAMPLE * pOut, const int iBufferSize) 00478 { 00479 Q_ASSERT(even(iBufferSize)); 00480 m_pReader->process(); 00481 // Steps: 00482 // - Lookup new reader information 00483 // - Calculate current rate 00484 // - Scale the audio with m_pScale, copy the resulting samples into the 00485 // output buffer 00486 // - Give EngineControl's a chance to do work / request seeks, etc 00487 // - Process repeat mode if we're at the end or beginning of a track 00488 // - Set last sample value (m_fLastSampleValue) so that rampOut works? Other 00489 // miscellaneous upkeep issues. 00490 00491 CSAMPLE * pOutput = (CSAMPLE *)pOut; 00492 bool bCurBufferPaused = false; 00493 double rate = 0; 00494 00495 if (!m_pTrackEnd->get() && pause.tryLock()) { 00496 float sr = m_pSampleRate->get(); 00497 00498 double baserate = 0.0f; 00499 if (sr > 0) 00500 baserate = ((double)file_srate_old/sr); 00501 00502 bool paused = playButton->get() != 0.0f ? false : true; 00503 00504 bool is_scratching = false; 00505 rate = m_pRateControl->calculateRate(baserate, paused, iBufferSize, 00506 &is_scratching); 00507 //qDebug() << "rate" << rate << " paused" << paused; 00508 00509 // Scratching always disables keylock because keylock sounds terrible 00510 // when not going at a constant rate. 00511 if (is_scratching && m_pScale != m_pScaleLinear) { 00512 setPitchIndpTimeStretch(false); 00513 } else if (!is_scratching) { 00514 if (m_pKeylock->get() && m_pScale != m_pScaleST) { 00515 setPitchIndpTimeStretch(true); 00516 } else if (!m_pKeylock->get() && m_pScale == m_pScaleST) { 00517 setPitchIndpTimeStretch(false); 00518 } 00519 } 00520 00521 // If the rate has changed, set it in the scale object 00522 if (rate != rate_old || m_bScalerChanged) { 00523 // The rate returned by the scale object can be different from the wanted rate! 00524 // Make sure new scaler has proper position 00525 if (m_bScalerChanged) { 00526 setNewPlaypos(filepos_play); 00527 } else if (m_pScale != m_pScaleLinear) { //linear scaler does this part for us now 00528 //XXX: Trying to force RAMAN to read from correct 00529 // playpos when rate changes direction - Albert 00530 if ((rate_old <= 0 && rate > 0) || 00531 (rate_old >= 0 && rate < 0)) { 00532 setNewPlaypos(filepos_play); 00533 } 00534 } 00535 00536 rate_old = rate; 00537 if (baserate > 0) //Prevent division by 0 00538 rate = baserate*m_pScale->setTempo(rate/baserate); 00539 m_pScale->setBaseRate(baserate); 00540 rate_old = rate; 00541 // Scaler is up to date now. 00542 m_bScalerChanged = false; 00543 } 00544 00545 bool at_start = filepos_play <= 0; 00546 bool at_end = filepos_play >= file_length_old; 00547 bool backwards = rate < 0; 00548 00549 // If we're playing past the end, playing before the start, or standing 00550 // still then by definition the buffer is paused. 00551 bCurBufferPaused = rate == 0 || 00552 //(at_start && backwards) || 00553 (at_end && !backwards); 00554 00555 00556 // If the buffer is not paused, then scale the audio. 00557 if (!bCurBufferPaused) { 00558 CSAMPLE *output; 00559 00560 // The fileposition should be: (why is this thing a double anyway!? 00561 // Integer valued. 00562 double filepos_play_rounded = round(filepos_play); 00563 if (filepos_play_rounded != filepos_play) { 00564 qWarning() << __FILE__ << __LINE__ << "ERROR: filepos_play is not round:" << filepos_play; 00565 filepos_play = filepos_play_rounded; 00566 } 00567 00568 // Even. 00569 if (!even(filepos_play)) { 00570 qWarning() << "ERROR: filepos_play is not even:" << filepos_play; 00571 filepos_play--; 00572 } 00573 00574 // Perform scaling of Reader buffer into buffer. 00575 output = m_pScale->scale(0, 00576 iBufferSize, 00577 0, 00578 0); 00579 double samplesRead = m_pScale->getNewPlaypos(); 00580 00581 // qDebug() << "sourceSamples used " << iSourceSamples 00582 // <<" samplesRead " << samplesRead 00583 // << ", buffer pos " << iBufferStartSample 00584 // << ", play " << filepos_play 00585 // << " bufferlen " << iBufferSize; 00586 00587 // Copy scaled audio into pOutput 00588 memcpy(pOutput, output, sizeof(pOutput[0]) * iBufferSize); 00589 00590 // Adjust filepos_play by the amount we processed. TODO(XXX) what 00591 // happens if samplesRead is a fraction? 00592 filepos_play = 00593 m_pReadAheadManager->getEffectiveVirtualPlaypositionFromLog( 00594 static_cast<int>(filepos_play), samplesRead); 00595 } // else (bCurBufferPaused) 00596 00597 m_engineLock.lock(); 00598 QListIterator<EngineControl*> it(m_engineControls); 00599 while (it.hasNext()) { 00600 EngineControl* pControl = it.next(); 00601 pControl->setCurrentSample(filepos_play, file_length_old); 00602 double control_seek = pControl->process(rate, filepos_play, 00603 file_length_old, iBufferSize); 00604 00605 if (control_seek != kNoTrigger) { 00606 // If we have not processed loops by this point then we have a 00607 // bug. RAMAN should be in charge of taking loops now. This 00608 // final step is more to notify all the EngineControls of the 00609 // happenings of the engine. TODO(rryan) log condition to a 00610 // stats-pipe once we have them. 00611 00612 filepos_play = control_seek; 00613 double filepos_play_rounded = round(filepos_play); 00614 if (filepos_play_rounded != filepos_play) { 00615 qWarning() << __FILE__ << __LINE__ << "ERROR: filepos_play is not round:" << filepos_play; 00616 filepos_play = filepos_play_rounded; 00617 } 00618 00619 // Fix filepos_play so that it is not out of bounds. 00620 if (file_length_old > 0) { 00621 if (filepos_play > file_length_old) { 00622 // TODO(XXX) limit to kMaxPlayposRange instead of file_length_old 00623 filepos_play = file_length_old; 00624 } else if(filepos_play < file_length_old * kMinPlayposRange) { 00625 filepos_play = kMinPlayposRange * file_length_old; 00626 } 00627 } 00628 00629 // Safety check that the EngineControl didn't pass us a bogus 00630 // value 00631 if (!even(filepos_play)) 00632 filepos_play--; 00633 00634 // TODO(XXX) need to re-evaluate this later. If we 00635 // setNewPlaypos, that clear()'s soundtouch, which might screw 00636 // up the audio. This sort of jump is a normal event. Also, the 00637 // EngineControl which caused this jump will get a notifySeek 00638 // for the same jump which might be confusing. For 1.8.0 00639 // purposes this works fine. If we do not notifySeek the RAMAN, 00640 // the engine and RAMAN can get out of sync. 00641 00642 //setNewPlaypos(filepos_play); 00643 m_pReadAheadManager->notifySeek(filepos_play); 00644 // Notify seek the rate control since it needs to track things 00645 // like looping. Hacky, I know, but this helps prevent things 00646 // like the scratch controller from flipping out. 00647 m_pRateControl->notifySeek(filepos_play); 00648 } 00649 } 00650 m_engineLock.unlock(); 00651 00652 00653 // Update all the indicators that EngineBuffer publishes to allow 00654 // external parts of Mixxx to observe its status. 00655 updateIndicators(rate, iBufferSize); 00656 00657 // Handle repeat mode 00658 at_start = filepos_play <= 0; 00659 at_end = filepos_play >= file_length_old; 00660 00661 bool repeat_enabled = m_pRepeat->get() != 0.0f; 00662 00663 bool end_of_track = //(at_start && backwards) || 00664 (at_end && !backwards); 00665 00666 // If playbutton is pressed, check if we are at start or end of track 00667 if ((playButton->get() || (fwdButton->get() || backButton->get())) 00668 && end_of_track) { 00669 if (repeat_enabled) { 00670 double seekPosition = at_start ? file_length_old : 0; 00671 slotControlSeek(seekPosition); 00672 } else { 00673 playButton->set(0.); 00674 } 00675 } 00676 00677 // release the pauselock 00678 pause.unlock(); 00679 } else { // if (!m_pTrackEnd->get() && pause.tryLock()) { 00680 // If we can't get the pause lock then this buffer will be silence. 00681 bCurBufferPaused = true; 00682 } 00683 00684 // Give the Reader hints as to which chunks of the current song we 00685 // really care about. It will try very hard to keep these in memory 00686 hintReader(rate, iBufferSize); 00687 00688 if (m_bLastBufferPaused && !bCurBufferPaused) { 00689 if (fabs(rate) > 0.005) //at very slow forward rates, don't ramp up 00690 m_iRampState = ENGINE_RAMP_UP; 00691 } else if (!m_bLastBufferPaused && bCurBufferPaused) { 00692 m_iRampState = ENGINE_RAMP_DOWN; 00693 } else { //we are not changing state 00694 //make sure we aren't accidentally ramping down 00695 //this is how we make sure that ramp value will become 1.0 eventually 00696 if (fabs(rate) > 0.005 && m_iRampState != ENGINE_RAMP_UP && m_fRampValue < 1.0) 00697 m_iRampState = ENGINE_RAMP_UP; 00698 } 00699 00700 //let's try holding the last sample value constant, and pull it 00701 //towards zero 00702 float ramp_inc = 0; 00703 if (m_iRampState == ENGINE_RAMP_UP || 00704 m_iRampState == ENGINE_RAMP_DOWN) { 00705 ramp_inc = m_iRampState * 300 / m_pSampleRate->get(); 00706 00707 for (int i=0; i<iBufferSize; i+=2) { 00708 if (bCurBufferPaused) { 00709 float dither = m_pDitherBuffer[m_iDitherBufferReadIndex]; 00710 m_iDitherBufferReadIndex = (m_iDitherBufferReadIndex + 1) % MAX_BUFFER_LEN; 00711 pOutput[i] = m_fLastSampleValue[0] * m_fRampValue + dither; 00712 pOutput[i+1] = m_fLastSampleValue[1] * m_fRampValue + dither; 00713 } else { 00714 pOutput[i] = pOutput[i] * m_fRampValue; 00715 pOutput[i+1] = pOutput[i+1] * m_fRampValue; 00716 } 00717 00718 m_fRampValue += ramp_inc; 00719 if (m_fRampValue >= 1.0) { 00720 m_iRampState = ENGINE_RAMP_NONE; 00721 m_fRampValue = 1.0; 00722 } 00723 if (m_fRampValue <= 0.0) { 00724 m_iRampState = ENGINE_RAMP_NONE; 00725 m_fRampValue = 0.0; 00726 } 00727 } 00728 } else if (m_fRampValue == 0.0) { 00729 SampleUtil::applyGain(pOutput, 0.0, iBufferSize); 00730 } 00731 00732 if ((!bCurBufferPaused && m_iRampState == ENGINE_RAMP_NONE) || 00733 (bCurBufferPaused && m_fRampValue == 0.0)) { 00734 m_fLastSampleValue[0] = pOutput[iBufferSize-2]; 00735 m_fLastSampleValue[1] = pOutput[iBufferSize-1]; 00736 } 00737 00738 /*for (int i=0; i<iBufferSize; i+=2) { 00739 writer << pOutput[i] << "\n"; 00740 }*/ 00741 00742 m_bLastBufferPaused = bCurBufferPaused; 00743 } 00744 00745 void EngineBuffer::updateIndicators(double rate, int iBufferSize) { 00746 00747 // Increase samplesCalculated by the buffer size 00748 m_iSamplesCalculated += iBufferSize; 00749 00750 double fFractionalPlaypos = 0.0; 00751 if (file_length_old!=0.) { 00752 fFractionalPlaypos = math_min(filepos_play,file_length_old); 00753 fFractionalPlaypos /= file_length_old; 00754 } else { 00755 fFractionalPlaypos = 0.; 00756 } 00757 00758 // Update indicators that are only updated after every 00759 // sampleRate/kiUpdateRate samples processed. (e.g. playposSlider, 00760 // rateEngine) 00761 if (m_iSamplesCalculated > (m_pSampleRate->get()/kiUpdateRate)) { 00762 playposSlider->set(fFractionalPlaypos); 00763 00764 if(rate != rateEngine->get()) 00765 rateEngine->set(rate); 00766 00767 //Update the BPM even more slowly 00768 m_iUiSlowTick = (m_iUiSlowTick + 1) % kiBpmUpdateRate; 00769 if (m_iUiSlowTick == 0) { 00770 visualBpm->set(m_pBpmControl->getBpm()); 00771 } 00772 00773 // Reset sample counter 00774 m_iSamplesCalculated = 0; 00775 } 00776 00777 // Update visual control object, this needs to be done more often than the 00778 // rateEngine and playpos slider 00779 visualPlaypos->set(fFractionalPlaypos); 00780 } 00781 00782 void EngineBuffer::hintReader(const double dRate, 00783 const int iSourceSamples) { 00784 m_engineLock.lock(); 00785 00786 m_hintList.clear(); 00787 m_pReadAheadManager->hintReader(dRate, m_hintList, iSourceSamples); 00788 00789 QListIterator<EngineControl*> it(m_engineControls); 00790 while (it.hasNext()) { 00791 EngineControl* pControl = it.next(); 00792 pControl->hintReader(m_hintList); 00793 } 00794 m_pReader->hintAndMaybeWake(m_hintList); 00795 00796 m_engineLock.unlock(); 00797 } 00798 00799 // WARNING: This method runs in the GUI thread 00800 void EngineBuffer::slotLoadTrack(TrackPointer pTrack) { 00801 // Raise the track end flag so the EngineBuffer stops processing frames 00802 m_pTrackEndCOT->slotSet(1.0); 00803 00804 //Stop playback 00805 playButtonCOT->slotSet(0.0); 00806 00807 // Signal to the reader to load the track. The reader will respond with 00808 // either trackLoaded or trackLoadFailed signals. 00809 m_pReader->newTrack(pTrack); 00810 m_pReader->wake(); 00811 } 00812 00813 void EngineBuffer::addControl(EngineControl* pControl) { 00814 // Connect to signals from EngineControl here... 00815 m_engineLock.lock(); 00816 m_engineControls.push_back(pControl); 00817 m_engineLock.unlock(); 00818 connect(pControl, SIGNAL(seek(double)), 00819 this, SLOT(slotControlSeek(double)), 00820 Qt::DirectConnection); 00821 connect(pControl, SIGNAL(seekAbs(double)), 00822 this, SLOT(slotControlSeekAbs(double)), 00823 Qt::DirectConnection); 00824 connect(this, SIGNAL(trackLoaded(TrackPointer)), 00825 pControl, SLOT(trackLoaded(TrackPointer)), 00826 Qt::DirectConnection); 00827 connect(this, SIGNAL(trackUnloaded(TrackPointer)), 00828 pControl, SLOT(trackUnloaded(TrackPointer)), 00829 Qt::DirectConnection); 00830 } 00831 00832 void EngineBuffer::bindWorkers(EngineWorkerScheduler* pWorkerScheduler) { 00833 pWorkerScheduler->bindWorker(m_pReader); 00834 } 00835 00836 bool EngineBuffer::isTrackLoaded() { 00837 if (m_pCurrentTrack) { 00838 return true; 00839 } 00840 return false; 00841 } 00842 00843 void EngineBuffer::slotEjectTrack(double v) { 00844 if (v > 0) { 00845 ejectTrack(); 00846 } 00847 }