static int sync_to_timecode(struct player_t *pl) { float when; double tcpos; signed int timecode; timecode = timecoder_get_position(pl->timecoder, &when); /* Instruct the caller to disconnect the timecoder if the needle * is outside the 'safe' zone of the record */ if(timecode != -1 && timecode > timecoder_get_safe(pl->timecoder)) return -1; /* If the timecoder is alive, use the pitch from the sine wave */ pl->pitch = timecoder_get_pitch(pl->timecoder); /* If we can read an absolute time from the timecode, then use it */ if(timecode == -1) pl->target_valid = 0; else { tcpos = (double)timecode / timecoder_get_resolution(pl->timecoder); pl->target_position = tcpos + pl->pitch * when; pl->target_valid = 1; } return 0; }
float ofApp::getAbsolute() { float when; float pos = timecoder_get_position(&timecoder, &when); if(pos != -1) { absolutePosition = pos; } else { float pitch = getPitch(); absolutePosition += (bufferSize / 45.7) * pitch; // not sure where the 45.7 comes from... } return absolutePosition; }
float VinylControlXwax::getAngle() { double when; float pos = timecoder_get_position(&timecoder, &when); if (pos == -1) return -1.0; float rps = timecoder_revs_per_sec(&timecoder); //invert angle to make vinyl spin direction correct return 360 - ((int)(when * 360.0 * rps) % 360); }
void ofxXwax::update(float* input) { // convert from -1 to 1 to a 16-byte signed short integer for (int i = 0; i < bufferSize * nChannels; i++) { shortBuffer[i] = input[i] * (1<<15); } timecoder_submit(&timecoder, &shortBuffer[0], bufferSize); float when; float curPosition = timecoder_get_position(&timecoder, &when); pitch = timecoder_get_pitch(&timecoder); velocity = (msPerSecond * bufferSize / sampleRate) * pitch; relativePosition += velocity; if(curPosition == invalidPosition) { absoluteValid = false; absolutePosition += velocity; } else { absoluteValid = true; absolutePosition = curPosition; } }
void VinylControlXwax::analyzeSamples(CSAMPLE* pSamples, size_t nFrames) { ScopedTimer t("VinylControlXwax::analyzeSamples"); CSAMPLE gain = m_pVinylControlInputGain->get(); const int kChannels = 2; // We only support amplifying with the VC pre-amp. if (gain < 1.0f) { gain = 1.0f; } size_t samplesSize = nFrames * kChannels; if (samplesSize > m_workBufferSize) { delete [] m_pWorkBuffer; m_pWorkBuffer = new short[samplesSize]; m_workBufferSize = samplesSize; } // Convert CSAMPLE samples to shorts, preventing overflow. for (int i = 0; i < static_cast<int>(samplesSize); ++i) { CSAMPLE sample = pSamples[i] * gain * SAMPLE_MAX; if (sample > SAMPLE_MAX) { m_pWorkBuffer[i] = SAMPLE_MAX; } else if (sample < SAMPLE_MIN) { m_pWorkBuffer[i] = SAMPLE_MIN; } else { m_pWorkBuffer[i] = static_cast<short>(sample); } } // Submit the samples to the xwax timecode processor. The size argument is // in stereo frames. timecoder_submit(&timecoder, m_pWorkBuffer, nFrames); bool bHaveSignal = fabs(pSamples[0]) + fabs(pSamples[1]) > kMinSignal; //qDebug() << "signal?" << bHaveSignal; //TODO: Move all these config object get*() calls to an "updatePrefs()" function, // and make that get called when any options get changed in the preferences dialog, rather than // polling everytime we get a buffer. // Check if vinyl control is enabled... m_bIsEnabled = enabled == NULL ? false : checkEnabled(m_bIsEnabled, enabled->get()); if(bHaveSignal) { // Always analyze the input samples m_iPosition = timecoder_get_position(&timecoder, NULL); //Notify the UI if the timecode quality is good establishQuality(m_iPosition != -1); } //are we even playing and enabled at all? if (!m_bIsEnabled) return; double dVinylPitch = timecoder_get_pitch(&timecoder); // Has a new track been loaded? Currently we use track duration which is // integer seconds in the song. However, for calculations we need the // higher-accuracy duration found by dividing the track samples by the // samplerate. // TODO(XXX): we should really sync on all track changes // TODO(rryan): Should we calculate the true duration to check if it // changed? It's just an extra division by trackSampleRate. double duration_inaccurate = duration->get(); if (duration_inaccurate != m_dOldDurationInaccurate) { m_bForceResync = true; m_bTrackSelectMode = false; //just in case m_dOldDurationInaccurate = duration_inaccurate; m_dOldDuration = trackSamples->get() / 2 / trackSampleRate->get(); // we were at record end, so turn it off and restore mode if(m_bAtRecordEnd) { disableRecordEndMode(); if (m_iOldVCMode == MIXXX_VCMODE_CONSTANT) m_iVCMode = MIXXX_VCMODE_RELATIVE; else m_iVCMode = m_iOldVCMode; } } // make sure m_dVinylPosition only has good values if (m_iPosition != -1) { m_dVinylPosition = static_cast<double>(m_iPosition) / 1000.0 - m_iLeadInTime; } // Initialize drift control to zero in case we don't get any position data // to calculate it with. double dDriftControl = 0.0; // Get the playback position in the file in seconds. double filePosition = playPos->get() * m_dOldDuration; int reportedMode = mode->get(); bool reportedPlayButton = playButton->get(); if (m_iVCMode != reportedMode) { //if we are playing, don't allow change //to absolute mode (would cause sudden track skip) if (reportedPlayButton && reportedMode == MIXXX_VCMODE_ABSOLUTE) { m_iVCMode = MIXXX_VCMODE_RELATIVE; mode->slotSet((double)m_iVCMode); } else { // go ahead and switch m_iVCMode = reportedMode; if (reportedMode == MIXXX_VCMODE_ABSOLUTE) { m_bForceResync = true; } } //if we are out of error mode... if (vinylStatus->get() == VINYL_STATUS_ERROR && m_iVCMode == MIXXX_VCMODE_RELATIVE) { vinylStatus->slotSet(VINYL_STATUS_OK); } } //if looping has been enabled, don't allow absolute mode if (loopEnabled->get() && m_iVCMode == MIXXX_VCMODE_ABSOLUTE) { m_iVCMode = MIXXX_VCMODE_RELATIVE; mode->slotSet((double)m_iVCMode); } // Don't allow cueing mode to be enabled in absolute mode. if (m_iVCMode == MIXXX_VCMODE_ABSOLUTE && cueing->get() != MIXXX_RELATIVE_CUE_OFF) { cueing->set(MIXXX_RELATIVE_CUE_OFF); } //are we newly playing near the end of the record? (in absolute mode, this happens //when the filepos is past safe (more accurate), //but it can also happen in relative mode if the vinylpos is nearing the end //If so, change to constant mode so DJ can move the needle safely if (!m_bAtRecordEnd && reportedPlayButton) { if (m_iVCMode == MIXXX_VCMODE_ABSOLUTE && (filePosition + m_iLeadInTime) * 1000.0 > m_uiSafeZone && !m_bForceResync) { // corner case: we are waiting for resync so don't enable just yet enableRecordEndMode(); } else if (m_iVCMode != MIXXX_VCMODE_ABSOLUTE && m_iPosition != -1 && m_iPosition > static_cast<int>(m_uiSafeZone)) { enableRecordEndMode(); } } if (m_bAtRecordEnd) { //if m_bAtRecordEnd was true, maybe it no longer applies: if (!reportedPlayButton) { //if we turned off play button, also disable disableRecordEndMode(); } else if (m_iPosition != -1 && m_iPosition <= static_cast<int>(m_uiSafeZone) && m_dVinylPosition > 0 && checkSteadyPitch(dVinylPitch, filePosition) > 0.5) { //if good position, and safe, and not in leadin, and steady, //disable disableRecordEndMode(); } if (m_bAtRecordEnd) { //ok, it's still valid, blink if ((reportedPlayButton && (int)(filePosition * 2.0) % 2) || (!reportedPlayButton && (int)(m_iPosition / 500.0) % 2)) vinylStatus->slotSet(VINYL_STATUS_WARNING); else vinylStatus->slotSet(VINYL_STATUS_DISABLED); } } //check here for position > safe, and if no record end mode, //then trigger track selection mode. just pass position to it //and ignore pitch if (!m_bAtRecordEnd) { if (m_iPosition != -1 && m_iPosition > static_cast<int>(m_uiSafeZone)) { //only enable if pitch is steady, though. Heavy scratching can //produce crazy results and trigger this mode if (m_bTrackSelectMode || checkSteadyPitch(dVinylPitch, filePosition) > 0.1) { //until I can figure out how to detect "track 2" on serato CD, //don't try track selection if (!m_bCDControl) { if (!m_bTrackSelectMode) { qDebug() << "position greater than safe, select mode" << m_iPosition << m_uiSafeZone; m_bTrackSelectMode = true; togglePlayButton(false); resetSteadyPitch(0.0, 0.0); controlScratch->slotSet(0.0); } doTrackSelection(true, dVinylPitch, m_iPosition); } //hm I wonder if track will keep playing while this happens? //not sure what we want to do here... probably enforce //stopped deck. //but if constant mode... nah, force stop. return; } //if it's not steady yet we process as normal } else { //so we're not unsafe.... but //if no position, but we were in select mode, do select mode if (m_iPosition == -1 && m_bTrackSelectMode) { //qDebug() << "no position, but were in select mode"; doTrackSelection(false, dVinylPitch, m_iPosition); //again, force stop? return; } else if (m_bTrackSelectMode) { //qDebug() << "discontinuing select mode, selecting track"; if (m_pControlTrackLoader == NULL) m_pControlTrackLoader = new ControlObjectThread(m_group,"LoadSelectedTrack"); if (!m_pControlTrackLoader) { qDebug() << "ERROR: couldn't get track loading object?"; } else { m_pControlTrackLoader->slotSet(1.0); m_pControlTrackLoader->slotSet(0.0); //I think I have to do this... } //if position is known and safe then no track select mode m_bTrackSelectMode = false; } } } if (m_iVCMode == MIXXX_VCMODE_CONSTANT) { //when we enabled constant mode we set the rate slider //now we just either set scratch val to 0 (stops playback) //or 1 (plays back at that rate) double newScratch = reportedPlayButton ? rateDir->get() * (rateSlider->get() * rateRange->get()) + 1.0 : 0.0; controlScratch->slotSet(newScratch); //is there any reason we'd need to do anything else? return; } //CONSTANT MODE NO LONGER APPLIES... // When there's a timecode signal available // This is set when we analyze samples (no need for lock I think) if(bHaveSignal) { //POSITION: MAYBE PITCH: YES //We have pitch, but not position. so okay signal but not great (scratching / cueing?) //qDebug() << "Pitch" << dVinylPitch; if (m_iPosition != -1) { //POSITION: YES PITCH: YES //add a value to the pitch ring (for averaging / smoothing the pitch) //qDebug() << fabs(((m_dVinylPosition - m_dVinylPositionOld) * (dVinylPitch / fabs(dVinylPitch)))); //save the absolute amount of drift for when we need to estimate vinyl position m_dDriftAmt = m_dVinylPosition - filePosition; //qDebug() << "drift" << m_dDriftAmt; if (m_bForceResync) { //if forceresync was set but we're no longer absolute, //it no longer applies //if we're in relative mode then we'll do a sync //because it might select a cue if (m_iVCMode == MIXXX_VCMODE_ABSOLUTE || (m_iVCMode == MIXXX_VCMODE_RELATIVE && cueing->get())) { syncPosition(); resetSteadyPitch(dVinylPitch, m_dVinylPosition); } m_bForceResync = false; } else if (fabs(m_dVinylPosition - filePosition) > 0.1 && m_dVinylPosition < -2.0) { //At first I thought it was a bug to resync to leadin in relative mode, //but after using it that way it's actually pretty convenient. //qDebug() << "Vinyl leadin"; syncPosition(); resetSteadyPitch(dVinylPitch, m_dVinylPosition); if (uiUpdateTime(filePosition)) rateSlider->slotSet(rateDir->get() * (fabs(dVinylPitch) - 1.0) / rateRange->get()); } else if (m_iVCMode == MIXXX_VCMODE_ABSOLUTE && (fabs(m_dVinylPosition - m_dVinylPositionOld) >= 5.0)) { //If the position from the timecode is more than a few seconds off, resync the position. //qDebug() << "resync position (>15.0 sec)"; //qDebug() << m_dVinylPosition << m_dVinylPositionOld << m_dVinylPosition - m_dVinylPositionOld; syncPosition(); resetSteadyPitch(dVinylPitch, m_dVinylPosition); } else if (m_iVCMode == MIXXX_VCMODE_ABSOLUTE && m_bCDControl && fabs(m_dVinylPosition - m_dVinylPositionOld) >= 0.1) { //qDebug() << "CDJ resync position (>0.1 sec)"; syncPosition(); resetSteadyPitch(dVinylPitch, m_dVinylPosition); } else if (playPos->get() >= 1.0 && dVinylPitch > 0) { //end of track, force stop togglePlayButton(false); resetSteadyPitch(0.0, 0.0); controlScratch->slotSet(0.0); m_iPitchRingPos = 0; m_iPitchRingFilled = 0; return; } else { togglePlayButton(checkSteadyPitch(dVinylPitch, filePosition) > 0.5); } // Calculate how much the vinyl's position has drifted from it's timecode and compensate for it. // (This is caused by the manufacturing process of the vinyl.) if (m_iVCMode == MIXXX_VCMODE_ABSOLUTE && fabs(m_dDriftAmt) > 0.1 && fabs(m_dDriftAmt) < 5.0) { dDriftControl = m_dDriftAmt * .01; } else { dDriftControl = 0.0; } m_dVinylPositionOld = m_dVinylPosition; } else { //POSITION: NO PITCH: YES //if we don't have valid position, we're not playing so reset time to current //estimate vinyl position if (playPos->get() >= 1.0 && dVinylPitch > 0) { //end of track, force stop togglePlayButton(false); resetSteadyPitch(0.0, 0.0); controlScratch->slotSet(0.0); m_iPitchRingPos = 0; m_iPitchRingFilled = 0; return; } if (m_iVCMode == MIXXX_VCMODE_ABSOLUTE && fabs(dVinylPitch) < 0.05 && fabs(m_dDriftAmt) >= 0.3) { //qDebug() << "slow, out of sync, syncing position"; syncPosition(); } m_dVinylPositionOld = filePosition + m_dDriftAmt; if (dVinylPitch > 0.2) { togglePlayButton(checkSteadyPitch(dVinylPitch, filePosition) > 0.5); } } //playbutton status may have changed reportedPlayButton = playButton->get(); if (reportedPlayButton) { // Only add to the ring if pitch is stable m_pPitchRing[m_iPitchRingPos] = dVinylPitch; if (m_iPitchRingFilled < m_iPitchRingSize) { m_iPitchRingFilled++; } m_iPitchRingPos = (m_iPitchRingPos + 1) % m_iPitchRingSize; } else { // Reset ring if pitch isn't steady m_iPitchRingPos = 0; m_iPitchRingFilled = 0; } //only smooth when we have good position (no smoothing for scratching) double averagePitch = 0.0; if (m_iPosition != -1 && reportedPlayButton) { for (int i = 0; i < m_iPitchRingFilled; ++i) { averagePitch += m_pPitchRing[i]; } averagePitch /= m_iPitchRingFilled; // Round out some of the noise averagePitch = round(averagePitch * 10000.0); averagePitch /= 10000.0; } else { averagePitch = dVinylPitch; } controlScratch->slotSet(averagePitch + dDriftControl); if (m_iPosition != -1 && reportedPlayButton && uiUpdateTime(filePosition)) { double true_pitch = averagePitch + dDriftControl; double pitch_difference = true_pitch - m_dDisplayPitch; // The true pitch can show a misleading amount of variance -- // differences of .1% or less can show up as 1 or 2 bpm changes. // Therefore we react slowly to bpm changes to show a more steady // number to the user. if (fabs(pitch_difference) > 0.5) { // For large changes in pitch (start/stop, usually), immediately // update the display. m_dDisplayPitch = true_pitch; } else if (fabs(pitch_difference) > 0.005) { // For medium changes in pitch, take 4 callback loops to // converge on the correct amount. m_dDisplayPitch += pitch_difference * .25; } else { // For extremely small changes, converge very slowly. m_dDisplayPitch += pitch_difference * .01; } rateSlider->slotSet(rateDir->get() * (m_dDisplayPitch - 1.0) / rateRange->get()); m_dUiUpdateTime = filePosition; } m_dOldFilePos = filePosition; } else { // No pitch data available (the needle is up/stopped.... or *really* // crappy signal) //POSITION: NO PITCH: NO //if it's been a long time, we're stopped. //if it hasn't been long, //let the track play a wee bit more before deciding we've stopped rateSlider->slotSet(0.0); if (m_iVCMode == MIXXX_VCMODE_ABSOLUTE && fabs(m_dVinylPosition - filePosition) >= 0.1) { //qDebug() << "stopped, out of sync, syncing position"; syncPosition(); } if(fabs(filePosition - m_dOldFilePos) >= 0.1 || filePosition == m_dOldFilePos) { //We are not playing any more togglePlayButton(false); resetSteadyPitch(0.0, 0.0); controlScratch->slotSet(0.0); //resetSteadyPitch(dVinylPitch, filePosition); // Notify the UI that the timecode quality is garbage/missing. m_fTimecodeQuality = 0.0f; m_iPitchRingPos = 0; m_iPitchRingFilled = 0; m_iQualPos = 0; m_iQualFilled = 0; m_bForceResync = true; vinylStatus->slotSet(VINYL_STATUS_OK); } } }