void ofApp::audioOut(float* output, int bufferSize, int nChannels) { float sum = 0; for (int i = 0; i < bufferSize; i++){ output[i * nChannels] = floatBuffer[bufferPosition * nChannels]; output[i * nChannels + 1] = floatBuffer[bufferPosition * nChannels + 1]; curBuffer[i * nChannels] = output[i * nChannels] * (1<<15); curBuffer[i * nChannels + 1] = output[i * nChannels + 1] * (1<<15); if(i % 2 == 0) { // drop 96khz to 48khz bufferPosition++; if(bufferPosition == bufferFrames) { bufferPosition = 0; relativePosition = 0; exportXml(); } } } timecoder_submit(&timecoder, &curBuffer[0], bufferSize); relativeTtm[ttmPosition] = getRelative(); absoluteTtm[ttmPosition] = getAbsolute(); if(exporting) { wholeBuffer.push_back(absoluteTtm[ttmPosition] ); } pitchTtm[ttmPosition] = getPitch(); ttmPosition++; if(ttmPosition == relativeTtm.size()) { ttmPosition = 0; } }
static int capture(struct device_t *dv) { int r; struct alsa_t *alsa = (struct alsa_t*)dv->local; r = snd_pcm_readi(alsa->capture.pcm, alsa->capture.buf, alsa->capture.period); if (r < 0) return r; if (r < alsa->capture.period) { fprintf(stderr, "alsa: capture underrun %d/%ld.\n", r, alsa->capture.period); } if (dv->timecoder) timecoder_submit(dv->timecoder, alsa->capture.buf, r); return 0; }
static void process_deck(struct device_t *dv, jack_nframes_t nframes) { int n; jack_default_audio_sample_t *in[DEVICE_CHANNELS], *out[DEVICE_CHANNELS]; jack_nframes_t remain; struct jack_t *jack = (struct jack_t*)dv->local; for (n = 0; n < DEVICE_CHANNELS; n++) { in[n] = jack_port_get_buffer(jack->input_port[n], nframes); assert(in[n] != NULL); out[n] = jack_port_get_buffer(jack->output_port[n], nframes); assert(out[n] != NULL); } /* For large values of nframes, communicate with the timecoder and * player in smaller blocks */ remain = nframes; while (remain > 0) { signed short buf[MAX_BLOCK * DEVICE_CHANNELS]; jack_nframes_t block; if (remain < MAX_BLOCK) block = remain; else block = MAX_BLOCK; /* Timecode input */ interleave(buf, in, block); if (dv->timecoder) timecoder_submit(dv->timecoder, buf, block); /* Audio output -- handle in the same loop for finer granularity */ player_collect(dv->player, buf, block, rate); uninterleave(out, buf, block); remain -= block; } }
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); } } }
void device_submit(struct device *dv, signed short *pcm, size_t n) { assert(dv->timecoder != NULL); timecoder_submit(dv->timecoder, pcm, n); }