Mixxx

/home/maxime/Projets/Mixxx/1.10/mixxx/src/vinylcontrol/vinylcontrolxwax.cpp

Go to the documentation of this file.
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines