=== modified file 'mixxx/res/skins/LateNight1280x800-WXGA/skin.xml' --- mixxx/res/skins/LateNight1280x800-WXGA/skin.xml 2012-05-31 20:28:19 +0000 +++ mixxx/res/skins/LateNight1280x800-WXGA/skin.xml 2012-06-03 03:25:07 +0000 @@ -4215,6 +4215,7 @@ 2 true + true 0 btn_beatloop1_0125.png @@ -4233,6 +4234,11 @@ true + [Channel1],beatlooproll_0.125_activate + true + RightButton + + [Channel1],beatloop_0.125_enabled false @@ -4242,6 +4248,7 @@ 2 true + true 0 btn_beatloop1_0250.png @@ -4260,6 +4267,11 @@ true + [Channel1],beatlooproll_0.25_activate + true + RightButton + + [Channel1],beatloop_0.25_enabled false @@ -4269,6 +4281,7 @@ 2 true + true 0 btn_beatloop1_0500.png @@ -4287,6 +4300,11 @@ true + [Channel1],beatlooproll_0.5_activate + true + RightButton + + [Channel1],beatloop_0.5_enabled false @@ -4296,6 +4314,7 @@ 2 true + true 0 btn_beatloop1_1.png @@ -4314,6 +4333,11 @@ true + [Channel1],beatlooproll_1_activate + true + RightButton + + [Channel1],beatloop_1_enabled false @@ -4323,6 +4347,7 @@ 2 true + true 0 btn_beatloop1_2.png @@ -4341,6 +4366,11 @@ true + [Channel1],beatlooproll_2_activate + true + RightButton + + [Channel1],beatloop_2_enabled false @@ -4350,6 +4380,7 @@ 2 true + true 0 btn_beatloop1_4.png @@ -4368,6 +4399,11 @@ true + [Channel1],beatlooproll_4_activate + true + RightButton + + [Channel1],beatloop_4_enabled false @@ -4377,6 +4413,7 @@ 2 true + true 0 btn_beatloop1_8.png @@ -4395,6 +4432,11 @@ true + [Channel1],beatlooproll_8_activate + true + RightButton + + [Channel1],beatloop_8_enabled false @@ -4404,6 +4446,7 @@ 2 true + true 0 btn_beatloop1_16.png @@ -4422,6 +4465,11 @@ true + [Channel1],beatlooproll_16_activate + true + RightButton + + [Channel1],beatloop_16_enabled false @@ -5164,6 +5212,7 @@ 2 true + true 0 btn_beatloop2_0125.png @@ -5182,6 +5231,11 @@ true + [Channel2],beatlooproll_0.125_activate + true + RightButton + + [Channel2],beatloop_0.125_enabled false @@ -5191,6 +5245,7 @@ 2 true + true 0 btn_beatloop2_0250.png @@ -5209,6 +5264,11 @@ true + [Channel2],beatlooproll_0.25_activate + true + RightButton + + [Channel2],beatloop_0.25_enabled false @@ -5218,6 +5278,7 @@ 2 true + true 0 btn_beatloop2_0500.png @@ -5236,6 +5297,11 @@ true + [Channel2],beatlooproll_0.5_activate + true + RightButton + + [Channel2],beatloop_0.5_enabled false @@ -5245,6 +5311,7 @@ 2 true + true 0 btn_beatloop2_1.png @@ -5263,6 +5330,11 @@ true + [Channel2],beatlooproll_1_activate + true + RightButton + + [Channel2],beatloop_1_enabled false @@ -5272,6 +5344,7 @@ 2 true + true 0 btn_beatloop2_2.png @@ -5290,6 +5363,11 @@ true + [Channel2],beatlooproll_2_activate + true + RightButton + + [Channel2],beatloop_2_enabled false @@ -5299,6 +5377,7 @@ 2 true + true 0 btn_beatloop2_4.png @@ -5317,6 +5396,11 @@ true + [Channel2],beatlooproll_4_activate + true + RightButton + + [Channel2],beatloop_4_enabled false @@ -5326,6 +5410,7 @@ 2 true + true 0 btn_beatloop2_8.png @@ -5344,6 +5429,11 @@ true + [Channel2],beatlooproll_8_activate + true + RightButton + + [Channel2],beatloop_8_enabled false @@ -5353,6 +5443,7 @@ 2 true + true 0 btn_beatloop2_16.png @@ -5371,6 +5462,11 @@ true + [Channel2],beatlooproll_16_activate + true + RightButton + + [Channel2],beatloop_16_enabled false === modified file 'mixxx/src/engine/enginebuffer.cpp' --- mixxx/src/engine/enginebuffer.cpp 2012-05-05 02:36:53 +0000 +++ mixxx/src/engine/enginebuffer.cpp 2012-06-05 04:39:37 +0000 @@ -82,13 +82,19 @@ m_fRampValue(0.0), m_iRampState(ENGINE_RAMP_NONE), m_pDitherBuffer(new CSAMPLE[MAX_BUFFER_LEN]), - m_iDitherBufferReadIndex(0) { + m_iDitherBufferReadIndex(0), + m_pCrossFadeBuffer(new CSAMPLE[MAX_BUFFER_LEN]), + m_iCrossFadeSamples(0), + m_iLastBufferSize(0) { // Generate dither values for (int i = 0; i < MAX_BUFFER_LEN; ++i) { m_pDitherBuffer[i] = static_cast(rand() % 32768) / 32768.0 - 0.5; } + //zero out crossfade buffer + SampleUtil::applyGain(m_pCrossFadeBuffer, 0.0, MAX_BUFFER_LEN); + m_fLastSampleValue[0] = 0; m_fLastSampleValue[1] = 0; @@ -150,6 +156,15 @@ connect(endButton, SIGNAL(valueChanged(double)), this, SLOT(slotControlEnd(double)), Qt::DirectConnection); + + m_pSlipButton = new ControlPushButton(ConfigKey(group, "slip_enabled")); + m_pSlipButton->setButtonMode(ControlPushButton::TOGGLE); + connect(m_pSlipButton, SIGNAL(valueChanged(double)), + this, SLOT(slotControlSlip(double)), + Qt::DirectConnection); + connect(m_pSlipButton, SIGNAL(valueChangedFromEngine(double)), + this, SLOT(slotControlSlip(double)), + Qt::DirectConnection); // Actual rate (used in visuals, not for control) rateEngine = new ControlObject(ConfigKey(group, "rateEngine")); @@ -178,7 +193,7 @@ m_pRepeat = new ControlPushButton(ConfigKey(group, "repeat")); m_pRepeat->setButtonMode(ControlPushButton::TOGGLE); - + #ifdef __VINYLCONTROL__ //a midi knob to tweak the vinyl pitch for decks with crappy sliders m_pVinylPitchTweakKnob = new ControlPotmeter(ConfigKey(_group, "vinylpitchtweak"), -0.005, 0.005); @@ -344,6 +359,15 @@ void EngineBuffer::setNewPlaypos(double newpos) { //qDebug() << "engine new pos " << newpos; + + // Before seeking, read extra buffer for crossfading + CSAMPLE *fadeout; + fadeout = m_pScale->scale(0, + m_iLastBufferSize, + 0, + 0); + m_iCrossFadeSamples = m_iLastBufferSize; + SampleUtil::copyWithGain(m_pCrossFadeBuffer, fadeout, 1.0, m_iLastBufferSize); filepos_play = newpos; @@ -354,7 +378,7 @@ if (m_pScale) m_pScale->clear(); m_pReadAheadManager->notifySeek(filepos_play); - + // Must hold the engineLock while using m_engineControls m_engineLock.lock(); for (QList::iterator it = m_engineControls.begin(); @@ -449,7 +473,7 @@ // Ensure that the file position is even (remember, stereo channel files...) if (!even((int)new_playpos)) new_playpos--; - + setNewPlaypos(new_playpos); } @@ -512,6 +536,27 @@ } } +void EngineBuffer::slotControlSlip(double v) +{ + bool enabled = v > 0.0; + if (enabled == m_bSlipEnabled) { + return; + } + + m_bSlipEnabled = enabled; + + if (enabled) { + m_dSlipPosition = filepos_play; + m_dSlipRate = rate_old; + } + else + { + //TODO(owen) assuming that looping will get cancelled properly + slotControlSeekAbs(m_dSlipPosition); + } +} + + void EngineBuffer::process(const CSAMPLE *, const CSAMPLE * pOut, const int iBufferSize) { Q_ASSERT(even(iBufferSize)); @@ -544,6 +589,12 @@ &is_scratching); //qDebug() << "rate" << rate << " paused" << paused; + + // Update the slipped position + if (m_bSlipEnabled) { + m_dSlipPosition += static_cast(iBufferSize) * m_dSlipRate; + } + // Scratching always disables keylock because keylock sounds terrible // when not going at a constant rate. @@ -632,6 +683,31 @@ m_pReadAheadManager->getEffectiveVirtualPlaypositionFromLog( static_cast(filepos_play), samplesRead); } // else (bCurBufferPaused) + + //Crossfade if we just did a seek + if (m_iCrossFadeSamples > 0) + { + int i = 0; + double cross_len = 0; + if (m_iCrossFadeSamples >= iBufferSize) { + i = m_iCrossFadeSamples - iBufferSize; + cross_len = static_cast(iBufferSize) / 2.0; + } else { + cross_len = static_cast(m_iCrossFadeSamples) / 2.0; + } + + double cross_mix = 0.0; + double cross_inc = 1.0 / cross_len; + + // Do crossfade from old fadeout buffer to this new data + for (int j=0; i < m_iCrossFadeSamples; i+=2, j+=2) + { + pOutput[j] = pOutput[j] * cross_mix + m_pCrossFadeBuffer[i] * (1.0 - cross_mix); + pOutput[j+1] = pOutput[j+1] * cross_mix + m_pCrossFadeBuffer[i+1] * (1.0 - cross_mix); + cross_mix += cross_inc; + } + m_iCrossFadeSamples = 0; + } m_engineLock.lock(); QListIterator it(m_engineControls); @@ -779,8 +855,9 @@ writer << pOutput[i] << "\n"; } #endif - + m_bLastBufferPaused = bCurBufferPaused; + m_iLastBufferSize = iBufferSize; } void EngineBuffer::updateIndicators(double rate, int iBufferSize) { @@ -826,6 +903,16 @@ m_hintList.clear(); m_pReadAheadManager->hintReader(dRate, m_hintList, iSourceSamples); + + //if slipping, hint about virtual position so we're ready for it + if (m_bSlipEnabled) + { + Hint hint; + hint.length = 2048; //default length please + hint.sample = m_dSlipRate >= 0 ? m_dSlipPosition : m_dSlipPosition - 2048; + hint.priority = 1; + m_hintList.append(hint); + } QListIterator it(m_engineControls); while (it.hasNext()) { === modified file 'mixxx/src/engine/enginebuffer.h' --- mixxx/src/engine/enginebuffer.h 2012-05-10 00:21:25 +0000 +++ mixxx/src/engine/enginebuffer.h 2012-06-05 04:39:37 +0000 @@ -131,6 +131,7 @@ void slotControlEnd(double); void slotControlSeek(double); void slotControlSeekAbs(double); + void slotControlSlip(double); // Request that the EngineBuffer load a track. Since the process is // asynchronous, EngineBuffer will emit a trackLoaded signal when the load @@ -206,11 +207,19 @@ /** Used in update of playpos slider */ int m_iSamplesCalculated; int m_iUiSlowTick; - + + /** The location where the track would have been had slip not been engaged */ + double m_dSlipPosition; + /** Saved value of rate for slip mode */ + double m_dSlipRate; + /** Slip Status */ + bool m_bSlipEnabled; + ControlObject* m_pTrackSamples; ControlObject* m_pTrackSampleRate; ControlPushButton *playButton, *buttonBeatSync, *playStartButton, *stopStartButton, *stopButton, *playSyncButton; + ControlPushButton *m_pSlipButton; ControlObjectThreadMain *playButtonCOT, *playStartButtonCOT, *stopStartButtonCOT, *m_pTrackEndCOT, *stopButtonCOT; ControlObject *fwdButton, *backButton; @@ -265,6 +274,9 @@ #endif CSAMPLE* m_pDitherBuffer; unsigned int m_iDitherBufferReadIndex; + CSAMPLE* m_pCrossFadeBuffer; + int m_iCrossFadeSamples; + int m_iLastBufferSize; }; #endif === modified file 'mixxx/src/engine/loopingcontrol.cpp' --- mixxx/src/engine/loopingcontrol.cpp 2012-05-05 02:36:01 +0000 +++ mixxx/src/engine/loopingcontrol.cpp 2012-06-02 21:15:29 +0000 @@ -68,6 +68,7 @@ m_pNextBeat = ControlObject::getControl(ConfigKey(_group, "beat_next")); m_pClosestBeat = ControlObject::getControl(ConfigKey(_group, "beat_closest")); m_pTrackSamples = ControlObject::getControl(ConfigKey(_group,"track_samples")); + m_pSlipEnabled = ControlObject::getControl(ConfigKey(_group,"slip_enabled")); // Connect beatloop, which can flexibly handle different values. // Using this CO directly is meant to be used internally and by scripts, @@ -84,9 +85,15 @@ connect(pBeatLoop, SIGNAL(activateBeatLoop(BeatLoopingControl*)), this, SLOT(slotBeatLoopActivate(BeatLoopingControl*)), Qt::DirectConnection); + connect(pBeatLoop, SIGNAL(activateBeatLoopRoll(BeatLoopingControl*)), + this, SLOT(slotBeatLoopActivateRoll(BeatLoopingControl*)), + Qt::DirectConnection); connect(pBeatLoop, SIGNAL(deactivateBeatLoop(BeatLoopingControl*)), this, SLOT(slotBeatLoopDeactivate(BeatLoopingControl*)), Qt::DirectConnection); + connect(pBeatLoop, SIGNAL(deactivateBeatLoopRoll(BeatLoopingControl*)), + this, SLOT(slotBeatLoopDeactivateRoll(BeatLoopingControl*)), + Qt::DirectConnection); m_beatLoops.append(pBeatLoop); } @@ -520,10 +527,25 @@ beatLoopAlreadyActive && m_bLoopingEnabled); } +void LoopingControl::slotBeatLoopActivateRoll(BeatLoopingControl* pBeatLoopControl) { + if (!m_pTrack) { + return; + } + + //Disregard existing loops + m_pSlipEnabled->set(1); + slotBeatLoop(pBeatLoopControl->getSize(), false); +} + void LoopingControl::slotBeatLoopDeactivate(BeatLoopingControl* pBeatLoopControl) { slotReloopExit(1); } +void LoopingControl::slotBeatLoopDeactivateRoll(BeatLoopingControl* pBeatLoopControl) { + slotReloopExit(1); + m_pSlipEnabled->set(0); +} + void LoopingControl::clearActiveBeatLoop() { if (m_pActiveBeatLoop != NULL) { m_pActiveBeatLoop->deactivate(); @@ -654,6 +676,13 @@ connect(m_pToggle, SIGNAL(valueChanged(double)), this, SLOT(slotToggle(double)), Qt::DirectConnection); + + // A push-button which activates rolling beatloops + m_pActivateRoll = new ControlPushButton( + keyForControl(pGroup, "beatlooproll_%1_activate", size)); + connect(m_pActivateRoll, SIGNAL(valueChanged(double)), + this, SLOT(slotActivateRoll(double)), + Qt::DirectConnection); // An indicator control which is 1 if the beatloop is enabled and 0 if not. m_pEnabled = new ControlObject( @@ -696,6 +725,15 @@ emit(activateBeatLoop(this)); } +void BeatLoopingControl::slotActivateRoll(double v) { + //qDebug() << "slotActivateRoll" << m_dBeatLoopSize << "v" << v; + if (v > 0) { + emit(activateBeatLoopRoll(this)); + } else { + emit(deactivateBeatLoopRoll(this)); + } +} + void BeatLoopingControl::slotToggle(double v) { //qDebug() << "slotToggle" << m_dBeatLoopSize << "v" << v; if (!v) { === modified file 'mixxx/src/engine/loopingcontrol.h' --- mixxx/src/engine/loopingcontrol.h 2011-12-22 08:17:26 +0000 +++ mixxx/src/engine/loopingcontrol.h 2012-06-02 21:15:29 +0000 @@ -67,7 +67,9 @@ // beatslicing effect. void slotBeatLoop(double loopSize, bool keepStartPoint=false); void slotBeatLoopActivate(BeatLoopingControl* pBeatLoopControl); + void slotBeatLoopActivateRoll(BeatLoopingControl* pBeatLoopControl); void slotBeatLoopDeactivate(BeatLoopingControl* pBeatLoopControl); + void slotBeatLoopDeactivateRoll(BeatLoopingControl* pBeatLoopControl); void slotLoopScale(double); void slotLoopDouble(double); @@ -86,6 +88,7 @@ ControlObject* m_pCOLoopScale; ControlPushButton* m_pLoopHalveButton; ControlPushButton* m_pLoopDoubleButton; + ControlObject* m_pSlipEnabled; bool m_bLoopingEnabled; int m_iLoopEndSample; @@ -124,11 +127,14 @@ public slots: void slotLegacy(double value); void slotActivate(double value); + void slotActivateRoll(double value); void slotToggle(double value); signals: void activateBeatLoop(BeatLoopingControl*); void deactivateBeatLoop(BeatLoopingControl*); + void activateBeatLoopRoll(BeatLoopingControl*); + void deactivateBeatLoopRoll(BeatLoopingControl*); private: // Used simply to generate the beatloop_%SIZE and beatseek_%SIZE CO @@ -138,6 +144,7 @@ bool m_bActive; ControlPushButton* m_pLegacy; ControlPushButton* m_pActivate; + ControlPushButton* m_pActivateRoll; ControlPushButton* m_pToggle; ControlObject* m_pEnabled; }; === modified file 'mixxx/src/engine/readaheadmanager.cpp' --- mixxx/src/engine/readaheadmanager.cpp 2011-12-22 22:25:45 +0000 +++ mixxx/src/engine/readaheadmanager.cpp 2012-06-05 04:42:25 +0000 @@ -4,15 +4,19 @@ #include #include "engine/readaheadmanager.h" +#include "sampleutil.h" #include "mathstuff.h" #include "engine/enginecontrol.h" #include "cachingreader.h" - ReadAheadManager::ReadAheadManager(CachingReader* pReader) : m_iCurrentPosition(0), + m_pCrossFadeBuffer(new CSAMPLE[MAX_BUFFER_LEN]), m_pReader(pReader) { + + //zero out crossfade buffer + SampleUtil::applyGain(m_pCrossFadeBuffer, 0.0, MAX_BUFFER_LEN); } ReadAheadManager::~ReadAheadManager() { @@ -35,6 +39,7 @@ next_loop.second = m_sEngineControls[0]->nextTrigger(dRate, m_iCurrentPosition, 0, 0); + int preloop_samples = 0; if (next_loop.second != kNoTrigger) { int samples_available; @@ -45,23 +50,24 @@ } samples_needed = math_max(0, math_min(samples_needed, samples_available)); + if (in_reverse) { + preloop_samples = m_iCurrentPosition - next_loop.second; + } else { + preloop_samples = next_loop.second - m_iCurrentPosition; + } + } if (in_reverse) { start_sample = m_iCurrentPosition - samples_needed; - /*if (start_sample < 0) { - samples_needed = math_max(0, samples_needed + start_sample); - start_sample = 0; - }*/ } // Sanity checks - //Q_ASSERT(start_sample >= 0); Q_ASSERT(samples_needed >= 0); int samples_read = m_pReader->read(start_sample, samples_needed, base_buffer); - + if (samples_read != samples_needed) qDebug() << "didn't get what we wanted" << samples_read << samples_needed; @@ -85,10 +91,42 @@ if (loop_target != kNoTrigger && ((in_reverse && m_iCurrentPosition <= loop_trigger) || (!in_reverse && m_iCurrentPosition >= loop_trigger))) { + m_iCurrentPosition = loop_target; + + if (in_reverse) { + m_iCurrentPosition += preloop_samples; + } else { + m_iCurrentPosition -= preloop_samples; + } + + int looping_samples_read = m_pReader->read(m_iCurrentPosition, samples_read, + m_pCrossFadeBuffer); + + if (looping_samples_read != samples_read) { + qDebug() << "ERROR: Couldn't get samples for crossfade? (should assert?)"; + } + + // Add log entry so we don't notify seeks + if (in_reverse) { + m_iCurrentPosition -= looping_samples_read; + } else { + m_iCurrentPosition += looping_samples_read; + } + addReadLogEntry(loop_target, m_iCurrentPosition); + + //do crossfade from the current buffer into the new loop beginning + double mix_amount = 0.0; + double mix_inc = 2.0 / static_cast(samples_read); + for (int i=0; i m_readAheadLog; int m_iCurrentPosition; CachingReader* m_pReader; + CSAMPLE *m_pCrossFadeBuffer; }; #endif // READAHEADMANGER_H