![]() |
Mixxx
|
00001 /*************************************************************************** 00002 vinylcontrolxwax.cpp 00003 ------------------- 00004 begin : Sometime in Summer 2007 00005 copyright : (C) 2007 Albert Santoni 00006 (C) 2007 Mark Hills 00007 (C) 2011 Owen Williams 00008 Portions of xwax used under the terms of the GPL 00009 current maintainer : Owen Williams 00010 email : owilliams@mixxx.org 00011 ***************************************************************************/ 00012 00013 /*************************************************************************** 00014 * * 00015 * This program is free software; you can redistribute it and/or modify * 00016 * it under the terms of the GNU General Public License as published by * 00017 * the Free Software Foundation; either version 2 of the License, or * 00018 * (at your option) any later version. * 00019 * * 00020 ***************************************************************************/ 00021 00022 #include <QtDebug> 00023 #include <limits.h> 00024 #include "vinylcontrolxwax.h" 00025 #include "controlobjectthreadmain.h" 00026 #include <math.h> 00027 00028 00029 /****** TODO ******* 00030 Stuff to maybe implement here 00031 1) The smoothing thing that xwax does 00032 2) Tons of cleanup 00033 3) Speed up needle dropping 00034 4) Extrapolate small dropouts and keep track of "dynamics" 00035 00036 ********************/ 00037 00038 bool VinylControlXwax::m_bLUTInitialized = false; 00039 QMutex VinylControlXwax::s_xwaxLUTMutex; 00040 00041 VinylControlXwax::VinylControlXwax(ConfigObject<ConfigValue> * pConfig, QString group) : VinylControl(pConfig, group) 00042 { 00043 dOldPos = 0.0f; 00044 m_samples = NULL; 00045 char * timecode = NULL; 00046 bShouldClose = false; 00047 bForceResync = false; 00048 iOldMode = MIXXX_VCMODE_ABSOLUTE; 00049 dUiUpdateTime = -1.0f; 00050 m_bNeedleSkipPrevention = (bool)(m_pConfig->getValueString( ConfigKey( "[VinylControl]", "needle_skip_prevention" ) ).toInt()); 00051 signalenabled->slotSet(m_pConfig->getValueString( ConfigKey( "[VinylControl]", "show_signal_quality" ) ).toInt()); 00052 00053 dLastTrackSelectPos = 0.0; 00054 dCurTrackSelectPos = 0.0; 00055 trackSelector = trackLoader = NULL; 00056 bTrackSelectMode = false; 00057 00058 tSinceSteadyPitch = QTime(); 00059 dOldSteadyPitch = 1.0f; 00060 00061 iQualPos = 0; 00062 iQualFilled = 0; 00063 00064 m_bCDControl = false; 00065 00066 //this is all needed because libxwax indexes by C-strings 00067 //so we go and pass libxwax a pointer into our local stack... 00068 if (strVinylType == MIXXX_VINYL_SERATOCV02VINYLSIDEA) 00069 timecode = (char*)"serato_2a"; 00070 else if (strVinylType == MIXXX_VINYL_SERATOCV02VINYLSIDEB) 00071 timecode = (char*)"serato_2b"; 00072 else if (strVinylType == MIXXX_VINYL_SERATOCD) { 00073 timecode = (char*)"serato_cd"; 00074 m_bNeedleSkipPrevention = false; 00075 m_bCDControl = true; 00076 } 00077 else if (strVinylType == MIXXX_VINYL_TRAKTORSCRATCHSIDEA) 00078 timecode = (char*)"traktor_a"; 00079 else if (strVinylType == MIXXX_VINYL_TRAKTORSCRATCHSIDEB) 00080 timecode = (char*)"traktor_b"; 00081 else { 00082 qDebug() << "Unknown vinyl type, defaulting to serato_2a"; 00083 timecode = (char*)"serato_2a"; 00084 } 00085 00086 double speed = 1.0f; 00087 if (strVinylSpeed == MIXXX_VINYL_SPEED_45) 00088 speed = 1.35f; 00089 00090 //qDebug() << "Xwax Vinyl control starting with a sample rate of:" << iSampleRate; 00091 qDebug() << "Building timecode lookup tables for" << strVinylType << "with speed" << strVinylSpeed; 00092 00093 00094 //Initialize the timecoder structure. 00095 s_xwaxLUTMutex.lock(); //Static mutex! We don't want two threads doing this! 00096 00097 timecoder_init(&timecoder, timecode, speed, iSampleRate); 00098 timecoder_monitor_init(&timecoder, MIXXX_VINYL_SCOPE_SIZE); 00099 //Note that timecoder_init will not double-malloc the LUTs, and after this we are guaranteed 00100 //that the LUT has been generated unless we ran out of memory. 00101 m_bLUTInitialized = true; 00102 m_uiSafeZone = timecoder_get_safe(&timecoder); 00103 //} 00104 s_xwaxLUTMutex.unlock(); 00105 00106 qDebug() << "Starting vinyl control xwax thread"; 00107 00108 //Start this thread (ends up calling-back the function "run()" below) 00109 start(); 00110 } 00111 00112 VinylControlXwax::~VinylControlXwax() 00113 { 00114 // Remove existing samples 00115 if (m_samples) 00116 free(m_samples); 00117 00118 //Cleanup xwax nicely 00119 timecoder_clear(&timecoder); 00120 m_bLUTInitialized = false; 00121 00122 // Continue the run() function and close it 00123 lockSamples.lock(); 00124 bShouldClose = true; 00125 waitForNextInput.wakeAll(); 00126 lockSamples.unlock(); 00127 00128 controlScratch->slotSet(0.0f); 00129 wait(); 00130 } 00131 00132 //static 00133 void VinylControlXwax::freeLUTs() 00134 { 00135 s_xwaxLUTMutex.lock(); //Static mutex! We don't want two threads doing this! 00136 if (m_bLUTInitialized) { 00137 timecoder_free_lookup(); //Frees all the LUTs in xwax. 00138 m_bLUTInitialized = false; 00139 } 00140 s_xwaxLUTMutex.unlock(); 00141 } 00142 00143 00144 void VinylControlXwax::AnalyseSamples(const short *samples, size_t size) 00145 { 00146 if (lockSamples.tryLock()) 00147 { 00148 //Submit the samples to the xwax timecode processor 00149 timecoder_submit(&timecoder, samples, size); 00150 00151 bHaveSignal = fabs((float)samples[0]) + fabs((float)samples[1]) > MIN_SIGNAL; 00152 //qDebug() << "signal?" << bHaveSignal; 00153 00154 waitForNextInput.wakeAll(); 00155 lockSamples.unlock(); 00156 } 00157 } 00158 00159 unsigned char* VinylControlXwax::getScopeBytemap() 00160 { 00161 return timecoder.mon; 00162 } 00163 00164 00165 void VinylControlXwax::run() 00166 { 00167 unsigned static id = 0; //the id of this thread, for debugging purposes //XXX copypasta (should factor this out somehow), -kousu 2/2009 00168 QThread::currentThread()->setObjectName(QString("VinylControlXwax %1").arg(++id)); 00169 00170 dVinylPosition = 0.0f; 00171 dVinylPitch = 0.0f; 00172 dOldPitch = 0.0f; 00173 //bool absoluteMode = true; 00174 int iPosition = -1; 00175 float filePosition = 0.0f; 00176 //bool bScratchMode = true; 00177 double dDriftAmt = 0.0f; 00178 double dPitchRing[RING_SIZE]; 00179 int ringPos = 0; 00180 int ringFilled = 0; 00181 double cur_duration = -1.0f; 00182 double old_duration = -1.0f; 00183 int reportedMode = 0; 00184 bool reportedPlayButton = 0; 00185 tSinceSteadyPitch.start(); 00186 00187 float when; //unused, needed for calling xwax 00188 00189 bShouldClose = false; 00190 00191 iVCMode = mode->get(); 00192 00193 while(true) 00194 { 00195 lockSamples.lock(); 00196 waitForNextInput.wait(&lockSamples); 00197 lockSamples.unlock(); 00198 00199 if (bShouldClose) 00200 return; 00201 00202 //TODO: Move all these config object get*() calls to an "updatePrefs()" function, 00203 // and make that get called when any options get changed in the preferences dialog, rather than 00204 // polling everytime we get a buffer. 00205 00206 00207 //Check if vinyl control is enabled... 00208 bIsEnabled = checkEnabled(bIsEnabled, enabled->get()); 00209 00210 //Get the pitch range from the prefs. 00211 fRateRange = rateRange->get(); 00212 00213 if(bHaveSignal) 00214 { 00215 //Always analyse the input samples 00216 iPosition = timecoder_get_position(&timecoder, &when); 00217 //Notify the UI if the timecode quality is good 00218 establishQuality(iPosition != -1); 00219 } 00220 00221 //are we even playing and enabled at all? 00222 if (!bIsEnabled) 00223 continue; 00224 00225 dVinylPitch = timecoder_get_pitch(&timecoder); 00226 00227 //if no track loaded, let track selection work but that's it 00228 if (duration == NULL) 00229 { 00230 //until I can figure out how to detect "track 2" on serato CD, 00231 //don't try track selection 00232 if (!m_bCDControl) 00233 { 00234 bTrackSelectMode = true; 00235 doTrackSelection(false, dVinylPitch, iPosition); 00236 } 00237 continue; 00238 } 00239 //qDebug() << m_group << id << iPosition << dVinylPitch; 00240 00241 cur_duration = duration->get(); 00242 00243 00244 //Has a new track been loaded? 00245 //FIXME? we should really sync on all track changes 00246 if (cur_duration != old_duration) 00247 { 00248 bForceResync=true; 00249 bTrackSelectMode = false; //just in case 00250 old_duration = cur_duration; 00251 //duration from the control object is an integer. We need 00252 //more precision: 00253 fTrackDuration = trackSamples->get() / 2 / trackSampleRate->get(); 00254 00255 //we were at record end, so turn it off and restore mode 00256 if(atRecordEnd) 00257 { 00258 disableRecordEndMode(); 00259 if (iOldMode == MIXXX_VCMODE_CONSTANT) 00260 iVCMode = MIXXX_VCMODE_RELATIVE; 00261 else 00262 iVCMode = iOldMode; 00263 } 00264 } 00265 00266 //make sure dVinylPosition only has good values 00267 if (iPosition != -1) 00268 { 00269 dVinylPosition = iPosition; 00270 dVinylPosition = dVinylPosition / 1000.0f; 00271 dVinylPosition -= iLeadInTime; 00272 } 00273 00274 00275 00276 //Initialize drift control to zero in case we don't get any position data to calculate it with. 00277 dDriftControl = 0.0f; 00278 00279 filePosition = playPos->get() * fTrackDuration; //Get the playback position in the file in seconds. 00280 00281 reportedMode = mode->get(); 00282 reportedPlayButton = playButton->get(); 00283 00284 if (iVCMode != reportedMode) 00285 { 00286 //if we are playing, don't allow change 00287 //to absolute mode (would cause sudden track skip) 00288 if (reportedPlayButton && reportedMode == MIXXX_VCMODE_ABSOLUTE) 00289 { 00290 iVCMode = MIXXX_VCMODE_RELATIVE; 00291 mode->slotSet((double)iVCMode); 00292 } 00293 else //go ahead and switch 00294 { 00295 iVCMode = reportedMode; 00296 if (reportedMode == MIXXX_VCMODE_ABSOLUTE) 00297 bForceResync = true; 00298 } 00299 00300 //if we are out of error mode... 00301 if (vinylStatus->get() == VINYL_STATUS_ERROR && iVCMode == MIXXX_VCMODE_RELATIVE) 00302 { 00303 vinylStatus->slotSet(VINYL_STATUS_OK); 00304 } 00305 } 00306 00307 //if looping has been enabled, don't allow absolute mode 00308 if (loopEnabled->get() && iVCMode == MIXXX_VCMODE_ABSOLUTE) 00309 { 00310 iVCMode = MIXXX_VCMODE_RELATIVE; 00311 mode->slotSet((double)iVCMode); 00312 } 00313 //are we newly playing near the end of the record? (in absolute mode, this happens 00314 //when the filepos is past safe (more accurate), 00315 //but it can also happen in relative mode if the vinylpos is nearing the end 00316 //If so, change to constant mode so DJ can move the needle safely 00317 00318 if (!atRecordEnd && reportedPlayButton) 00319 { 00320 if (iVCMode == MIXXX_VCMODE_ABSOLUTE) 00321 { 00322 if ((filePosition + iLeadInTime) * 1000.0f > m_uiSafeZone && 00323 !bForceResync) //corner case: we are waiting for resync so don't enable just yet 00324 enableRecordEndMode(); 00325 } 00326 else if (iVCMode == MIXXX_VCMODE_RELATIVE || iVCMode == MIXXX_VCMODE_CONSTANT) 00327 { 00328 if (iPosition != -1 && iPosition > m_uiSafeZone) 00329 enableRecordEndMode(); 00330 } 00331 } 00332 00333 if (atRecordEnd) 00334 { 00335 //if atRecordEnd was true, maybe it no longer applies: 00336 00337 if (!reportedPlayButton) 00338 { 00339 //if we turned off play button, also disable 00340 disableRecordEndMode(); 00341 } 00342 else if (iPosition != -1 && 00343 iPosition <= m_uiSafeZone && 00344 dVinylPosition > 0 && 00345 checkSteadyPitch(dVinylPitch, filePosition) > 0.5) 00346 00347 { 00348 //if good position, and safe, and not in leadin, and steady, 00349 //disable 00350 disableRecordEndMode(); 00351 } 00352 00353 if (atRecordEnd) 00354 { 00355 //ok, it's still valid, blink 00356 if ((reportedPlayButton && (int)(filePosition * 2.0f) % 2) || 00357 (!reportedPlayButton && (int)(iPosition / 500.0f) % 2)) 00358 vinylStatus->slotSet(VINYL_STATUS_WARNING); 00359 else 00360 vinylStatus->slotSet(VINYL_STATUS_DISABLED); 00361 } 00362 } 00363 00364 //check here for position > safe, and if no record end mode, 00365 //then trigger track selection mode. just pass position to it 00366 //and ignore pitch 00367 00368 if (!atRecordEnd) 00369 { 00370 if (iPosition != -1 && iPosition > m_uiSafeZone) 00371 { 00372 //until I can figure out how to detect "track 2" on serato CD, 00373 //don't try track selection 00374 if (!m_bCDControl) 00375 { 00376 if (!bTrackSelectMode) 00377 { 00378 qDebug() << "position greater than safe, select mode" << iPosition << m_uiSafeZone; 00379 bTrackSelectMode = true; 00380 togglePlayButton(FALSE); 00381 resetSteadyPitch(0.0f, 0.0f); 00382 controlScratch->slotSet(0.0f); 00383 } 00384 doTrackSelection(true, dVinylPitch, iPosition); 00385 } 00386 00387 //hm I wonder if track will keep playing while this happens? 00388 //not sure what we want to do here... probably enforce 00389 //stopped deck. 00390 00391 //but if constant mode... nah, force stop. 00392 continue; 00393 } 00394 else 00395 { 00396 //so we're not unsafe.... but 00397 //if no position, but we were in select mode, do select mode 00398 if (iPosition == -1 && bTrackSelectMode) 00399 { 00400 //qDebug() << "no position, but were in select mode"; 00401 doTrackSelection(false, dVinylPitch, iPosition); 00402 00403 //again, force stop? 00404 continue; 00405 } 00406 else if (bTrackSelectMode) 00407 { 00408 //qDebug() << "discontinuing select mode, selecting track"; 00409 if (trackLoader == NULL) 00410 trackLoader = new ControlObjectThread(ControlObject::getControl(ConfigKey(m_group,"LoadSelectedTrack"))); 00411 00412 if (!trackLoader) 00413 qDebug() << "ERROR: couldn't get track loading object?"; 00414 else 00415 { 00416 trackLoader->slotSet(1.0); 00417 trackLoader->slotSet(0.0); //I think I have to do this... 00418 } 00419 //if position is known and safe then no track select mode 00420 bTrackSelectMode = false; 00421 } 00422 } 00423 } 00424 00425 if (iVCMode == MIXXX_VCMODE_CONSTANT) 00426 { 00427 //when we enabled constant mode we set the rate slider 00428 //now we just either set scratch val to 0 (stops playback) 00429 //or 1 (plays back at that rate) 00430 00431 if (reportedPlayButton) 00432 controlScratch->slotSet(rateDir->get() * (rateSlider->get() * fRateRange) + 1.0f); 00433 else 00434 controlScratch->slotSet(0.0f); 00435 00436 //is there any reason we'd need to do anything else? 00437 continue; 00438 } 00439 00440 //CONSTANT MODE NO LONGER APPLIES... 00441 00442 // When there's a timecode signal available 00443 // This is set when we analyze samples (no need for lock I think) 00444 if(bHaveSignal) 00445 { 00446 //POSITION: MAYBE PITCH: YES 00447 00448 //We have pitch, but not position. so okay signal but not great (scratching / cueing?) 00449 //qDebug() << "Pitch" << dVinylPitch; 00450 00451 if (iPosition != -1) 00452 { 00453 //POSITION: YES PITCH: YES 00454 //add a value to the pitch ring (for averaging / smoothing the pitch) 00455 //qDebug() << fabs(((dVinylPosition - dOldPos) * (dVinylPitch / fabs(dVinylPitch)))); 00456 00457 //save the absolute amount of drift for when we need to estimate vinyl position 00458 dDriftAmt = dVinylPosition - filePosition; 00459 00460 //qDebug() << "drift" << dDriftAmt; 00461 00462 if (bForceResync) 00463 { 00464 //if forceresync was set but we're no longer absolute, 00465 //it no longer applies 00466 //if we're in relative mode then we'll do a sync 00467 //because it might select a cue 00468 if (iVCMode == MIXXX_VCMODE_ABSOLUTE || (iVCMode == MIXXX_VCMODE_RELATIVE && cueing->get())) 00469 { 00470 syncPosition(); 00471 resetSteadyPitch(dVinylPitch, dVinylPosition); 00472 } 00473 bForceResync = false; 00474 } 00475 else if (fabs(dVinylPosition - filePosition) > 0.1f && 00476 dVinylPosition < -2.0f) 00477 { 00478 //At first I thought it was a bug to resync to leadin in relative mode, 00479 //but after using it that way it's actually pretty convenient. 00480 //qDebug() << "Vinyl leadin"; 00481 syncPosition(); 00482 resetSteadyPitch(dVinylPitch, dVinylPosition); 00483 if (uiUpdateTime(filePosition)) 00484 rateSlider->slotSet(rateDir->get() * (fabs(dVinylPitch) - 1.0f) / fRateRange); 00485 } 00486 else if (iVCMode == MIXXX_VCMODE_ABSOLUTE && (fabs(dVinylPosition - dOldPos) >= 15.0f)) 00487 { 00488 //If the position from the timecode is more than a few seconds off, resync the position. 00489 //qDebug() << "resync position (>15.0 sec)"; 00490 //qDebug() << dVinylPosition << dOldPos << dVinylPosition - dOldPos; 00491 syncPosition(); 00492 resetSteadyPitch(dVinylPitch, dVinylPosition); 00493 } 00494 else if (iVCMode == MIXXX_VCMODE_ABSOLUTE && m_bNeedleSkipPrevention && 00495 fabs(dVinylPosition - dOldPos) > 0.4 && 00496 (tSinceSteadyPitch.elapsed() < 400 || reportedPlayButton)) 00497 { 00498 //red alert, moved wrong direction or jumped forward a lot, 00499 //and we were just playing nicely... 00500 //move to constant mode and keep playing 00501 qDebug() << "WARNING: needle skip detected!:"; 00502 qDebug() << filePosition << dOldFilePos << dVinylPosition << dOldPos; 00503 qDebug() << (dVinylPosition - dOldPos) * (dVinylPitch / fabs(dVinylPitch)); 00504 //try setting the rate to the steadypitch value 00505 enableConstantMode(dOldSteadyPitch); 00506 vinylStatus->slotSet(VINYL_STATUS_ERROR); 00507 } 00508 else if (iVCMode == MIXXX_VCMODE_ABSOLUTE && m_bCDControl && 00509 fabs(dVinylPosition - dOldPos) >= 0.1f) 00510 { 00511 //qDebug() << "CDJ resync position (>0.1 sec)"; 00512 syncPosition(); 00513 resetSteadyPitch(dVinylPitch, dVinylPosition); 00514 } 00515 else if (playPos->get() >= 1.0 && dVinylPitch > 0) 00516 { 00517 //end of track, force stop 00518 togglePlayButton(false); 00519 resetSteadyPitch(0.0f, 0.0f); 00520 controlScratch->slotSet(0.0f); 00521 ringPos = 0; 00522 ringFilled = 0; 00523 continue; 00524 } 00525 else 00526 { 00527 togglePlayButton(checkSteadyPitch(dVinylPitch, filePosition) > 0.5); 00528 } 00529 00530 //Calculate how much the vinyl's position has drifted from it's timecode and compensate for it. 00531 //(This is caused by the manufacturing process of the vinyl.) 00532 if (fabs(dDriftAmt) > 0.1 && fabs(dDriftAmt) < 5.0) { 00533 dDriftControl = dDriftAmt; 00534 } else { 00535 dDriftControl = 0.0; 00536 } 00537 00538 //if we hit the end of the ring, loop around 00539 dOldPos = dVinylPosition; 00540 } 00541 else 00542 { 00543 //POSITION: NO PITCH: YES 00544 //if we don't have valid position, we're not playing so reset time to current 00545 //estimate vinyl position 00546 00547 if (playPos->get() >= 1.0 && dVinylPitch > 0) 00548 { 00549 //end of track, force stop 00550 togglePlayButton(false); 00551 resetSteadyPitch(0.0f, 0.0f); 00552 controlScratch->slotSet(0.0f); 00553 ringPos = 0; 00554 ringFilled = 0; 00555 continue; 00556 } 00557 00558 if (iVCMode == MIXXX_VCMODE_ABSOLUTE && 00559 fabs(dVinylPitch) < 0.05 && 00560 fabs(dDriftAmt) >= 0.3f) 00561 { 00562 //qDebug() << "slow, out of sync, syncing position"; 00563 syncPosition(); 00564 } 00565 00566 dOldPos = filePosition + dDriftAmt; 00567 00568 if (dVinylPitch > 0.2) 00569 { 00570 togglePlayButton(checkSteadyPitch(dVinylPitch, filePosition) > 0.5); 00571 } 00572 } 00573 00574 //playbutton status may have changed 00575 reportedPlayButton = playButton->get(); 00576 00577 if (reportedPlayButton) 00578 { 00579 //only add to the ring if pitch is stable 00580 dPitchRing[ringPos] = dVinylPitch; 00581 if(ringFilled < RING_SIZE) 00582 ringFilled++; 00583 ringPos = (ringPos + 1) % RING_SIZE; 00584 } 00585 else 00586 { 00587 //reset ring if pitch isn't steady 00588 ringPos = 0; 00589 ringFilled = 0; 00590 } 00591 00592 //only smooth when we have good position (no smoothing for scratching) 00593 double averagePitch = 0.0f; 00594 if (iPosition != -1 && reportedPlayButton) 00595 { 00596 for (int i=0; i<ringFilled; i++) 00597 { 00598 averagePitch += dPitchRing[i]; 00599 } 00600 averagePitch /= ringFilled; 00601 //round out some of the noise 00602 averagePitch = (double)(int)(averagePitch * 10000.0f); 00603 averagePitch /= 10000.0f; 00604 } 00605 else 00606 averagePitch = dVinylPitch; 00607 00608 if (iVCMode == MIXXX_VCMODE_ABSOLUTE) 00609 { 00610 controlScratch->slotSet(dVinylPitch + dDriftControl); 00611 if (iPosition != -1 && reportedPlayButton && uiUpdateTime(filePosition)) 00612 { 00613 rateSlider->slotSet(rateDir->get() * (fabs(dVinylPitch + dDriftControl) - 1.0f) / fRateRange); 00614 dUiUpdateTime = filePosition; 00615 } 00616 } 00617 else if (iVCMode == MIXXX_VCMODE_RELATIVE) 00618 { 00619 controlScratch->slotSet(averagePitch); 00620 if (iPosition != -1 && reportedPlayButton && uiUpdateTime(filePosition)) 00621 { 00622 rateSlider->slotSet(rateDir->get() * (fabs(averagePitch) - 1.0f) / fRateRange); 00623 dUiUpdateTime = filePosition; 00624 } 00625 } 00626 00627 dOldPitch = dVinylPitch; 00628 dOldFilePos = filePosition; 00629 } 00630 else //No pitch data available (the needle is up/stopped.... or *really* crappy signal) 00631 { 00632 //POSITION: NO PITCH: NO 00633 //if it's been a long time, we're stopped. 00634 //if it hasn't been long, and we're preventing needle skips, 00635 //let the track play a wee bit more before deciding we've stopped 00636 00637 rateSlider->slotSet(0.0f); 00638 00639 if (iVCMode == MIXXX_VCMODE_ABSOLUTE && 00640 fabs(dVinylPosition - filePosition) >= 0.1f) 00641 { 00642 //qDebug() << "stopped, out of sync, syncing position"; 00643 syncPosition(); 00644 } 00645 00646 if(fabs(filePosition - dOldFilePos) >= 0.1 || 00647 !m_bNeedleSkipPrevention || 00648 filePosition == dOldFilePos) 00649 { 00650 //We are not playing any more 00651 togglePlayButton(FALSE); 00652 resetSteadyPitch(0.0f, 0.0f); 00653 controlScratch->slotSet(0.0f); 00654 //resetSteadyPitch(dVinylPitch, filePosition); 00655 //Notify the UI that the timecode quality is garbage/missing. 00656 m_fTimecodeQuality = 0.0f; 00657 ringPos = 0; 00658 ringFilled = 0; 00659 iQualPos = 0; 00660 iQualFilled = 0; 00661 bForceResync=true; 00662 vinylStatus->slotSet(VINYL_STATUS_OK); 00663 } 00664 } 00665 } 00666 } 00667 00668 void VinylControlXwax::enableRecordEndMode() 00669 { 00670 qDebug() << "record end, setting constant mode"; 00671 vinylStatus->slotSet(VINYL_STATUS_WARNING); 00672 enableConstantMode(); 00673 atRecordEnd = true; 00674 } 00675 00676 void VinylControlXwax::enableConstantMode() 00677 { 00678 iOldMode = iVCMode; 00679 iVCMode = MIXXX_VCMODE_CONSTANT; 00680 mode->slotSet((double)iVCMode); 00681 togglePlayButton(true); 00682 double rate = controlScratch->get(); 00683 rateSlider->slotSet(rateDir->get() * (fabs(rate) - 1.0f) / fRateRange); 00684 controlScratch->slotSet(rate); 00685 } 00686 00687 void VinylControlXwax::enableConstantMode(double rate) 00688 { 00689 iOldMode = iVCMode; 00690 iVCMode = MIXXX_VCMODE_CONSTANT; 00691 mode->slotSet((double)iVCMode); 00692 togglePlayButton(true); 00693 rateSlider->slotSet(rateDir->get() * (fabs(rate) - 1.0f) / fRateRange); 00694 controlScratch->slotSet(rate); 00695 } 00696 00697 void VinylControlXwax::disableRecordEndMode() 00698 { 00699 vinylStatus->slotSet(VINYL_STATUS_OK); 00700 atRecordEnd = false; 00701 iVCMode = MIXXX_VCMODE_RELATIVE; 00702 mode->slotSet((double)iVCMode); 00703 } 00704 00705 void VinylControlXwax::togglePlayButton(bool on) 00706 { 00707 if (bIsEnabled && (playButton->get() > 0) != on) { 00708 //switching from on to off -- restart counter for checking needleskip 00709 if (!on) 00710 tSinceSteadyPitch.restart(); 00711 playButton->slotSet((float)on); //and we all float on all right 00712 } 00713 } 00714 00715 void VinylControlXwax::doTrackSelection(bool valid_pos, double pitch, double position) 00716 { 00717 //compare positions, fabricating if we don't have position data, and 00718 //move the selector every so often 00719 //track will be selected when the needle is moved back to play area 00720 //track selection can be cancelled by loading a track manually 00721 00722 const int SELECT_INTERVAL = 150; 00723 const double NOPOS_SPEED = 0.50; 00724 00725 if (trackSelector == NULL) 00726 { 00727 //this isn't done in the constructor because this object 00728 //doesn't seem to be created yet 00729 trackSelector = new ControlObjectThread(ControlObject::getControl(ConfigKey("[Playlist]","SelectTrackKnob"))); 00730 if (trackSelector == NULL) 00731 { 00732 qDebug() << "Warning: Track Selector control object NULL"; 00733 return; 00734 } 00735 } 00736 00737 if (!valid_pos) 00738 { 00739 if (fabs(pitch) > 0.1) 00740 { 00741 //how to estimate how far the record has moved when we don't have a valid 00742 //position and no mp3 track to compare with??? just add a bullshit amount? 00743 dCurTrackSelectPos += pitch * NOPOS_SPEED; //MADE UP CONSTANT, needs to be based on frames per second I think 00744 } 00745 else //too slow, do nothing 00746 return; 00747 } 00748 else 00749 dCurTrackSelectPos = position; //if we have valid pos, use it 00750 00751 00752 //we have position or at least record is moving, so check if we should 00753 //change location 00754 00755 if (fabs(dCurTrackSelectPos - dLastTrackSelectPos) > 10.0 * 1000) 00756 { 00757 //yeah probably not a valid value 00758 //qDebug() << "large change in track position, resetting"; 00759 dLastTrackSelectPos = dCurTrackSelectPos; 00760 } 00761 else if (fabs(dCurTrackSelectPos - dLastTrackSelectPos) > SELECT_INTERVAL) 00762 { 00763 //only adjust by one at a time. It's no help jumping around 00764 trackSelector->slotSet((int)(dCurTrackSelectPos - dLastTrackSelectPos) / fabs(dCurTrackSelectPos - dLastTrackSelectPos)); 00765 dLastTrackSelectPos = dCurTrackSelectPos; 00766 } 00767 } 00768 00769 void VinylControlXwax::resetSteadyPitch(double pitch, double time) 00770 { 00771 dSteadyPitch = pitch; 00772 dSteadyPitchTime = time; 00773 } 00774 00775 double VinylControlXwax::checkSteadyPitch(double pitch, double time) 00776 { 00777 //return length of time pitch has been steady, 0 if not steady 00778 const double PITCH_THRESHOLD = 0.07f; 00779 00780 if (time < dSteadyPitchTime) //bad values, often happens during resync 00781 { 00782 if (loopEnabled->get()) 00783 { 00784 //if looping, fake it since we don't know where the loop 00785 //actually is 00786 if (fabs(pitch - dSteadyPitch) < PITCH_THRESHOLD) 00787 { 00788 dSteadyPitchTime = time - 2.0; 00789 return 2.0; 00790 } 00791 } 00792 else 00793 { 00794 resetSteadyPitch(pitch, time); 00795 return 0.0; 00796 } 00797 } 00798 00799 if (fabs(pitch - dSteadyPitch) < PITCH_THRESHOLD) 00800 { 00801 if (time - dSteadyPitchTime > 2.0) 00802 { 00803 dSteadyPitch = pitch; 00804 dOldSteadyPitch = dSteadyPitch; //this was a known-good value 00805 dSteadyPitchTime += 1.0; 00806 } 00807 return time - dSteadyPitchTime; 00808 } 00809 00810 //else 00811 resetSteadyPitch(pitch, time); 00812 return 0.0; 00813 } 00814 00815 //Synchronize Mixxx's position to the position of the timecoded vinyl. 00816 void VinylControlXwax::syncPosition() 00817 { 00818 //qDebug() << "sync position" << dVinylPosition / duration->get(); 00819 vinylSeek->slotSet(dVinylPosition / fTrackDuration); //VinylPos in seconds / total length of song 00820 } 00821 00822 bool VinylControlXwax::checkEnabled(bool was, bool is) 00823 { 00824 // if we're not enabled, but the last object was, try turning ourselves on 00825 // XXX: is this just a race that's working right now? 00826 if (!is && wantenabled->get() > 0) 00827 { 00828 enabled->slotSet(true); 00829 wantenabled->slotSet(false); //don't try to do this over and over 00830 return true; //optimism! 00831 } 00832 if (was != is) 00833 { 00834 //we reset the scratch value, but we don't reset the rate slider. 00835 //This means if we are playing, and we disable vinyl control, 00836 //the track will keep playing at the previous rate. 00837 //This allows for single-deck control, dj handoffs, etc. 00838 00839 togglePlayButton(playButton->get() || fabs(controlScratch->get()) > 0.05f); 00840 controlScratch->slotSet(rateDir->get() * (rateSlider->get() * fRateRange) + 1.0f); 00841 resetSteadyPitch(0.0f, 0.0f); 00842 bForceResync = true; 00843 if (!was) 00844 dOldFilePos = 0.0f; 00845 iVCMode = mode->get(); 00846 atRecordEnd = false; 00847 } 00848 if (is && !was) 00849 { 00850 vinylStatus->slotSet(VINYL_STATUS_OK); 00851 } 00852 if (!is) 00853 vinylStatus->slotSet(VINYL_STATUS_DISABLED); 00854 00855 return is; 00856 } 00857 00858 bool VinylControlXwax::isEnabled() 00859 { 00860 return bIsEnabled; 00861 } 00862 00863 void VinylControlXwax::ToggleVinylControl(bool enable) 00864 { 00865 bIsEnabled = enable; 00866 } 00867 00868 bool VinylControlXwax::uiUpdateTime(double now) 00869 { 00870 if (dUiUpdateTime > now || now - dUiUpdateTime > 0.05) 00871 { 00872 dUiUpdateTime = now; 00873 return true; 00874 } 00875 return false; 00876 } 00877 00878 void VinylControlXwax::establishQuality(bool quality_sample) 00879 { 00880 bQualityRing[iQualPos] = quality_sample; 00881 if(iQualFilled < QUALITY_RING_SIZE) 00882 { 00883 iQualFilled++; 00884 } 00885 00886 int quality = 0; 00887 for (int i=0; i<iQualFilled; i++) 00888 { 00889 if (bQualityRing[i]) 00890 quality++; 00891 } 00892 00893 //qDebug() << "quality" << m_fTimecodeQuality; 00894 m_fTimecodeQuality = (float)quality / (float)iQualFilled; 00895 00896 iQualPos = (iQualPos + 1) % QUALITY_RING_SIZE; 00897 } 00898 00899 float VinylControlXwax::getAngle() 00900 { 00901 float when; 00902 float pos = timecoder_get_position(&timecoder, &when); 00903 00904 if (pos == -1) 00905 return -1.0; 00906 00907 pos /= 1000.0; 00908 00909 float rps = timecoder_revs_per_sec(&timecoder); 00910 //invert angle to make vinyl spin direction correct 00911 return 360 - ((int)(pos * 360.0 * rps) % 360); 00912 }